. // Read and count comments class CommentFiles extends DataFiles { protected $setup; protected $thread; public function __construct (Setup $setup, Thread $thread) { // Construct parent class parent::__construct ($setup); // Store parameters as properties $this->setup = $setup; $this->thread = $thread; } // Gets the appropriate thread directory path protected function getThreadRoot ($thread) { // Use thread directory path if told to if ($thread !== 'auto') { return $this->setup->joinPaths ($this->setup->threadsPath, $thread); } // Otherwise, use thread from setup return $this->setup->threadPath; } // Returns a comment file path for a given file and thread protected function getCommentPath ($file, $extension, $thread = 'auto') { // Get thread root $thread = $this->getThreadRoot ($thread); // Construct full file path $path = $this->setup->joinPaths ($thread, $file . '.' . $extension); return $path; } // Read directory contents, put filenames in array, count files protected function loadFiles ($extension) { // Initial comments to return $comments = array (); // File pattern to match against $pattern = $this->setup->threadPath . '/*.' . $extension; // Find files matching the pattern, with no sorting $files = glob ($pattern, GLOB_NOSORT); // Run through comments foreach ($files as $file) { // Get file name without extension $key = basename ($file, '.' . $extension); // Add file name to comments $comments[$key] = (string)($key); } return $comments; } // Gets the appropriate metadata directory path protected function getMetaDirectory ($thread, $global) { // Check if we're getting metadata for a specific thread if ($global !== true) { // If so, use the thread's path $directory = $this->getThreadRoot ($thread) . '/metadata'; } else { // If not, use the global metadata path $directory = $this->setup->commentsPath . '/metadata'; } // Return metadata directory path return $directory; } // Gets the appropriate metadata file path protected function getMetaPath ($name, $thread, $global) { // Metadata directory path $directory = $this->getMetaDirectory ($thread, $global); // Metadata file path $path = $this->setup->joinPaths ($directory, $name . '.json'); // Return metadata file path return $path; } // Creates metadata directory if it doesn't exist protected function setupMeta ($thread, $global) { // Metadata directory path $metadata = $this->getMetaDirectory ($thread, $global); // Check if metadata root directory exists if (file_exists ($this->getThreadRoot ($thread))) { // If so, attempt to create metadata directory if (file_exists ($metadata) or @mkdir ($metadata, 0755, true)) { // If successful, set permissions to 0755 again @chmod ($metadata, 0755); return true; } // Otherwise throw exception throw new \Exception (sprintf ( 'Failed to create metadata directory at: %s', $metadata )); } } // Gets and returns latest comments from metadata files protected function getLatestComments ($path, $thread, $global) { // Latest comments $comments = array (); // Attempt to read metadata $latest = $this->readJSON ($path); // Do nothing if JSON could not be read if ($latest === false) { return $comments; } // Run through the latest comments foreach ($latest as $item) { // Get comment key $key = basename ($item); // Decide proper thread $thread = $global ? dirname ($item) : $thread; // Initial comment data $data = array ('comment' => $key, 'thread' => $thread); // Attempt to read comment $comment = $this->thread->data->read ($key, $thread); // Check if comment read successfully if ($comment !== false) { // If so, get comment status $status = Misc::getArrayItem ($comment, 'status') ?: 'approved'; // Add comment to array if it is approved if ($status === 'approved') { $comments[] = array_merge ($data, $comment); } } } return $comments; } // Read and return specific metadata from JSON file public function readMeta ($name, $thread = 'auto', $global = false) { // Metadata JSON file path $path = $this->getMetaPath ($name, $thread, $global); // Choose statement for supported metadata switch ($name) { // Latest comments case 'latest-comments': { return $this->getLatestComments ($path, $thread, $global); } // All others, just try to read as-is default: { return $this->readJSON ($path); } } } // Save metadata to specific metadata JSON file public function saveMeta ($name, array $data, $thread = 'auto', $global = false) { // Metadata JSON file path $metadata_path = $this->getMetaPath ($name, $thread, $global); // Create metadata directory if it doesn't exist $this->setupMeta ($thread, $global); // Check if metadata root directory exists if (file_exists ($this->getThreadRoot ($thread))) { // If so, attempt to save data to metadata JSON file if ($this->saveJSON ($metadata_path, $data) === false) { // Throw exception on failure throw new \Exception ( 'Failed to save metadata!' ); } } return true; } // Check if comment thread directory exists public function checkThread () { // Attempt to create the directory if (!file_exists ($this->setup->threadPath) and !@mkdir ($this->setup->threadPath, 0755, true) and !@chmod ($this->setup->threadPath, 0755)) { throw new \Exception (sprintf ( 'Failed to create comment thread directory at: %s', $this->setup->threadPath )); } // If yes, check if it is or can be made to be writable if (!is_writable ($this->setup->threadPath) and !@chmod ($this->setup->threadPath, 0755)) { throw new \Exception (sprintf ( 'Comment thread directory at %s is not writable.', $this->setup->threadPath )); } } // Queries an array of directory names protected function queryDirs ($path, $callback = false) { // Directory names output $names = array (); // Get comment directories $dirs = glob ($path . '/*', GLOB_ONLYDIR); // Convert directories paths to just their names foreach ($dirs as $name) { // Check callback conditions $match = $callback ? $callback ($name) : true; // And add directory if callback returns true if ($match === true) { $names[] = basename ($name); } } return $names; } // Queries an array of websites public function queryWebsites () { // Path to get website directories from $path = $this->setup->commentsRoot; // Get website directories $websites = $this->queryDirs ($path, function ($name) { return file_exists ($name . '/threads'); }); return $websites; } // Queries an array of comment threads public function queryThreads () { return $this->queryDirs ($this->setup->threadsPath); } // Prepends a comment to latest comments metadata file protected function prependLatestComments ($file, $global = false) { // Add thread to file if metadata is global if ($global === true) { $file = $this->setup->joinPaths ($this->setup->threadName, $file); } // Initial latest comments metadata array $latest = array ($file); // Get metadata file path $path = $this->getMetaPath ('latest-comments', 'auto', $global); // Attempt to read existing latest comments metadata $metadata = $this->readJSON ($path); // Merge existing comments with initial array if ($metadata !== false) { $latest = array_merge ($latest, $metadata); } // Maximum number of latest comments to store $latest_max = max (10, $this->setup->latestMax); // Limit latest comments metadata array to configurable size $latest = array_slice ($latest, 0, $latest_max); // Attempt to save latest comments metadata $this->saveMeta ('latest-comments', $latest, 'auto', $global); } // Removes a comment from latest comments metadata file protected function spliceLatestComments ($file, $global = false) { // Add thread to file if metadata is global if ($global === true) { $file = $this->setup->joinPaths ($this->setup->threadName, $file); } // Get metadata file path $path = $this->getMetaPath ('latest-comments', 'auto', $global); // Attempt to read existing latest comments metadata $latest = $this->readJSON ($path); // Check if latest comments metadata read successfully if ($latest !== false) { // If so, get index of file in array $index = array_search ($file, $latest); // Remove comment from latest array if ($index !== false) { array_splice ($latest, $index, 1); } // Attempt to save latest comments metadata $this->saveMeta ('latest-comments', $latest, 'auto', $global); } } // Adds a comment to latest comments metadata file public function addLatestComment ($file) { // Add comment to thread-specific latest comments metadata $this->prependLatestComments ($file); // Add comment to global latest comments metadata $this->prependLatestComments ($file, true); } // Removes a comment from latest comments metadata file public function removeFromLatest ($file) { // Add comment to thread-specific latest comments metadata $this->spliceLatestComments ($file); // Add comment to global latest comments metadata $this->spliceLatestComments ($file, true); } }