From 866bbfaa7f2e265432eb1a906a70d22151c7508f Mon Sep 17 00:00:00 2001 From: Trevor Slocum Date: Sat, 26 Jul 2014 03:22:53 -0700 Subject: [PATCH] Add PDO database mode and fix an HTML page caching issue. Closes #11. Code contributed by @mozai - thanks! --- README.md | 1 + imgboard.php | 2 +- inc/database_pdo.php | 207 +++++++++++++++++++++++++++++++++++++++++++ inc/defines.php | 9 ++ inc/functions.php | 4 +- inc/html.php | 2 +- settings.default.php | 10 ++- 7 files changed, 229 insertions(+), 6 deletions(-) create mode 100644 inc/database_pdo.php diff --git a/README.md b/README.md index e6acb85..46cc3a0 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Installing - Ensure your web host is running Linux. - Install [mediainfo](http://mediaarea.net/en/MediaInfo) and [ffmpegthumbnailer](https://code.google.com/p/ffmpegthumbnailer/). On Ubuntu, run ``sudo apt-get install mediainfo ffmpegthumbnailer``. - Set ``TINYIB_WEBM`` to ``true``. + - When setting ``TINYIB_DBMODE`` to ``pdo``, note that PDO mode has been tested on **MySQL databases only**. Theoretically it will work with any applicable driver, but this is not guaranteed. If you use an alternative driver, please report back regarding how it works. 6. [CHMOD](http://en.wikipedia.org/wiki/Chmod) write permissions to these directories: - ./ (the directory containing TinyIB) - ./src/ diff --git a/imgboard.php b/imgboard.php index 6572077..0f80b77 100644 --- a/imgboard.php +++ b/imgboard.php @@ -42,7 +42,7 @@ foreach ($writedirs as $dir) { } $includes = array("inc/defines.php", "inc/functions.php", "inc/html.php"); -if (in_array(TINYIB_DBMODE, array('flatfile', 'mysql', 'mysqli', 'sqlite'))) { +if (in_array(TINYIB_DBMODE, array('flatfile', 'mysql', 'mysqli', 'sqlite', 'pdo'))) { $includes[] = 'inc/database_' . TINYIB_DBMODE . '.php'; } else { fancyDie("Unknown database mode specificed"); diff --git a/inc/database_pdo.php b/inc/database_pdo.php new file mode 100644 index 0000000..5af52db --- /dev/null +++ b/inc/database_pdo.php @@ -0,0 +1,207 @@ + 0) { + $dsn .= ";port=" . TINYIB_DBPORT; + } + $dsn .= ";dbname=" . TINYIB_DBNAME; +} else { // Use a custom DSN + $dsn = TINYIB_DBDSN; +} + +$options = array(PDO::ATTR_PERSISTENT => true, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'); + +try { + $dbh = new PDO($dsn, TINYIB_DBUSERNAME, TINYIB_DBPASSWORD, $options); +} catch (PDOException $e) { + fancyDie("Failed to connect to the database: " . $e->getMessage()); +} + +// Create the posts table if it does not exist +$dbh->query("SHOW TABLES LIKE " . $dbh->quote(TINYIB_DBPOSTS)); +if ($dbh->query("SELECT FOUND_ROWS()")->fetchColumn() == 0) { + $dbh->exec($posts_sql); +} + +// Create the bans table if it does not exist +$dbh->query("SHOW TABLES LIKE " . $dbh->quote(TINYIB_DBBANS)); +if ($dbh->query("SELECT FOUND_ROWS()")->fetchColumn() == 0) { + $dbh->exec($bans_sql); +} + +# Utililty +function pdoQuery($sql, $params = false) { + global $dbh; + + if ($params) { + $statement = $dbh->prepare($sql); + $statement->execute($params); + } else { + $statement = $dbh->query($sql); + } + + return $statement; +} + +# Post Functions +function uniquePosts() { + $result = pdoQuery("SELECT COUNT(DISTINCT(ip)) FROM " . TINYIB_DBPOSTS); + return (int)$result->fetchColumn(); +} + +function postByID($id) { + $result = pdoQuery("SELECT * FROM " . TINYIB_DBPOSTS . " WHERE id = ?", array($id)); + if ($result) { + return $result->fetch(); + } +} + +function threadExistsByID($id) { + $result = pdoQuery("SELECT COUNT(*) FROM " . TINYIB_DBPOSTS . " WHERE id = ? AND parent = 0", array($id)); + return $result->fetchColumn() != 0; +} + +function insertPost($post) { + global $dbh; + $now = time(); + $stm = $dbh->prepare("INSERT INTO " . TINYIB_DBPOSTS . " (parent, timestamp, bumped, ip, name, tripcode, email, nameblock, subject, message, password, file, file_hex, file_original, file_size, file_size_formatted, image_width, image_height, thumb, thumb_width, thumb_height) " . + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + $stm->execute(array($post['parent'], $now, $now, $_SERVER['REMOTE_ADDR'], $post['name'], $post['tripcode'], $post['email'], + $post['nameblock'], $post['subject'], $post['message'], $post['password'], + $post['file'], $post['file_hex'], $post['file_original'], $post['file_size'], $post['file_size_formatted'], + $post['image_width'], $post['image_height'], $post['thumb'], $post['thumb_width'], $post['thumb_height'])); + return $dbh->lastInsertId(); +} + +function bumpThreadByID($id) { + $now = time(); + pdoQuery("UPDATE " . TINYIB_DBPOSTS . " SET bumped = ? WHERE id = ?", array($now, $id)); +} + +function countThreads() { + $result = pdoQuery("SELECT COUNT(*) FROM " . TINYIB_DBPOSTS . " WHERE parent = 0"); + return (int)$result->fetchColumn(); +} + +function allThreads() { + $threads = array(); + $results = pdoQuery("SELECT * FROM " . TINYIB_DBPOSTS . " WHERE parent = 0 ORDER BY bumped DESC"); + while ($row = $results->fetch()) { + $threads[] = $row; + } + return $threads; +} + +function numRepliesToThreadByID($id) { + $result = pdoQuery("SELECT COUNT(*) FROM " . TINYIB_DBPOSTS . " WHERE parent = ?", array($id)); + return (int)$result->fetchColumn(); +} + +function postsInThreadByID($id) { + $posts = array(); + $results = pdoQuery("SELECT * FROM " . TINYIB_DBPOSTS . " WHERE id = ? OR parent = ? ORDER BY id ASC", array($id, $id)); + while ($row = $results->fetch(PDO::FETCH_ASSOC)) { + $posts[] = $row; + } + return $posts; +} + +function postsByHex($hex) { + $posts = array(); + $results = pdoQuery("SELECT * FROM " . TINYIB_DBPOSTS . " WHERE file_hex = ? LIMIT 1", array($hex)); + while ($row = $results->fetch(PDO::FETCH_ASSOC)) { + $posts[] = $row; + } + return $posts; +} + +function latestPosts() { + $posts = array(); + $results = pdoQuery("SELECT * FROM " . TINYIB_DBPOSTS . " ORDER BY timestamp DESC LIMIT 10"); + while ($row = $results->fetch(PDO::FETCH_ASSOC)) { + $posts[] = $row; + } + return $posts; +} + +function deletePostByID($id) { + $posts = postsInThreadByID($id); + foreach ($posts as $post) { + if ($post['id'] != $id) { + deletePostImages($post); + pdoQuery("DELETE FROM " . TINYIB_DBPOSTS . " WHERE id = ?", array($id)); + } else { + $thispost = $post; + } + } + if (isset($thispost)) { + if ($thispost['parent'] == TINYIB_NEWTHREAD) { + @unlink('res/' . $thispost['id'] . '.html'); + } + deletePostImages($thispost); + pdoQuery("DELETE FROM " . TINYIB_DBPOSTS . " WHERE id = ?", array($thispost['id'])); + } +} + +function trimThreads() { + $limit = (int)TINYIB_MAXTHREADS; + if ($limit > 0) { + $results = pdoQuery("SELECT id FROM " . TINYIB_DBPOSTS . " WHERE parent = 0 ORDER BY bumped LIMIT 100 OFFSET " . $limit + ); + # old mysql, sqlite3: SELECT id FROM $table ORDER BY bumped LIMIT $limit,100 + # mysql, postgresql, sqlite3: SELECT id FROM $table ORDER BY bumped LIMIT 100 OFFSET $limit + # oracle: SELECT id FROM ( SELECT id, rownum FROM $table ORDER BY bumped) WHERE rownum >= $limit + # MSSQL: WITH ts AS (SELECT ROWNUMBER() OVER (ORDER BY bumped) AS 'rownum', * FROM $table) SELECT id FROM ts WHERE rownum >= $limit + foreach ($results as $post) { + deletePostByID($post['id']); + } + } +} + +function lastPostByIP() { + $result = pdoQuery("SELECT * FROM " . TINYIB_DBPOSTS . " WHERE ip = ? ORDER BY id DESC LIMIT 1", array($_SERVER['REMOTE_ADDR'])); + return $result->fetch(PDO::FETCH_ASSOC); +} + +# Ban Functions +function banByID($id) { + $result = pdoQuery("SELECT * FROM " . TINYIB_DBBANS . " WHERE id = ?", array($id)); + return $result->fetch(PDO::FETCH_ASSOC); +} + +function banByIP($ip) { + $result = pdoQuery("SELECT * FROM " . TINYIB_DBBANS . " WHERE ip = ? LIMIT 1", array($ip)); + return $result->fetch(PDO::FETCH_ASSOC); +} + +function allBans() { + $bans = array(); + $results = pdoQuery("SELECT * FROM " . TINYIB_DBBANS . " ORDER BY timestamp DESC"); + while ($row = $results->fetch(PDO::FETCH_ASSOC)) { + $bans[] = $row; + } + return $bans; +} + +function insertBan($ban) { + global $dbh; + $now = time(); + $stm = $dbh->prepare("INSERT INTO " . TINYIB_DBBANS . " (ip, timestamp, expire, reason) VALUES (?, ?, ?, ?)"); + $stm->execute(array($ban['ip'], $now, $ban['expire'], $ban['reason'])); + return $dbh->lastInsertId(); +} + +function clearExpiredBans() { + $now = time(); + pdoQuery("DELETE FROM " . TINYIB_DBBANS . " WHERE expire > 0 AND expire <= ?", array($now)); +} + +function deleteBanByID($id) { + pdoQuery("DELETE FROM " . TINYIB_DBBANS . " WHERE id = ?", array($id)); +} diff --git a/inc/defines.php b/inc/defines.php index 7af2f49..1807cfd 100644 --- a/inc/defines.php +++ b/inc/defines.php @@ -30,3 +30,12 @@ if (!defined('TINYIB_WEBM')) { if (!defined('TINYIB_DBMIGRATE')) { define('TINYIB_DBMIGRATE', false); } +if (!defined('TINYIB_DBPORT')) { + define('TINYIB_DBPORT', 3306); +} +if (!defined('TINYIB_DBDRIVER')) { + define('TINYIB_DBDRIVER', 'pdo'); +} +if (!defined('TINYIB_DBDSN')) { + define('TINYIB_DBDSN', ''); +} diff --git a/inc/functions.php b/inc/functions.php index 27b0f97..7882b73 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -29,7 +29,7 @@ $posts_sql = "CREATE TABLE `" . TINYIB_DBPOSTS . "` ( PRIMARY KEY (`id`), KEY `parent` (`parent`), KEY `bumped` (`bumped`) -) ENGINE=MyISAM"; +)"; $bans_sql = "CREATE TABLE `" . TINYIB_DBBANS . "` ( `id` mediumint(7) unsigned NOT NULL auto_increment, @@ -39,7 +39,7 @@ $bans_sql = "CREATE TABLE `" . TINYIB_DBBANS . "` ( `reason` text NOT NULL, PRIMARY KEY (`id`), KEY `ip` (`ip`) -) ENGINE=MyISAM"; +)"; function cleanString($string) { $search = array("<", ">"); diff --git a/inc/html.php b/inc/html.php index 309daa6..60c7a37 100644 --- a/inc/html.php +++ b/inc/html.php @@ -18,7 +18,7 @@ EOF; - + EOF; return $return; diff --git a/settings.default.php b/settings.default.php index ac9c5ed..6fc6d14 100644 --- a/settings.default.php +++ b/settings.default.php @@ -20,13 +20,19 @@ define('TINYIB_LOGO', ""); // Logo HTML define('TINYIB_TRIPSEED', ""); // Enter some random text - Used when generating secure tripcodes - Must not change once set define('TINYIB_ADMINPASS', ""); // Text entered at the manage prompt to gain administrator access define('TINYIB_MODPASS', ""); // Moderators only have access to delete posts ["" to disable] -define('TINYIB_DBMODE', "flatfile"); // Choose: flatfile / mysql / mysqli / sqlite (flatfile is not recommended for popular sites) +define('TINYIB_DBMODE', "flatfile"); // Choose: flatfile / mysql / mysqli / sqlite / pdo (flatfile is not recommended for popular sites) define('TINYIB_DBMIGRATE', false); // Enable database migration tool (see README for instructions) -// Note: The following only apply when TINYIB_DBMODE is set to mysql or mysqli (recommended) +// Note: The following only apply when TINYIB_DBMODE is set to mysql, mysqli (recommended over mysql) or pdo with default (blank) TINYIB_DBDSN (this is recommended most) define('TINYIB_DBHOST', "localhost"); +define('TINYIB_DBPORT', 3306); // Set to 0 if you are using a UNIX socket as the host define('TINYIB_DBUSERNAME', ""); define('TINYIB_DBPASSWORD', ""); define('TINYIB_DBNAME', ""); define('TINYIB_DBPOSTS', TINYIB_BOARD . "_posts"); define('TINYIB_DBBANS', "bans"); + +// Note: The following only apply when TINYIB_DBMODE is set to pdo (see README for instructions) +define('TINYIB_DBDRIVER', "mysql"); // PDO driver to use (mysql / sqlite / pgsql / etc.) +define('TINYIB_DBDSN', ""); // Enter a custom DSN to override all of the connection/driver settings above (see README for instructions) +# You should still set TINYIB_DBDRIVER appropriately when using a custom DSN