. class Database extends Secrets { protected $setup; protected $database; // Initial comment data protected $commentsTable = array ( 'domain' => '', 'thread' => '', 'comment' => '', 'body' => '', 'status' => '', 'date' => '', 'name' => '', 'password' => '', 'login_id' => '', 'email' => '', 'encryption' => '', 'email_hash' => '', 'notifications' => '', 'website' => '', 'ipaddr' => '', 'likes' => 0, 'dislikes' => 0 ); public function __construct (Setup $setup) { // Store parameters as properties $this->setup = $setup; // Check if database type is SQLite if ($this->databaseType === 'sqlite') { // If so, construct SQLite file name $file = sprintf ('%s/%s.sqlite', $setup->commentsRoot, $this->databaseName ); // Instantiate an SQLite data object $this->database = new \PDO ('sqlite:' . $file); // And change file permissions @chmod ($file, 0600); } else { // If not, create SQL server connection statement $connection = implode (';', array ( 'host=' . $this->databaseHost, 'port=' . $this->databasePort, 'dbname=' . $this->databaseName, 'charset=' . $this->databaseCharset )); // And create SQL server data object $this->database = new \PDO ( // PDO driver and connection details $this->databaseType . ':' . $connection, // Database user as configured $this->databaseUser, // Database password as configured $this->databasePassword, // We want the number of found (matched) rows, // not the number of changed rows array ( \PDO::MYSQL_ATTR_FOUND_ROWS => true ) ); } } // Prepares and executes an SQL statement protected function executeStatement ($statement, $data = null) { // Prepare statement $prepare = $this->database->prepare ($statement); // Check if prepare was successful if ($prepare !== false) { // If so, attempt to execute statement $execute = $prepare->execute ($data); // And return statement object if execute was successful if ($execute !== false) { return $prepare; } } return false; } // Returns a given thread or thread from setup protected function getCommentThread ($thread) { // Return thread from setup if thread is auto if ($thread === 'auto') { return $this->setup->threadName; } // Otherwise, return given thread return $thread; } // Creates table creation statement from array protected function creationArray (array $columns) { // Initial statement $statement = array (); // Create a statement using specific columns foreach ($columns as $name => $value) { // Decide type based on value type $type = is_numeric ($value) ? 'INTEGER' : 'TEXT'; // And add column to statement $statement[] = sprintf ('`%s` %s', $name, $type); } return $statement; } // Reads and returns specific metadata from database public function readMeta ($name, $thread = 'auto', $global = false) { // Get thread $thread = $this->getCommentThread ($thread); // Prepared data for statement execution $prepared = array ( 'domain' => $this->setup->website ); // Choose statement for supported metadata switch ($name) { // Latest comments case 'latest-comments': { // Initial statement $statements = array ( 'SELECT * FROM `comments`', 'WHERE (status IS NULL OR status="approved")', 'AND domain=:domain' ); // Check if we are getting metadata from multiple threads if ($global === false) { // If so, add thread condition to statement $statements[] = 'AND thread=:thread'; // And add thread to prepared data $prepared['thread'] = $thread; } // Sort comments by date $statements[] = 'ORDER BY `date` DESC'; // Limit comments to configured maximum $statements[] = 'LIMIT ' . $this->setup->latestMax; break; } // All others, just try to read as-is default: { // Initial statement $statements = array ( sprintf ('SELECT * FROM `%s`', $name), 'WHERE domain=:domain' ); // Check if we are getting metadata from multiple threads if ($global === false) { // Add thread condition to statement $statements[] = 'AND thread=:thread'; // And add thread to prepared data $prepared['thread'] = $thread; } break; } } // Convert statements array into string $statement = implode (' ', $statements); // Query statement $results = $this->executeStatement ($statement, $prepared); // Check if the query was successful if ($results !== false) { // If so, attempt to get all metadata $fetch_all = $results->fetchAll (\PDO::FETCH_ASSOC); // Check if metadata read successfully if (!empty ($fetch_all)) { // If so, return first for if metadata is page info if ($name === 'page-info') { return $fetch_all[0]; } // Otherwise, return all metadata return $fetch_all; } } return false; } // Creates comment table if it doesn't exist protected function createTable ($name, array $columns) { // Statement for creating an initial table $statement = sprintf ( 'CREATE TABLE IF NOT EXISTS `%s` (%s)', $name, implode (', ', $columns) ); // Execute statement $created = $this->executeStatement ($statement); // Throw exception on failure if ($created === false) { throw new \Exception (sprintf ( 'Failed to create "%s" table!', $name )); } } // Get formatted string of array keys protected function formatKeys (array $data, $format, $glue = ', ') { // Initial formatted output $formatted = array (); // Add each formatted array key foreach (array_keys ($data) as $key) { $formatted[] = str_replace ('%s', $key, $format); } // And convert formatted array to string $statement = implode ($glue, $formatted); return $statement; } // Saves metadata to specific metadata JSON file public function saveMeta ($name, array $data, $thread = 'auto') { // Get thread $thread = $this->getCommentThread ($thread); // Add website domain and thread to data $data = array_merge (array ( 'domain' => $this->setup->website, 'thread' => $thread ), $data); // Get metadata table creation statements $creation_statement = $this->creationArray ($data); // Attempt to create metadata table $this->createTable ($name, $creation_statement); // Update data in specific columns $save = implode (' ', array ( sprintf ('UPDATE `%s`', $name), 'SET ' . $this->formatKeys ($data, '%s=:%s'), 'WHERE domain=:domain', 'AND thread=:thread' )); // Execute statement $saved = $this->executeStatement ($save, $data); // Check if we failed to update any rows if ($saved !== false and $saved->rowCount () === 0) { // If so, create metadata column names list $column_names = $this->formatKeys ($data, '`%s`'); // Create metadata column values list $column_values = $this->formatKeys ($data, ':%s'); // Insert data into specific columns $save = sprintf ( 'INSERT INTO `%s` (%s) VALUES (%s)', $name, $column_names, $column_values ); // Execute statement $saved = $this->executeStatement ($save, $data); } // Throw exception on failure if ($saved === false) { throw new \Exception ( 'Failed to save metadata!' ); } } // Writes new or changed content to database public function write ($action, $thread, array $data, $alt = false) { // Get thread $thread = $this->getCommentThread ($thread); // Add website domain and thread to data $data = array_merge ($data, array ( 'domain' => $this->setup->website, 'thread' => $thread )); // Decide on an action switch ($action) { // Action for posting a comment case 'insert': { // Construct SQL statement $query = sprintf ( // Insertion statement 'INSERT INTO `comments` VALUES (%s)', // Get list of table columns $this->formatKeys ($this->commentsTable, ':%s') ); break; } // Action for editing a comment case 'update': { // Columns to query $columns = implode (', ', array ( 'body=:body', 'status=:status', 'name=:name', 'password=:password', 'email=:email', 'encryption=:encryption', 'email_hash=:email_hash', 'notifications=:notifications', 'website=:website', 'likes=:likes', 'dislikes=:dislikes' )); // Construct SQL statement $query = implode (' ', array ( 'UPDATE `comments`', 'SET ' . $columns, 'WHERE domain=:domain', 'AND thread=:thread', 'AND comment=:comment' )); break; } // Action for deleting a comment case 'delete': { // Check if we're actually deleting the comment if ($alt === true) { // If so, use delete statement $query = implode (' ', array ( 'DELETE FROM `comments`', 'WHERE domain=:domain', 'AND thread=:thread', 'AND comment=:comment' )); } else { // If not, use status update statement $query = implode (' ', array ( 'UPDATE `comments`', 'SET status=:status', 'WHERE domain=:domain', 'AND thread=:thread', 'AND comment=:comment' )); } break; } } // Execute statement $queried = $this->executeStatement ($query, $data); // Throw exception on failure if ($queried === false) { throw new \Exception ( 'Failed to write to database!' ); } return true; } // Check if comments table exists public function checkThread () { // Create comments table creation statements $statement = $this->creationArray ($this->commentsTable); // Create initial comments if it doesn't exist $this->createTable ('comments', $statement); } // Queries unique rows as of a specific column an array protected function queryColumn ($column) { // Select unique thread names $results = $this->executeStatement (sprintf ( 'SELECT DISTINCT `%s` FROM `comments`', $column )); // Check if query was successful if ($results !== false) { // If so, fetch all rows in column $fetch_all = $results->fetchAll (\PDO::FETCH_ASSOC); // Return column as array return array_column ($fetch_all, $column); } // Otherwise, return an empty array return array (); } // Queries an array of websites public function queryWebsites () { return $this->queryColumn ('domain'); } // Queries an array of comment threads public function queryThreads () { return $this->queryColumn ('thread'); } // These methods are not necessary in SQL public function addLatestComment () {} public function removeFromLatest () {} }