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