Rev 1 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download
<?php namespace ProcessWire;/*** Comments Manager** Manage all comments field data in chronological order.** ProcessWire 3.x* Copyright (C) 2019 by Ryan Cramer* This file licensed under Mozilla Public License v2.0 http://mozilla.org/MPL/2.0/** https://processwire.com**/class ProcessCommentsManager extends Process {/*** Return information about this module (required)**/public static function getModuleInfo() {return array('title' => __('Comments', __FILE__),'summary' => __('Manage comments in your site outside of the page editor.', __FILE__),'version' => 10,'author' => 'Ryan Cramer','icon' => 'comments','requires' => 'FieldtypeComments','searchable' => 'comments','permission' => 'comments-manager','permissions' => array('comments-manager' => 'Use the comments manager',),'page' => array('name' => 'comments','parent' => 'setup','title' => 'Comments',),'nav' => array(array('url' => '?go=approved','label' => __('Approved', __FILE__),),array('url' => '?go=pending','label' => __('Pending', __FILE__),),array('url' => '?go=spam','label' => __('Spam', __FILE__),),array('url' => '?go=all','label' => __('All', __FILE__),)));}/*** Statuses and names that a Comment can have**/protected $statuses = array();/*** Translated statuses**/protected $statusTranslations = array();/*** Translated flags** @var array**/protected $notifyFlagsTranslations = array();/*** Number of comments to show per page**/protected $limit = 10;/*** Headline for masthead**/protected $headline = '';/*** Translated 'All' label** @var string**/protected $labelAll = 'All';/*** Initialize the comments manager and define the statuses**/public function init() {$this->wire('modules')->get('FieldtypeComments');parent::init();$this->statuses = array(Comment::statusFeatured => 'featured',Comment::statusApproved => 'approved',Comment::statusPending => 'pending',Comment::statusSpam => 'spam',Comment::statusDelete => 'delete');$this->statusTranslations = array(Comment::statusFeatured => $this->_('Featured'),Comment::statusApproved => $this->_('Approved'),Comment::statusPending => $this->_('Pending'),Comment::statusSpam => $this->_('Spam'),Comment::statusDelete => $this->_('Delete'));$this->notifyFlagsTranslations = array(0 => $this->_('No'),Comment::flagNotifyAll => $this->_('All'),Comment::flagNotifyReply => $this->_('Replies'),);$this->labelAll = $this->_('All');}/*** Ask the user to select which comments field they want to manage** Or, redirect to the comments field if there is only 1.** @return string**/public function ___execute() {$this->checkInstall();// locate all the FieldtypeComments fields$fields = array();foreach($this->fields as $field) {if($field->type instanceof FieldtypeComments) $fields[] = $field;}$count = count($fields);if(!$count) {$error = $this->_('There are no comments fields installed');$this->error($error);return "<p>$error</p>";}$go = $this->wire('sanitizer')->pageName($this->wire('input')->get('go'));if($count == 1 || $go) {$field = reset($fields);$to = 'all';if($go && in_array($go, $this->statuses)) $to = $go;$this->wire('session')->redirect("./list/$field->name/$to/");return '';}$out = "<h2>" . $this->_('Please select a comments field') . "</h2><ul>";foreach($fields as $field) {$out .= "<li><a href='./list/{$field->name}/pending/'>{$field->name}</a></li>";}$out .= "</ul>";return $out;}/*** Execute the comments list** @return string**/public function ___executeList() {if(wireClassExists("CommentStars")) {$config = $this->config;$cssFile = $config->urls('FieldtypeComments') . 'comments.css';$jsFile = $config->urls('FieldtypeComments') . 'comments.js';$config->styles->add($cssFile);$config->scripts->add($jsFile);CommentStars::setDefault('star', wireIconMarkup('star'));}$session = $this->wire('session'); /** @var Session $session */$input = $this->wire('input'); /** @var WireInput $input */$sanitizer = $this->wire('sanitizer'); /** @var Sanitizer $sanitizer */$page = $this->wire('page'); /** @var Page $page */$commentID = (int) $input->get('id');$name = $sanitizer->fieldName($input->urlSegment2);if(!$name) return $this->error($this->_('No comments field specified in URL'));$field = $this->fields->get($name);if(!$field || !$field->type instanceof FieldtypeComments) return $this->error($this->_('Unrecognized field'));$status = $input->urlSegment3;if(empty($status) || ($status != 'all' && !in_array($status, $this->statuses))) {$redirectUrl = $page->url() . "list/$field->name/all/";if($commentID) $redirectUrl .= "?id=$commentID";$session->redirect($redirectUrl);}$statusNum = array_search($status, $this->statuses);$headline = $statusNum !== false ? $this->statusTranslations[$statusNum] : $status;if($headline === 'all') $headline = $this->labelAll;$this->breadcrumb('../', $field->getLabel());$limit = (int) $input->get('limit');if($limit) {$session->setFor($this, 'limit', $limit);$session->redirect('./');} else {$limit = (int) $session->getFor($this, 'limit');if(!$limit) $limit = (int) $this->limit;}$sort = $sanitizer->name($input->get('sort'));if($sort) {$session->setFor($this, 'sort', $sort);$session->redirect('./');} else {$sort = $session->getFor($this, 'sort');if(!$sort) $sort = '-created';}$start = ($input->pageNum() - 1) * $limit;$selector = "start=$start, limit=$limit, sort=$sort";if($status != 'all') $selector .= ", status=$statusNum";$filterOut = '';$filterLabels = array('id' => $this->_('ID'),'parent_id' => $this->_('Replies to'),'pages_id' => $this->_('Page'),);$properties = array('cite','email','text','ip','id','pages_id','parent_id','stars');$q = $input->get('q');if($q !== null) {// query $q that contain a selector$q = trim($sanitizer->text($q, array('stripTags' => false)));$op = Selectors::stringHasOperator($q, true);if($op) {list($property, $value) = explode($op, $q, 2);$property = $sanitizer->fieldName($property);if(!in_array($property, $properties)) $property = '';} else {$property = 'text';$op = strpos($q, ' ') ? '~=' : '%=';$value = $q;}if($property && $value) {$selector .= ", $property$op" . $sanitizer->selectorValue($value);$input->whitelist('q', "$property$op$value");}$filterOut .= $sanitizer->entities(", $property$op$value");}foreach($properties as $key) {$value = $input->get($key);if(is_null($value)) continue;if($key == 'id' || $key == 'pages_id' || $key == 'parent_id' || $key == 'stars') {$value = (int) $value;} else {$value = trim($sanitizer->text($value));}$input->whitelist($key, $value);$value = $sanitizer->selectorValue($value);$selector .= ", $key=$value";$filterLabel = isset($filterLabels[$key]) ? $filterLabels[$key] : ucfirst($key);$filterOut .= $sanitizer->entities(", $filterLabel: $value");}/** @var FieldtypeComments $fieldtype */$fieldtype = $field->type;$comments = $fieldtype->find($field, $selector);if($input->post('processComments')) {$this->processComments($comments, $field);}if($filterOut) {$this->breadcrumb('./', $headline);$headline = trim($filterOut, ", ");}$this->headline = $headline;return $this->renderComments($comments);}/*** Process changes to posted comments** @param CommentArray $comments* @param Field $field**/protected function processComments(CommentArray $comments, Field $field) {$numDeleted = 0;$numChanged = 0;$isSuperuser = $this->user->isSuperuser();$allowChangeParent = $isSuperuser && $field->get('depth') > 0;$allowChangePage = $isSuperuser;$commentField = $field instanceof CommentField ? $field : null;/** @var FieldtypeComments $fieldtype */$fieldtype = $field->type;/** @var WireInput $input */$input = $this->wire('input');foreach($comments as $comment) {/** @var Comment $comment */$properties = array();$text = $input->post("CommentText{$comment->id}");if(!is_null($text) && $text != $comment->text) {$comment->text = $text; // cleans it$properties['text'] = $comment->text;$numChanged++;}if($field->get('useVotes')) {foreach(array("upvotes", "downvotes") as $name) {$votes = (int) $input->post("Comment" . ucfirst($name) . $comment->id);if($votes != $comment->$name) {$comment->set($name, $votes);$properties[$name] = $comment->$name;$numChanged++;}}}if($field->get('useStars')) {$stars = (int) $input->post("CommentStars$comment->id");if($stars != $comment->stars) {$comment->set('stars', $stars);$properties['stars'] = $comment->stars;$numChanged++;}}$_status = $input->post("CommentStatus{$comment->id}");$status = (int) $_status;if($status === Comment::statusDelete && (!$commentField || $commentField->allowDeleteComment($comment))) {if($fieldtype->deleteComment($comment->getPage(), $field, $comment)) {$this->message(sprintf($this->_('Deleted comment #%d'), $comment->id));$numDeleted++;}continue;}if($_status !== null && $status !== (int) $comment->status && array_key_exists($status, $this->statuses)) {$comment->status = $status;$numChanged++;$properties['status'] = $comment->status;}$notify = $input->post("CommentNotify{$comment->id}");if($field->useNotify && ctype_digit($notify)) {$notify = (int) $notify;if($notify === 0 && $comment->flags) {if($comment->flags & Comment::flagNotifyAll) $comment->flags = $comment->flags & ~Comment::flagNotifyAll;if($comment->flags & Comment::flagNotifyReply) $comment->flags = $comment->flags & ~Comment::flagNotifyReply;if($comment->flags & Comment::flagNotifyConfirmed) $comment->flags = $comment->flags & ~Comment::flagNotifyConfirmed;$properties['flags'] = $comment->flags;} else if($notify === Comment::flagNotifyAll && !($comment->flags & Comment::flagNotifyAll)) {$comment->flags = $comment->flags | Comment::flagNotifyAll;$properties['flags'] = $comment['flags'];} else if($notify === Comment::flagNotifyReply) {$comment->flags = $comment->flags | Comment::flagNotifyReply;$properties['flags'] = $comment['flags'];}}$changePage = null;if($allowChangePage) {// check for change of Page ID$pageID = (int) $input->post("CommentPage{$comment->id}");if($pageID > 0 && "$pageID" !== "$comment->page") {$page = $this->wire('pages')->get($pageID);$parentID = $comment->parent_id;if($parentID) $comment->parent_id = 0; // temporarily set to 0 for page changeif(!$page->id) {$this->error(sprintf($this->_('Unable to find page: %d'), $pageID));} else if(!$page->hasField($field)) {$this->error(sprintf($this->_('Page %d does not have field: %s'), $pageID, "$field"));} else if($commentField && !$commentField->allowCommentPage($comment, $page, true)) {// this one reports errors on its own} else {$this->message(sprintf($this->_('Moved comment #%1$d from page %2$d to page %3$d'), $comment->id, $comment->page->id, $pageID));$properties['pages_id'] = $pageID;if($comment->parent_id) {$comment->parent_id = 0;$properties['parent_id'] = 0;}$changePage = $page;$numChanged++;}if($changePage === null) {// if page was not changed, restore back to original parentif($parentID) $comment->parent_id = $parentID;}}}$changeParentID = null;if($allowChangeParent) {// check for change of parent on threaded comment$parentID = $input->post("CommentParent$comment->id");if(strlen("$parentID") && ctype_digit("$parentID")) {// allows for parent_id "0" but ignore blank$parentID = (int) $parentID;if($parentID != $comment->parent_id) {if(!empty($properties['pages_id'])) {// we will apply the parent change after the Page change has applied$changeParentID = $parentID;} else {// parent ID has changed to another parent, or to no parent (0)if($commentField && $commentField->allowCommentParent($comment, $parentID, true)) {$comment->parent_id = $parentID;$properties['parent_id'] = $parentID;$numChanged++;}}}} else {$parentID = null;}}if(count($properties)) {$fieldtype->updateComment($comment->getPage(), $field, $comment, $properties);$this->message(sprintf($this->_('Updated comment #%d'), $comment->id) . " (" . implode(', ', array_keys($properties)) . ")");}if($changeParentID !== null && $changePage !== null) {// parent ID has changed at the same time that Page ID changed, so we apply parentID change afterwards$comment->setPage($changePage);if($commentField && $commentField->allowCommentParent($comment, $changeParentID, true)) {$comment->parent_id = $changeParentID;$fieldtype->updateComment($changePage, $field, $comment, array('parent_id' => $changeParentID));$numChanged++;}}}if($numDeleted || $numChanged) {$pageNum = $input->pageNum() > 1 ? 'page' . $input->pageNum() : '';$this->session->redirect('./' . $pageNum . $this->getQueryString());}}/*** Render the markup for a single comment** @param Comment $comment* @return string**/protected function renderComment(Comment $comment) {$sanitizer = $this->sanitizer;$page = $comment->getPage();$pageTitle = $sanitizer->entities1($page->get('title|name'));$field = $comment->getField();$adminTheme = $this->wire('adminTheme');$isSuperuser = $this->user->isSuperuser();$allowDepth = $field->depth > 0;$allowDepthChange = $isSuperuser && $allowDepth;$allowPageChange = $isSuperuser;$parent = $comment->parent();$numChildren = 0;$text = $this->renderCommentText($comment);$id = $comment->id;$icons = array('edit' => 'edit','upvote' => 'arrow-up','downvote' => 'arrow-down','changed' => 'dot-circle-o','reply' => 'angle-double-down','replies' => 'angle-double-right',);$outs = array('status' => '','notify' => '','website' => '','stars' => '','votes' => '','page' => '','parent' => '','reply' => '','where' => '','children' => '',);$classes = array('input' => 'CommentInput','textarea' => '','radio' => '','checkbox' => '','table' => '');$labels = array('edit' => $this->_('edit'),'view' => $this->_('view'),'page' => $this->_('Page'),'date' => $this->_('When'),'status' => $this->_('Status'),'cite' => $this->_('Cite'),'website' => $this->_('Web'),'email' => $this->_('Mail'),'none' => $this->_('None'),'parent' => $this->_('Parent'),'where' => $this->_('Where'),'replyTo' => $this->_('Reply to %s'),'stars' => $this->_('Stars'),'votes' => $this->_('Votes'),'notify' => $this->_('Notify'),'commentId' => $this->_('Comment ID'),);$values = array('cite' => $comment->cite,'email' => $comment->email,'website' => $comment->website,'ip' => $comment->ip,'date' => wireDate($this->_('Y/m/d g:i a'), $comment->created),'dateRelative' => wireDate('relative', $comment->created),'parent' => $parent && $parent->id ? $parent->id : '','parentPlaceholder' => $parent && $parent->id ? '' : $labels['none'],'parentCite' => sprintf($labels['replyTo'], $labels['commentId']),'stars' => $comment->stars ? $comment->stars : '','upvotes' => $comment->upvotes,'downvotes' => $comment->downvotes,'page' => (int) "$comment->page",);$urls = array('parent' => $parent ? "../all/?id=$parent->id" : '','children' => "../all/?parent_id=$id",'siblings' => "../all/?pages_id=$page->id",'pageView' => "$page->url#Comment$id",'pageEdit' => $page->editUrl(),'email' => "./?email=" . urlencode($values['email']),'cite' => "../all/?cite=" . urlencode($values['cite']),'ip' => "../all/?ip=" . urlencode($values['ip']),);$tooltips = array('parent' => $this->_('ID of the Comment that this one is replying to.'),'page' => $this->_('ID of the Page that this comment lives on.'),'viewAll' => $this->_('View all having value'),'edit' => $this->_('Edit value'),'pageFilter' => $this->_('Show only comments from page'),);foreach($values as $key => $value) {$values[$key] = $sanitizer->entities($value);}foreach($icons as $key => $value) {$icons[$key] = wireIconMarkup($value);}if($allowDepth) {$children = $comment->children();$numChildren = count($children);}if($adminTheme && $adminTheme instanceof AdminThemeFramework) {$classes['input'] = trim("$classes[input] " . $adminTheme->getClass('input-small'));$classes['textarea'] = $adminTheme->getClass('textarea');$classes['radio'] = $adminTheme->getClass('input-radio');$classes['checkbox'] = $adminTheme->getClass('input-checkbox');$classes['table'] = $adminTheme->getClass('table');// if(strpos($classes['input'], 'uk-input') !== false) $classes['input'] .= " uk-form-blank";}foreach($this->statusTranslations as $status => $label) {if($status == Comment::statusDelete && $numChildren) continue;$checked = $comment->status == $status ? "checked='checked' " : '';$outs['status'] .="<label class='CommentStatus'>" ."<input class='$classes[radio]' type='radio' name='CommentStatus$id' value='$status' $checked/> " ."<small>$label</small>" ."</label> ";}if($page->editable()) {$text ="<div class='CommentTextEditable' id='CommentText$id' data-textarea-class='$classes[textarea]'>" ."<p>$text <a class='CommentTextEdit' href='#'>$icons[edit] $labels[edit]</a></p>" ."</div>";} else {$text = "<p>$text</p>";}if($allowPageChange) $outs['page'] ="<span class='detail ui-priority-secondary'>Page #</span> " ."<input class='$classes[input] pw-tooltip' title='$tooltips[page]' name='CommentPage$id' value='$values[page]' /> ";if($allowDepthChange) $outs['parent'] ="<span class='detail ui-priority-secondary'>" . $this->_('Reply to #') . "</span> " ."<input class='$classes[input] pw-tooltip' title='$tooltips[parent]' placeholder='$values[parentPlaceholder]' name='CommentParent$id' value='$values[parent]' />";if($outs['parent'] || $outs['page']) $outs['where'] ="<tr>" ."<th>$labels[where]</th>" ."<td class='CommentWhere'>$outs[page]$outs[parent]</td>" ."</tr>";if($values['website']) $outs['website'] ="<tr>" ."<th>$labels[website]</th>" ."<td><a target='_blank' href='$values[website]'>$values[website]</a></td>" ."</tr>";if($field->useStars) $outs['stars'] ="<tr>" ."<th>$labels[stars]</th>" ."<td class='CommentStars'>" ."<input type='hidden' name='CommentStars$id' value='$values[stars]' />" .$comment->renderStars(array('input' => true)) ."</td>" ."</tr>";if($field->useVotes) $outs['votes'] ="<tr>" ."<th>$labels[votes]</th>" ."<td class='CommentVotes'>" ."<label class='CommentUpvotes'>" ."<span>$icons[upvote]</span>" ."<input class='$classes[input]' title='upvotes' type='number' min='0' name='CommentUpvotes$id' value='$values[upvotes]' />" ."</label> " ."<label class='CommentDownvotes'>" ."<span>$icons[downvote]</span>" ."<input class='$classes[input]' title='downvotes' type='number' min='0' name='CommentDownvotes$id' value='$values[downvotes]' />" ."</label> " ."</td>" ."</tr>";if($field->useNotify) {foreach($this->notifyFlagsTranslations as $flag => $label) {$checked = false;if($flag && $comment->flags & $flag) {$checked = true; // All or Replies} else if(!$flag && !($comment->flags & 2) && !($comment->flags & 4) && !($comment->flags & 8)) {$checked = true; // None}$checked = $checked ? "checked='checked' " : '';$outs['notify'] .="<label class='CommentNotify'>" ."<input class='$classes[radio]' type='radio' name='CommentNotify$id' value='$flag' $checked/> " ."<small>$label</small>" ."</label> ";}$outs['notify'] = "<tr><th>$labels[notify]</th><td>$outs[notify]</td></tr>";}if($parent) {$a = $sanitizer->entities($parent->cite) . " <a href='$urls[parent]'>$parent->id</a>";$outs['reply'] = // displayed after table"<p class='CommentReplyInfo detail'>" ."$icons[reply] " .sprintf($labels['replyTo'], $a) ."</p>";}if($numChildren) $outs['children'] = // displayed after table"<p class='CommentChildrenInfo detail'>" ."<a href='$urls[children]'>" ."$icons[replies] " .sprintf($this->_n('%d reply', '%d replies', $numChildren), $numChildren) ."</a>" ."</p>";/** @var FieldtypeComments $fieldtype */// $fieldtype = $field->type;// $who = $fieldtype->getNotifyEmails($page, $field, $comment);// $text .= "<pre>" . htmlentities(print_r($who, true)) . "</pre>";$numRows = 0;foreach($outs as $out) if(!empty($out)) $numRows++;$contentClass = 'CommentContent';if($numRows >= 7) $contentClass .= ' CommentContentLarge';$out ="<div class='CommentItem ui-helper-clearfix CommentItemStatus$comment->status'>" ."<table class='CommentItemInfo $classes[table]' cellspacing='0'>" ."<tr class='CommentTitle'>" ."<th>" ."<label>" ."<input class='CommentCheckbox $classes[checkbox]' type='checkbox' name='comment[]' value='$id' /> " ."<span class='CommentID'>$id</span>" ."</label>" ."</th>" ."<td>" ."<a href='$urls[siblings]' class='pw-tooltip' title='$tooltips[pageFilter]'><strong>$pageTitle</strong></a> " ."<span class='detail'> " ."<a class='detail' href='$urls[pageView]'>$labels[view]</a> / " ."<a class='detail' href='$urls[pageEdit]'>$labels[edit]</a>" ."</span>" ."<span class='CommentChangedIcon'>$icons[changed]</span>" ."</td>" ."</tr>" ."<tr class='CommentItemStatus'>" ."<th>$labels[status]</th>" ."<td class='CommentStatus'>$outs[status]</td>" ."</tr>" .$outs['notify'] ."<tr>" ."<th>$labels[date]</th>" ."<td>$values[date] <span class='detail'>$values[dateRelative]</span></td>" ."</tr>" ."<tr>" ."<th>$labels[cite]</th>" ."<td class='CommentCite'>" ."<a href='$urls[cite]' class='pw-tooltip' title='$tooltips[viewAll]'>$values[cite]</a> " .//"<input type='text' class='$classes[input]' name='CommentCite$id' value='$values[cite]' hidden /> " . // @todo//"<a href='#' class='CommentToggleSiblings pw-tooltip' title='$tooltips[edit]'>$icons[edit]</a> " ."<a class='detail pw-tooltip' title='$tooltips[viewAll]' href='$urls[ip]'>$values[ip]</a> " ."</td>" ."</tr>" ."<tr>" ."<th>$labels[email]</th>" ."<td>" ."<a href='$urls[email]' class='pw-tooltip' title='$tooltips[viewAll]'>$values[email]</a> " .//"<input type='email' class='$classes[input]' name='CommentEmail$id' value='$values[email]' hidden /> " . // @todo//"<a href='#' class='CommentToggleSiblings pw-tooltip' title='$tooltips[edit]'>$icons[edit]</a>" ."</td>" ."</tr>" .$outs['website'] .$outs['stars'] .$outs['votes'] .$outs['where'] ."</table>" ."<div class='$contentClass'>" ."$outs[reply]" ."<div class='CommentText'>$text</div>" ."$outs[children]" ."</div>" ."</div>";/*$out ="<div class='CommentItem ui-helper-clearfix CommentItemStatus{$comment->status}'>" ."$out " ."<div class='CommentContent'>" ."$outs[parent]" ."<div class='CommentText'>$text</div>" ."$outs[children]" ."</div>" ."</div>";*/$page->of(false);return $out;}/*** Prep comment text for output in editor** @param Comment $comment* @return string**/protected function renderCommentText(Comment $comment) {$text = $this->sanitizer->entities($comment->get('text'));$text = str_replace('\r', ' ', $text);$text = preg_replace('/\r?(\n)/', '\r', $text);$text = str_replace('\r\r', "<br />\n<br />\n", $text);$text = str_replace('\r', "<br />\n", $text);return $text;}/*** Render the comments list header** @param int $limit* @param bool $useVotes* @param bool $useStars* @return string**/protected function renderCommentsHeader($limit, $useVotes, $useStars) {$setStatusLabel = $this->_('Set status:');$perPageLabel = $this->_('per page');$adminTheme = $this->wire('adminTheme');$selectClass = $adminTheme && $adminTheme instanceof AdminThemeFramework ? $adminTheme->getClass('select') : '';$checkboxClass = $adminTheme && $adminTheme instanceof AdminThemeFramework ? $adminTheme->getClass('input-checkbox') : '';$pagerLimitOut = "<select class='$selectClass' id='CommentLimitSelect'><option>10 $perPageLabel</option><option>25 $perPageLabel</option><option>50 $perPageLabel</option><option>100 $perPageLabel</option></select>";$pagerLimitOut = str_replace("<option>$limit ", "<option selected>$limit ", $pagerLimitOut);$checkAllLabel = $this->_('Check/uncheck all');$checkAll ="<label title='$checkAllLabel'><input class='$checkboxClass' type='checkbox' id='CommentCheckAll' /> " ."<span class='detail'></span></label>";$noCheckedLabel = $this->_('There are no checked items');$actionsOut ="<select class='$selectClass' id='CommentActions' data-nochecked='$noCheckedLabel'>" ."<option value=''>" . $this->_('Actions (checked items)') . "</option>";foreach($this->statusTranslations as $status => $label) {$actionsOut .= "<option value='$status'>$setStatusLabel $label</option>";}if($useVotes) {$actionsOut .= "<option value='reset-upvotes'>" . $this->_('Reset: Upvotes') . "</option>";$actionsOut .= "<option value='reset-downvotes'>" . $this->_('Reset: Downvotes') . "</option>";}$actionsOut .= "</select>";$sorts = array('-created' => $this->_('Date (new–old)'),'created' => $this->_('Date (old–new)'),);if($useStars) {$sorts['-stars'] = $this->_('Stars (high–low)');$sorts['stars'] = $this->_('Stars (low–high)');}if($useVotes) {$sorts['upvotes'] = $this->_('Upvotes');$sorts['downvotes'] = $this->_('Downvotes');}$sortByOut = "<select class='$selectClass' id='CommentListSort'>";$sortLabelPrefix = $this->_('Sort:');foreach($sorts as $sortKey => $sortLabel) {$sortByOut .= "<option value='$sortKey'>$sortLabelPrefix $sortLabel</option>";}$sortByOut .= "</select>";$sort = $this->wire('session')->getFor($this, 'sort');if(empty($sort)) $sort = "-created";$sortByOut = str_replace("'$sort'", "'$sort' selected", $sortByOut);return"<p class='CommentCheckAll'>$checkAll</p>" ."<p class='CommentActions'>$actionsOut</p>" ."<p class='CommentSorts'>$sortByOut</p>" ."<p class='CommentLimitSelect'>$pagerLimitOut</p>";}/*** Render the markup for a list of comments** @param CommentArray $comments* @return string**/protected function renderComments(CommentArray $comments) {$commentsBody = '';$cnt = 0;$status = $this->input->urlSegment3;$start = $comments->getStart();$limit = $comments->getLimit();$total = $comments->getTotal();$pageNumPrefix = $this->config->pageNumUrlPrefix;$pageNum = $this->wire('input')->pageNum;$queryString = $this->getQueryString();$unsavedChangesLabel = $this->_('You have unsaved changes!');$field = $comments->getField();foreach($comments as $comment) {/** @var Comment $comment */if($status && $status != 'all' && $this->statuses[$comment->status] != $status) continue;$commentsBody .= $this->renderComment($comment);$cnt++;if($cnt >= $limit) break;}/** @var MarkupPagerNav $pager */$pager = $this->wire('modules')->get('MarkupPagerNav');$pagerOut = $pager->render($comments, array('queryString' => $queryString,'baseUrl' => "./"));/** @var JqueryWireTabs $wireTabs */$wireTabs = $this->modules->get('JqueryWireTabs');$tabs = array();$class = $this->input->urlSegment3 === 'all' ? 'on' : '';$tabs["tabStatusAll"] = "<a class='$class' href='../all/'>" . $this->labelAll . "</a>";foreach($this->statuses as $status => $name) {if($status == Comment::statusDelete) continue;$class = $this->input->urlSegment3 === $name ? 'on' : '';$label = $this->statusTranslations[$status];if($label === $name) $label = ucfirst($label);$tabs["tabStatus$status"] = "<a class='$class' href='../$name/'>$label</a>";}$tabsOut = $wireTabs->renderTabList($tabs);$this->headline .= ' (' . ($start+1) . "–" . ($start + $cnt) . " " . sprintf($this->_('of %d'), $total) . ')';$this->headline($this->headline);if($cnt) {/** @var InputfieldSubmit $button */$button = $this->modules->get('InputfieldSubmit');$button->attr('name', 'processComments');$button->showInHeader();$button = $button->render();} else $button = '';if($this->input->pageNum > 1) {$queryString = "./$pageNumPrefix$pageNum$queryString";}if(!count($comments)) {return"<form>" .$tabsOut ."<h2>" . $this->_('None to display') . "</h2>" ."</form>";}$commentsHeader = $this->renderCommentsHeader($limit, $field->get('useVotes'), $field->get('useStars'));return"<form id='CommentListForm' action='$queryString' method='post' data-unsaved='$unsavedChangesLabel'>" .$tabsOut ."<div id='CommentListHeader' class='ui-helper-clearfix'>" .$pagerOut .$commentsHeader ."</div>" ."<div class='CommentItems ui-helper-clearfix'>" .$commentsBody ."</div>" .$pagerOut .$button ."</form>";}protected function getQueryString() {$queryString = '';foreach($this->input->whitelist as $key => $value) {$queryString .= $this->wire('sanitizer')->entities($key) . "=" . $this->wire('sanitizer')->entities($value) . "&";}$queryString = trim($queryString, '&');if($queryString) $queryString = "?$queryString";return $queryString;}protected function checkInstall() {if($this->wire('modules')->isInstalled('ProcessLatestComments')) {$this->warning('Please uninstall the ProcessLatestComments module (this module replaces it).');}}public function ___install() {$this->checkInstall();return parent::___install();}/*** Search for items containing $text and return an array representation of them** Implementation for SearchableModule interface** @param string $text Text to search for* @param array $options Options to modify behavior:* - `edit` (bool): True if any 'url' returned should be to edit items rather than view them* - `multilang` (bool): If true, search all languages rather than just current (default=true).* - `start` (int): Start index (0-based), if pagination active (default=0).* - `limit` (int): Limit to this many items, if pagination active (default=0, disabled).* @return array**/public function search($text, array $options = array()) {/** @var Languages $languages */$page = $this->getProcessPage();$fields = array();foreach($this->wire('fields') as $field) {if($field->type instanceof FieldtypeComments) $fields[$field->name] = $field;}$result = array('title' => $page->id ? (string) $page->title : $this->className(),'total' => 0,'url' => '','items' => array(),'properties' => array('text','cite','email','status','created','website','ip','user_agent','upvotes','downvotes','stars'));if($options['help']) return $result;$operator = empty($options['operator']) ? '%=' : $options['operator'];$value = $this->wire('sanitizer')->selectorValue($text);$summaryLength = $options['verbose'] ? 1024 : 200;if(!empty($options['property'])) {$selector = "$options[property]$operator$value";$q = "$options[property]$operator$text";} else {$selector = "text$operator$value";$q = "text{$operator}$text";}if(!empty($options['limit'])) $selector .= ", limit=$options[limit]";if(!empty($options['start'])) $selector .= ", start=$options[start]";foreach($fields as $fieldName => $field) {/** @var CommentArray $comments */$comments = $field->type->find($selector);if(!count($comments)) continue;$result['total'] += $comments->getTotal();$url = $page->url() . "list/$field->name/all/";if(count($fields) == 1) $result['url'] = $url . "?q=" . urlencode($q);foreach($comments as $comment) {/** @var Comment $comment */$commentPage = $comment->getPage();$editUrl = $url . "?id=$comment->id";$item = array('id' => $comment->id,'name' => $comment->cite,'title' => $comment->cite,'subtitle' => $commentPage && $commentPage->id ? (string) $commentPage->get('title|name') : '','summary' => $this->wire('sanitizer')->truncate($comment->text, $summaryLength),'url' => empty($options['edit']) ? $comment->url() : $editUrl);$result['items'][] = $item;}}/* // for debugging:$result['items'][] = array('id' => 0,'name' => '','title' => $selector,'subtitle' => '','summary' => '','url' => '#');*/return $result;}}