__('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 "
$error
";
}
$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 = "" . $this->_('Please select a comments field') . "
";
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 change
if(!$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 parent
if($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'] .=
" ";
}
if($page->editable()) {
$text =
"";
} else {
$text = "$text
";
}
if($allowPageChange) $outs['page'] =
"Page # " .
" ";
if($allowDepthChange) $outs['parent'] =
"" . $this->_('Reply to #') . " " .
"";
if($outs['parent'] || $outs['page']) $outs['where'] =
"" .
"$labels[where] | " .
"" .
"
";
if($values['website']) $outs['website'] =
"" .
"$labels[website] | " .
"$values[website] | " .
"
";
if($field->useStars) $outs['stars'] =
"" .
"$labels[stars] | " .
"" .
"
";
if($field->useVotes) $outs['votes'] =
"" .
"$labels[votes] | " .
"" .
"
";
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'] .=
" ";
}
$outs['notify'] = "$labels[notify] | $outs[notify] |
";
}
if($parent) {
$a = $sanitizer->entities($parent->cite) . " $parent->id";
$outs['reply'] = // displayed after table
"";
}
if($numChildren) $outs['children'] = // displayed after table
"";
/** @var FieldtypeComments $fieldtype */
// $fieldtype = $field->type;
// $who = $fieldtype->getNotifyEmails($page, $field, $comment);
// $text .= "" . htmlentities(print_r($who, true)) . "
";
$numRows = 0;
foreach($outs as $out) if(!empty($out)) $numRows++;
$contentClass = 'CommentContent';
if($numRows >= 7) $contentClass .= ' CommentContentLarge';
$out =
"";
/*
$out =
"";
*/
$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', "
\n
\n", $text);
$text = str_replace('\r', "
\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 = "
";
$pagerLimitOut = str_replace("