. class PHPMode { protected $setup; protected $ui; protected $comments; protected $rawComments; protected $crypto; protected $locale; protected $templater; protected $markdown; protected $trimTagRegexes = array ( 'blockquote' => '/(
)([\s\S]*?)(<\/blockquote>)/iS', 'ul' => '/()([\s\S]*?)(<\/ul>)/iS', 'ol' => '/(
)([\s\S]*?)(<\/ol>)/iS' ); protected $linkRegex = '/((http|https|ftp):\/\/[a-z0-9-@:;%_\+.~#?&\/=]+) {0,1}/iS'; protected $codeTagCount = 0; protected $codeTags = array (); protected $preTagCount = 0; protected $preTags = array (); protected $paragraphRegex = '/(?:\r\n|\r|\n){2}/S'; protected $lineRegex = '/(?:\r\n|\r|\n)/S'; public function __construct (Setup $setup, CommentsUI $ui, array $comments, array $raw) { // Store parameters as properties $this->setup = $setup; $this->ui = $ui; $this->comments = $comments; $this->rawComments = $raw; // Instantiate various classes $this->crypto = new Crypto (); $this->locale = new Locale ($setup); $this->templater = new Templater ($setup); $this->markdown = new Markdown (); } protected function fileFromPermalink ($permalink) { $file = substr ($permalink, 1); $file = str_replace ('r', '-', $file); $file = str_replace ('-pop', '', $file); return $file; } protected function replyCheck ($permalink) { if (empty ($_GET['hashover-reply'])) { return; } if ($_GET['hashover-reply'] === $permalink) { $file = $this->fileFromPermalink ($permalink); $form = new HTMLTag ('form', array ( 'id' => $this->ui->prefix ('reply-' . $permalink), 'class' => 'hashover-reply-form', 'method' => 'post', 'action' => $this->setup->getBackendPath ('form-actions.php') )); $subscribed = ($this->setup->subscribesUser === true); $form->innerHTML ($this->ui->replyForm ($permalink, $this->setup->pageURL, $this->setup->threadName, $this->setup->pageTitle, $file, $subscribed)); return $form->asHTML (); } } protected function editCheck ($comment) { if (empty ($_GET['hashover-edit'])) { return; } $permalink = Misc::getArrayItem ($comment, 'permalink') ?: ''; if ($_GET['hashover-edit'] === $permalink) { $file = $this->fileFromPermalink ($permalink); $body = $comment['body']; $body = preg_replace ($this->linkRegex, '\\1', $body); $status = Misc::getArrayItem ($comment, 'status') ?: 'approved'; $name = Misc::getArrayItem ($comment, 'name') ?: ''; $website = Misc::getArrayItem ($comment, 'website') ?: ''; $subscribed = isset ($comment['subscribed']); if (!empty ($this->rawComments[$file])) { $raw_comment = $this->rawComments[$file]; $email = Misc::getArrayItem ($raw_comment, 'email') ?: ''; $encryption = Misc::getArrayItem ($raw_comment, 'encryption') ?: ''; $email = $this->crypto->decrypt ($email, $encryption); } else { $email = ''; } $form = new HTMLTag ('form', array ( 'id' => $this->ui->prefix ('edit-' . $permalink), 'class' => 'hashover-edit-form', 'method' => 'post', 'action' => $this->setup->getBackendPath ('form-actions.php') ), false); $edit_form = $this->ui->editForm ($permalink, $this->setup->pageURL, $this->setup->threadName, $this->setup->pageTitle, $file, $name, $email, $website, $body, $status, $subscribed); $form->innerHTML ($edit_form); return $form->asHTML (); } } protected function codeTagReplace ($grp) { $code_placeholder = $grp[1] . 'CODE_TAG[' . $this->codeTagCount . ']' . $grp[3]; $this->codeTags[$this->codeTagCount] = trim ($grp[2], "\r\n"); $this->codeTagCount++; return $code_placeholder; } protected function codeTagReturn ($grp) { return $this->codeTags[($grp[1])]; } protected function preTagReplace ($grp) { $pre_placeholder = $grp[1] . 'PRE_TAG[' . $this->preTagCount . ']' . $grp[3]; $this->preTags[$this->preTagCount] = trim ($grp[2], "\r\n"); $this->preTagCount++; return $pre_placeholder; } protected function preTagReturn ($grp) { return $this->preTags[($grp[1])]; } // Returns the permalink of a comment's parent protected function getParentPermalink ($permalink) { $permalink_parts = explode ('r', $permalink); array_pop ($permalink_parts); return implode ('r', $permalink_parts); } // Find a comment by its permalink protected function findByPermalink ($permalink, $comments) { // Loop through all comments foreach ($comments as $comment) { // Return comment if its permalink matches if ($comment['permalink'] === $permalink) { return $comment; } // Recursively check replies when present if (!empty ($comment['replies'])) { $reply = $this->findByPermalink ($permalink, $comment['replies']); if ($reply !== '') { return $reply; } } } // Otherwise, return nothing return ''; } public function parseComment (array $comment, $parent = '', $popular = false) { $permalink = $comment['permalink']; $first_instance = 'hashover-' . $permalink; $name_class = 'hashover-name-plain'; $this->codeTagCount = 0; $this->codeTags = array (); $this->preTagCount = 0; $this->preTags = array (); // Get instantiated prefix $prefix = $this->ui->prefix (); // Initial template $template = array ( 'hashover' => $prefix, 'permalink' => $permalink ); // Text for avatar image alt attribute $permatext = substr ($permalink, 1); $permatext = explode ('r', $permatext); $permatext = array_pop ($permatext); // Wrapper element for each comment $comment_wrapper = $this->ui->commentWrapper ($permalink); // Check if this comment is a popular comment if ($popular === true) { // Attempt to get parent comment permalink $parent = $this->getParentPermalink ($permalink); // Get parent comment by its permalink if it exists if ($parent !== '') { $parent = $this->findByPermalink ($parent, $this->comments['primary']); } // And remove "-pop" from text for avatar $permatext = str_replace ('-pop', '', $permatext); } else { // Append class to indicate comment is a reply when appropriate if ($parent !== '') { $comment_wrapper->appendAttribute ('class', 'hashover-reply'); } } // Add avatar image to template $template['avatar'] = $this->ui->userAvatar ($comment['avatar'], $permalink, $permatext); if (!isset ($comment['notice'])) { $name = Misc::getArrayItem ($comment, 'name') ?: $this->setup->defaultName; $is_twitter = false; // Check if user's name is a Twitter handle if ($name[0] === '@') { $name = mb_substr ($name, 1); $name_class = 'hashover-name-twitter'; $is_twitter = true; $name_length = mb_strlen ($name); // Check if Twitter handle is valid length if ($name_length > 1 and $name_length <= 30) { // Set website to Twitter profile if a specific website wasn't given if (empty ($comment['website'])) { $comment['website'] = 'https://twitter.com/' . $name; } } } // Check whether user gave a website if (!empty ($comment['website'])) { if ($is_twitter === false) { $name_class = 'hashover-name-website'; } // If so, display name as a hyperlink $name_link = $this->ui->nameElement ('a', $permalink, $name, $comment['website']); } else { // If not, display name as plain text $name_link = $this->ui->nameElement ('span', $permalink, $name); } // Check if comment has a parent if ($parent !== '') { // If so, create the parent thread permalink $parent_thread = 'hashover-' . $parent['permalink']; // Get the parent's name $parent_name = Misc::getArrayItem ($parent, 'name') ?: $this->setup->defaultName; // Add thread parent hyperlink to template $template['parent-link'] = $this->ui->parentThreadLink ($this->setup->filePath, $parent_thread, $permalink, $parent_name); } if (isset ($comment['user-owned'])) { // Append class to indicate comment is from logged in user $comment_wrapper->appendAttribute ('class', 'hashover-user-owned'); // Define "Reply" link with original poster title $reply_title = $this->locale->text['commenter-tip']; $reply_class = 'hashover-no-email'; } else { // Check if commenter is subscribed if (isset ($comment['subscribed'])) { // If so, set subscribed title $reply_title = $name . ' ' . $this->locale->text['subscribed-tip']; $reply_class = 'hashover-has-email'; } else{ // If not, set unsubscribed title $reply_title = $name . ' ' . $this->locale->text['unsubscribed-tip']; $reply_class = 'hashover-no-email'; } } // Check if the comment is editable for the user if (isset ($comment['editable'])) { // If so, add "Edit" hyperlink to template if (!empty ($_GET['hashover-edit']) and $_GET['hashover-edit'] === $permalink) { $template['edit-link'] = $this->ui->cancelLink ($first_instance, 'edit'); } else { $template['edit-link'] = $this->ui->formLink ($this->setup->filePath, 'edit', $permalink); } } // Check if the comment has been liked if (isset ($comment['likes'])) { // Add likes to HTML template $template['likes'] = $comment['likes']; // Check if there is more than one like if ($comment['likes'] !== 1) { // If so, use "X Likes" locale $like_count = $comment['likes'] . ' ' . $this->locale->text['likes']; } else { // If not, use "X Like" locale $like_count = $comment['likes'] . ' ' . $this->locale->text['like']; } // Add like count to HTML template $template['like-count'] = $this->ui->likeCount ('likes', $permalink, $like_count); } // Check if dislikes are enabled and the comment's been disliked if ($this->setup->allowsDislikes === true and isset ($comment['dislikes'])) { // Add likes to HTML template $template['dislikes'] = $comment['dislikes']; // Check if there is more than one dislike if ($comment['dislikes'] !== 1) { // If so, use "X Dislikes" locale $dislike_count = $comment['dislikes'] . ' ' . $this->locale->text['dislikes']; } else { // If not, use "X Dislike" locale $dislike_count = $comment['dislikes'] . ' ' . $this->locale->text['dislike']; } // Add dislike count to HTML template $template['dislike-count'] = $this->ui->likeCount ('dislikes', $permalink, $dislike_count); } // Add name HTML to template $template['name'] = $this->ui->nameWrapper ($name_class, $name_link); // Add IP address HTML to template if (!empty ($comment['ipaddr'])) { $template['ipaddr'] = $this->ui->ipWrapper ($comment['ipaddr']); } // Append status text to date if (!empty ($comment['status-text'])) { $comment['date'] .= ' (' . $comment['status-text'] . ')'; } // Add date permalink hyperlink to template $template['date'] = $this->ui->dateLink ($this->setup->filePath, $first_instance, $comment['date-time'], $comment['date']); // Add "Reply" hyperlink to template if (!empty ($_GET['hashover-reply']) and $_GET['hashover-reply'] === $permalink) { $template['reply-link'] = $this->ui->cancelLink ($first_instance, 'reply', $reply_class); } else { $template['reply-link'] = $this->ui->formLink ($this->setup->filePath, 'reply', $permalink, $reply_class, $reply_title); } // Add edit form HTML to template if (isset ($comment['editable'])) { $template['edit-form'] = $this->editCheck ($comment); } // Add reply form HTML to template $template['reply-form'] = $this->replyCheck ($permalink); // Add reply count to template if (!empty ($comment['replies'])) { $template['reply-count'] = count ($comment['replies']); if ($template['reply-count'] > 0) { if ($template['reply-count'] !== 1) { $template['reply-count'] .= ' ' . $this->locale->text['replies']; } else { $template['reply-count'] .= ' ' . $this->locale->text['reply']; } } } // Add comment data to template $template['comment'] = $comment['body']; // Remove [img] tags $template['comment'] = preg_replace ('/\[(img|\/img)\]/iS', '', $template['comment']); // Add HTML anchor tag to URLs (hyperlinks) $template['comment'] = preg_replace ($this->linkRegex, '\\1', $template['comment']); // Parse markdown in comment if ($this->setup->usesMarkdown !== false) { $template['comment'] = $this->markdown->parseMarkdown ($template['comment']); } // Replace code tags with placeholder text if (mb_strpos ($template['comment'], '
') !== false) { $template['comment'] = preg_replace_callback ('/(
)([\s\S]*?)(<\/code>)/iS', 'self::codeTagReplace', $template['comment']); } // Replace pre tags with placeholder text if (mb_strpos ($template['comment'], '
') !== false) { $template['comment'] = preg_replace_callback ('/()([\s\S]*?)(<\/pre>)/iS', 'self::preTagReplace', $template['comment']); } // Check for various multi-line tags foreach ($this->trimTagRegexes as $tag => $trimTagRegex) { if (mb_strpos ($template['comment'], '<' . $tag . '>') !== false) { // Trim leading and trailing whitespace $template['comment'] = preg_replace_callback ($trimTagRegex, function ($grp) { return $grp[1] . trim ($grp[2], "\r\n") . $grp[3]; }, $template['comment']); } } // Break comment into paragraphs $paragraphs = preg_split ($this->paragraphRegex, $template['comment']); $pd_comment = ''; // Wrap each paragraph intags and place
tags after each line for ($i = 0, $il = count ($paragraphs); $i < $il; $i++) { $pd_comment .= '' . preg_replace ($this->lineRegex, '
' . PHP_EOL; } // Replace code tag placeholders with original code tag HTML if ($this->codeTagCount > 0) { $pd_comment = preg_replace_callback ('/CODE_TAG\[([0-9]+)\]/S', 'self::codeTagReturn', $pd_comment); } // Replace pre tag placeholders with original pre tag HTML if ($this->preTagCount > 0) { $pd_comment = preg_replace_callback ('/PRE_TAG\[([0-9]+)\]/S', 'self::preTagReturn', $pd_comment); } // Add paragraph'd comment data to template $template['comment'] = $pd_comment; } else { // Append notice class $comment_wrapper->appendAttribute ('class', 'hashover-notice'); $comment_wrapper->appendAttribute ('class', $comment['notice-class']); // Add notice to template $template['comment'] = $comment['notice']; // Set name to 'Comment Deleted!' $template['name'] = $this->ui->nameWrapper ($name_class, $comment['title']); } // Parse theme layout HTML template $theme_html = $this->templater->parseTheme ('comments.html', $template); // Comment HTML template $comment_wrapper->innerHTML ($theme_html); // Check if comment has replies if (!empty ($comment['replies'])) { // If so, append class to indicate comment has replies $comment_wrapper->appendAttribute ('class', 'hashover-has-replies'); // Recursively parse replies foreach ($comment['replies'] as $reply) { $comment_wrapper->appendInnerHTML ($this->parseComment ($reply, $comment)); } } return $comment_wrapper->asHTML (); } }
', $paragraphs[$i]) . '