['onPluginsInitialized', 0]];
}
/**
* Add the comment form information to the page header dynamically
*
* Used by Form plugin >= 2.0
*/
public function onFormPageHeaderProcessed(Event $event) {
$header = $event['header'];
if ($this->enable) {
if (!isset($header->form)) {
$header->form = $this->grav['config']->get('plugins.comments.form');
}
}
$event->header = $header;
}
public function onTwigSiteVariables() {
$this->grav['twig']->enable_comments_plugin = $this->enable;
$this->grav['twig']->commenting_enabled = $this->commenting_enabled;
$this->grav['twig']->pingbacks_enabled = $this->pingbacks_enabled;
$this->grav['twig']->comments = $this->fetchComments();
//$this->grav['twig']->pingbacks = $this->fetchPingbacks();
$this->grav['twig']->akismet_enabled = $this->akismet_enabled;
//$this->grav['twig']->recent_comments = $this->getRecentComments(); //cannot be used for functions with arguments
$function = new Twig_SimpleFunction('recent_comments', [$this, 'getRecentComments']);
$this->grav['twig']->twig()->addFunction($function);
if ($this->config->get('plugins.comments.built_in_css')) {
$this->grav['assets']->addCss('plugin://comments/assets/comments.css');
}
$this->grav['assets']->add('jquery', 101)->addJs('plugin://comments/assets/comments.js');
}
/**
* Determine if the plugin should be enabled based on the enable_on_routes and disable_on_routes config options
*/
private function calculateEnable() {
$uri = $this->grav['uri'];
$disable_on_routes = (array)$this->config->get('plugins.comments.disable_on_routes');
$enable_on_routes = (array)$this->config->get('plugins.comments.enable_on_routes');
$callback = $this->config->get('plugins.comments.ajax_callback');
$path = $uri->path();
if ($callback === $path) {
$this->enable = true;
return;
}
if (!in_array($path, $disable_on_routes)) {
if (in_array($path, $enable_on_routes)) {
$this->enable = true;
} else {
foreach ($enable_on_routes as $route) {
if (Utils::startsWith($path, $route)) {
$this->enable = true;
break;
}
}
}
}
$this->commenting_enabled = $this->grav['config']->get('plugins.comments.commenting');
$this->pingbacks_enabled = $this->grav['config']->get('plugins.comments.pingbacks');
$this->akismet_enabled = $this->grav['config']->get('plugins.comments.akismet');
}
/**
* Frontend side initialization
*/
private function initializeFrontend() {
$this->enable(['onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], ]);
if ($this->enable) {
$this->enable(['onPageInitialized' => ['onPageInitialized', 0], 'onFormProcessed' => ['onFormProcessed', 0], 'onFormPageHeaderProcessed' => ['onFormPageHeaderProcessed', 0], 'onTwigSiteVariables' => ['onTwigSiteVariables', 0]]);
}
$cache = $this->grav['cache'];
$uri = $this->grav['uri'];
//init cache id
$this->comments_cache_id = md5('comments-data' . $cache->getKey() . '-' . $uri->url());
$this->pingbacks_cache_id = md5('pingbacks-data' . $cache->getKey() . '-' . $uri->url());
}
/**
* Admin side initialization
*/
private function initializeAdmin() {
/** @var Uri $uri */
$uri = $this->grav['uri'];
$this->enable(['onTwigTemplatePaths' => ['onTwigAdminTemplatePaths', 0], 'onAdminMenu' => ['onAdminMenu', 0], 'onAdminTaskExecute' => ['onAdminTaskExecute', 0], 'onAdminAfterSave' => ['onAdminAfterSave', 0], 'onAdminAfterDelete' => ['onAdminAfterDelete', 0], 'onDataTypeExcludeFromDataManagerPluginHook' => ['onDataTypeExcludeFromDataManagerPluginHook', 0], ]);
if (strpos($uri->path(), $this->config->get('plugins.admin.route') . '/' . $this->route) === false) {
return;
}
$page = $this->grav['uri']->param('page');
$comments = $this->getLastComments($page);
if ($page > 0) {
echo json_encode($comments);
exit();
}
$this->grav['twig']->comments = $comments;
$this->grav['twig']->pages = $this->fetchPages();
}
/**
*/
public function onPluginsInitialized() {
$this->calculateEnable();
if ($this->isAdmin()) {
$this->initializeAdmin();
} else {
$this->initializeFrontend();
}
}
/**
* Handle ajax call.
*/
public function onPageInitialized() {
$is_ajax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
//$callback = $this->config->get('plugins.comments.ajax_callback');
// Process comment if required
if ($is_ajax) { // || $callback === $this->grav['uri']->path()
$action = filter_input(INPUT_POST, 'action', FILTER_SANITIZE_STRING);
switch ($action) {
case 'addComment':
case '':
case null:
// try to add the comment
$result = $this->addComment(true);
echo json_encode(['status' => $result[0], 'message' => $result[1], 'data' => $result[2], ]);
break;
case 'delete':
// try to delete the comment
$result = $this->deleteComment(true);
echo json_encode(['status' => $result[0], 'message' => $result[1], 'data' => $result[2], ]);
break;
default:
//request unknown, present error page
//Set a 400 (bad request) response code.
http_response_code(400);
echo 'request malformed - action unknown';
break;
}
exit(); //prevents the page frontend from beeing displayed.
}
}
/**
* Validate ajax input before deleting comment
*
* @return boolean[]|string[]|array[][]
*/
private function deleteComment() {
$language = $this->grav['language'];
if (!$this->grav['user']->authorize('admin.super')) {
http_response_code(403);
return [false, 'access forbidden', [0, 0]];
}
$id = filter_input(INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT);
$nonce = filter_input(INPUT_POST, 'nonce', FILTER_SANITIZE_STRING);
// ensure both values are sent
if (is_null($id) || is_null($nonce)) {
// Set a 400 (bad request) response code and exit.
http_response_code(400);
return [false, 'request malformed - missing parameter(s)', [0, 0]];
}
if (!Utils::verifyNonce($nonce, 'comments')) {
http_response_code(403);
return [false, 'Invalid security nonce', [0, $nonce]];
}
$lang = $this->grav['language']->getLanguage();
$path = $this->grav['page']->path();
$route = $this->grav['page']->route();
$data = $this->removeComment($route, $path, $id, $lang);
if ($data[0]) {
return [true, $language->translate('PLUGIN_COMMENTS.DELETE_SUCCESS'), $data[1]];
} else {
http_response_code(403); //forbidden
return [false, $language->translate('PLUGIN_COMMENTS.DELETE_FAIL'), $data[1]];
}
}
/**
* Validate ajax input before adding comment
*
* @return boolean[]|string[]|array[][]
*/
private function addComment($raiseErrors=true) {
if (!$this->active) {
return [false, 'Comment plugin is currently disabled.', [0, 0]];
}
if (!$this->commenting_enabled) {
return [false, 'Commenting is currently disabled.', [0, 0]];
}
$language = $this->grav['language'];
if (!$_SERVER["REQUEST_METHOD"] == "POST") {
// Not a POST request, set a 403 (forbidden) response code.
if( $raiseErrors ) {
http_response_code(403);
}
return [false, 'There was a problem with your submission, please try again.', [0, 0]];
}
// get and filter the data
if (!isset($_POST['data']) || !is_array($_POST['data'])) {
// Set a 400 (bad request) response code and exit.
if( $raiseErrors ) {
http_response_code(400);
}
return [false, 'missing data', [0, 0]];
}
$post = isset($_POST['data']) ? $_POST['data'] : [];
$parent = filter_input(INPUT_POST, 'parent', FILTER_SANITIZE_NUMBER_INT);
$name = isset($post['name']) ? filter_var($post['name'], FILTER_SANITIZE_STRING) : null;
$email = isset($post['email']) ? filter_var($post['email'], FILTER_SANITIZE_EMAIL) : null;
$text = isset($post['text']) ? filter_var($post['text'], FILTER_SANITIZE_STRING) : null;
$date = isset($post['date']) ? filter_var($post['date'], FILTER_SANITIZE_STRING) : null;
$site = isset($post['site']) ? filter_var($post['site'], FILTER_SANITIZE_URL) : null;
$lang = isset($post['lang']) ? filter_var($post['lang'], FILTER_SANITIZE_STRING) : null;
$path = isset($post['path']) ? filter_var($post['path'], FILTER_SANITIZE_STRING) : null;
$formname = filter_input(INPUT_POST, 'form-name', FILTER_SANITIZE_STRING);
$formnonce = filter_input(INPUT_POST, 'form-nonce', FILTER_SANITIZE_STRING);
if (!Utils::verifyNonce($formnonce, 'comments')) {
if( $raiseErrors ) {
http_response_code(403);
}
return [false, 'Invalid security nonce', [0, $formnonce]];
}
// ensure both values are sent
if (is_null($text)) {
// Set a 400 (bad request) response code and exit.
http_response_code(400);
return [false, 'missing text', [0, 0]];
//return [false, $language->translate('PLUGIN_COMMENTS.FAIL'), $data];
}
// sanity checks for parents
if ($parent < 0) {
$parent = 0;
} elseif ($parent > 999) { //TODO: Change to 'exists in list of comment ids
$parent = 0;
}
$ip = $_SERVER['REMOTE_ADDR'];
$lang = $this->grav['language']->getLanguage();
$path = $this->grav['page']->path();
$route = $this->grav['page']->route();
$user = $this->grav['user']->authenticated ? $this->grav['user']->username : '';
$isAdmin = $this->grav['user']->authorize('admin.login');
$comment = $this->saveComment($ip, $route, $path, $parent, $lang, $text, $name, $email, $site, $user, $isAdmin);
//$comments = $this->fetchComments();
$data = array('parent' => $comment['parent'], 'id' => $comment['id'], 'text' => $comment['text'], 'name' => $comment['author'], 'date' => $comment['date'], 'authenticated' => !empty($comment['user']), 'isAdmin' => !empty($comment['isAdmin']), 'ADD_REPLY' => $language->translate('PLUGIN_COMMENTS.ADD_REPLY'), 'REPLY' => $language->translate('PLUGIN_COMMENTS.REPLY'), 'WRITTEN_ON' => $language->translate('PLUGIN_COMMENTS.WRITTEN_ON'), 'BY' => $language->translate('PLUGIN_COMMENTS.BY'),);
return [true, $language->translate('PLUGIN_COMMENTS.SUCCESS'), $data];
}
/**
* Handle form processing instructions.
*
* @param Event $event
*/
private function removeComment($route, $path, $id, $lang) {
$entry_removed = false;
$message = '';
$date = time(); //date('D, d M Y H:i:s', time());
/******************************/
/** store comments with page **/
/******************************/
$localfilename = $path . '/comments.yaml';
$localfile = CompiledYamlFile::instance($localfilename);
if (file_exists($localfilename)) {
$data = $localfile->content();
if (isset($data['comments']) && is_array($data['comments'])) {
foreach ($data['comments'] as $key => $comment) {
if (!empty($comment['parent']) && $comment['parent'] == $id) {
//hit an existing comment that is a reply to comment selected for deletion.
//deletion of "parent" comment not allowed to preserve integrity of nested comments.
//TODO: Alternatively allow it to mark parent comments as deleted
// and make sure (via Comment class / setCommentLevels) that children are
// filtered out from fetch regardless of their own deletion state.
$data['comments'][$key] = array_merge(array('deleted' => ''), $comment);
//set date after merge
//reason: could be possible that "deleted" already exists (e.g. false or '') in $comment which would overwrite the first (newly added) occurence
$data['comments'][$key]['deleted'] = $date;
//no need to look further as ids are supposed to be unique.
$localfile->save($data);
$entry_removed = false;
$reply_id = empty($comment['id']) ? '' : $comment['id'];
$message = "Found active reply ($reply_id) for selected comment ($id).";
return [$entry_removed, $message];
break;
}
}
foreach ($data['comments'] as $key => $comment) {
if (!empty($comment['id']) && $comment['id'] == $id) {
//add deleted as first item in array (better readability in file)
$data['comments'][$key] = array_merge(array('deleted' => ''), $comment);
//set date after merge
//reason: could be possible that "deleted" already exists (e.g. false or '') in $comment which would overwrite the first (newly added) occurence
$data['comments'][$key]['deleted'] = $date;
//no need to look further as ids are supposed to be unique.
$localfile->save($data);
$entry_removed = true;
$message = "Deleted comment ($id) via path ($path)";
break;
}
}
}
} else {
//nothing
}
/**********************************/
/** store comments in index file **/
/**********************************/
$indexfilename = DATA_DIR . 'comments/index.yaml';
$indexfile = CompiledYamlFile::instance($indexfilename);
if (file_exists($indexfilename)) {
$dataIndex = $indexfile->content();
if (isset($dataIndex['comments']) && is_array($dataIndex['comments'])) {
foreach ($dataIndex['comments'] as $key => $comment) {
if (!empty($comment['page']) && !empty($comment['id']) && $comment['page'] == $route && $comment['id'] == $id) {
//add deleted as first item in array (better readability in file)
$dataIndex['comments'][$key] = array_merge(array('deleted' => ''), $comment);
//set date after merge
//reason: could be possible that "deleted" already exists (e.g. false or '') in $comment which would overwrite the first (newly added) occurence
$dataIndex['comments'][$key]['deleted'] = $date;
//no need to look further as ids are supposed to be unique.
$indexfile->save($dataIndex);
break;
}
}
}
} else {
//nothing
}
//clear cache
$this->grav['cache']->delete($this->comments_cache_id);
$this->grav['cache']->delete($this->pingbacks_cache_id);
return [$entry_removed, $message];
}
private function validateComment($name, $email, $text, $site) {
if ($this->akismet_enabled) {
$key = $this->grav['config']->get('plugins.comments.akismet_key_api');
$url_override = $this->grav['config']->get('plugins.comments.akismet_site');
$url = !empty($url_override) ? $url_override : $_SERVER['HTTP_HOST'];
$akismet = new \Akismet($url, $key);
$akismet->setCommentAuthor($name);
$akismet->setCommentAuthorEmail($email);
$akismet->setCommentAuthorURL($site);
$akismet->setCommentContent($text);
//$akismet->setPermalink($comment->post->permalink);
try {
$approved = !$akismet->isCommentSpam() ? 'true' : 'false';
}
catch(Exception $e) {
//EventLog::log($e->getMessage(), 'notice', 'comment', 'HabariAkismet');
// TODO: admin needs to approve comment
$approved = 'pending';
}
return $approved;
}
return 'true';
}
/**
* Handle form processing instructions.
*
* @param Event $event
*/
private function saveComment($ip, $route, $path, $parent, $lang, $text, $name, $email, $site = "", $user = "", $isAdmin = false) {
$date = date('D, d M Y H:i:s', time());
$approved = $this->validateComment($name, $email, $text, $site);
/******************************/
/** store comments with page **/
/******************************/
$localfilename = $path . '/comments.yaml';
$localfile = CompiledYamlFile::instance($localfilename);
if (file_exists($localfilename)) {
$data = $localfile->content();
$data['autoincrement']++;
} else {
$data = array('autoincrement' => 1, 'comments' => array());
}
$localid = $data['autoincrement'];
$newComment = ['id' => $data['autoincrement'], 'ip' => $ip, 'parent' => $parent, 'lang' => $lang, 'text' => $text, 'date' => $date, 'author' => $name, 'email' => $email, 'site' => $site, 'user' => $user, 'approved' => $approved, 'isAdmin' => !empty($isAdmin), ];
$data['comments'][] = $newComment;
$localfile->save($data);
/**********************************/
/** store comments in index file **/
/**********************************/
$indexfilename = DATA_DIR . 'comments/index.yaml';
$indexfile = CompiledYamlFile::instance($indexfilename);
if (file_exists($indexfilename)) {
$data = $indexfile->content();
} else {
$data = array('comments' => array());
}
$data['comments'][] = ['page' => $route, 'id' => $localid, 'parent' => $parent, 'lang' => $lang, 'text' => $text, 'date' => $date, 'author' => $name, 'email' => $email, 'site' => $site, 'approved' => $approved, ];
$indexfile->save($data);
//clear cache, don't let incoming spam thrash the cache.
if ($approved == 'true') {
$this->grav['cache']->delete($this->comments_cache_id);
$this->grav['cache']->delete($this->pingbacks_cache_id);
}
return $newComment;
}
/**
* Handle form processing instructions.
*
* @param Event $event
*/
public function onFormProcessed(Event $event) {
$form = $event['form'];
$action = $event['action'];
$params = $event['params'];
switch ($action) {
case 'addComment':
addComment(false);
break;
}
}
/**
* Used to add a recent comments widget. Call {{ recent_comments(123,12) }} specifying an integer representing the result length.
*
* Returns three different arrays with stats and comments.
*
* @param integer $limit max amount of comments in result set
* @param integer $limit_pages max amount of pages in result set
*
* @return array|array|array global stats, page stats, list of recent comments, options
*/
public function getRecentComments($limit, $limit_pages) {
$routes = $this->grav['pages']->routes(); //routes[route] => path
$paths = array_flip($routes);
$cache = $this->grav['cache'];
$options = array('comments_limit' => $limit, 'pages_limit' => $limit_pages,);
//use cached stats if possible
$recent_comments_cache_id = md5('comments-stats' . $cache->getKey());
if ($recent_comments = $cache->fetch($recent_comments_cache_id)) {
//use cache only if limits are big enough
if ($recent_comments['options']['comments_limit'] >= $options['comments_limit'] && $recent_comments['options']['pages_limit'] >= $options['pages_limit']) {
return $recent_comments;
}
}
$path = PAGES_DIR;
$dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
$itrFilter = new \RecursiveIteratorIterator($dirItr, \RecursiveIteratorIterator::SELF_FIRST);
$filesItr = new \RegexIterator($itrFilter, '/^.+comments\.yaml$/i');
$files = array();
$global_stats = array('active_entries' => 0, 'deleted_entries' => 0, 'active_comments' => 0, 'deleted_comments' => 0, 'active_replies' => 0, 'deleted_replies' => 0, 'pages_with_active_entries' => 0,);
$page_stats = array();
$comments = array();
foreach ($filesItr as $filepath => $file) {
if ($file->isDir()) {
// this should never trigger as we are looking vor yamls only
} else {
$route = '';
$fileFolder = substr($filepath, 0, strlen($filepath) - strlen($file->getFilename()) - 1);
if (!empty($paths[str_replace('/', '\\', $fileFolder) ])) $route = $paths[str_replace('/', '\\', $fileFolder) ];
if (!empty($paths[str_replace('\\', '/', $fileFolder) ])) $route = $paths[str_replace('\\', '/', $fileFolder) ];
$page_stats[$filepath] = array('active_entries' => 0, 'deleted_entries' => 0, 'active_comments' => 0, 'deleted_comments' => 0, 'active_replies' => 0, 'deleted_replies' => 0, 'latest_active_entry' => 0, 'route' => $route,);
$localfile = CompiledYamlFile::instance($filepath);
$localcomments = $localfile->content();
if (!empty($localcomments['comments']) && is_array($localcomments['comments'])) {
foreach ($localcomments['comments'] as $comment) {
if (!empty($comment['deleted'])) {
empty($comment['parent']) ? $page_stats[$filepath]['deleted_comments']++ : $page_stats[$filepath]['deleted_replies']++;
empty($comment['parent']) ? $global_stats['deleted_comments']++ : $global_stats['deleted_replies']++;
$page_stats[$filepath]['deleted_entries']++;
$global_stats['deleted_entries']++;
} else {
empty($comment['parent']) ? $page_stats[$filepath]['active_comments']++ : $page_stats[$filepath]['active_replies']++;
empty($comment['parent']) ? $global_stats['active_comments']++ : $global_stats['active_replies']++;
$page_stats[$filepath]['active_entries']++;
$global_stats['active_entries']++;
//use unix timestamp for comparing and sorting
if (is_int($comment['date'])) {
$time = $comment['date'];
} else {
$time = \DateTime::createFromFormat('D, d M Y H:i:s', $comment['date'])->getTimestamp();
}
if (empty($page_stats[$filepath]['latest_active_entry']) || $page_stats[$filepath]['latest_active_entry'] < $time) {
$page_stats[$filepath]['latest_active_entry'] = $time;
}
$comments[] = array_merge(array('path' => $filepath, 'route' => $route, 'time' => $time,), $comment);
}
}
}
if (!empty($page_stats[$filepath]['latest_active_entry'])) {
$global_stats['pages_with_active_entries']++;
}
}
}
//most recent comments first
usort($comments, function ($a, $b) {
if ($a['time'] === $b['time']) return 0;
if ($a['time'] < $b['time']) return 1;
return -1;
});
//most recent pages first
usort($page_stats, function ($a, $b) {
if ($a['latest_active_entry'] === $b['latest_active_entry']) return 0;
if ($a['latest_active_entry'] < $b['latest_active_entry']) return 1;
return -1;
});
//reduce comments in output to limit
if (!empty($limit) && $limit > 0 && $limit < count($comments)) {
$comments = array_slice($comments, 0, $limit);
}
//reduce pages in output to limit
if (!empty($limit_pages) && $limit_pages > 0 && $limit_pages < count($page_stats)) {
$page_stats = array_slice($page_stats, 0, $limit_pages);
}
//save to cache if enabled
$cache->save($recent_comments_cache_id, ['global_stats' => $global_stats, 'pages' => $page_stats, 'comments' => $comments, 'options' => $options]);
return ['global_stats' => $global_stats, 'pages' => $page_stats, 'comments' => $comments, 'options' => $options];
}
private function getFilesOrderedByModifiedDate($path = '') {
$files = [];
if (!$path) {
$path = DATA_DIR . 'comments';
}
if (!file_exists($path)) {
Folder::mkdir($path);
}
$dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
$filterItr = new RecursiveFolderFilterIterator($dirItr);
$itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
$itrItr = new \RecursiveIteratorIterator($dirItr, \RecursiveIteratorIterator::SELF_FIRST);
$filesItr = new \RegexIterator($itrItr, '/^.+\.yaml$/i');
// Collect files if modified in the last 7 days
foreach ($filesItr as $filepath => $file) {
$modifiedDate = $file->getMTime();
$sevenDaysAgo = time() - (7 * 24 * 60 * 60);
if ($modifiedDate < $sevenDaysAgo) {
continue;
}
$files[] = (object)array("modifiedDate" => $modifiedDate, "fileName" => $file->getFilename(), "filePath" => $filepath, "data" => Yaml::parse(file_get_contents($filepath)));
}
// Traverse folders and recurse
foreach ($itr as $file) {
if ($file->isDir()) {
$this->getFilesOrderedByModifiedDate($file->getPath() . '/' . $file->getFilename());
}
}
// Order files by last modified date
usort($files, function ($a, $b) {
return !($a->modifiedDate > $b->modifiedDate);
});
return $files;
}
private function getLastComments($page = 0) {
$number = 30;
$files = [];
$files = $this->getFilesOrderedByModifiedDate();
$comments = [];
foreach ($files as $file) {
$data = Yaml::parse(file_get_contents($file->filePath));
for ($i = 0;$i < count($data['comments']);$i++) {
$commentTimestamp = \DateTime::createFromFormat('D, d M Y H:i:s', $data['comments'][$i]['date'])->getTimestamp();
$data['comments'][$i]['pageTitle'] = $data['title'];
$data['comments'][$i]['filePath'] = $file->filePath;
$data['comments'][$i]['timestamp'] = $commentTimestamp;
}
if (count($data['comments'])) {
$comments = array_merge($comments, $data['comments']);
}
}
// Order comments by date
usort($comments, function ($a, $b) {
return !($a['timestamp'] > $b['timestamp']);
});
$totalAvailable = count($comments);
$comments = array_slice($comments, $page * $number, $number);
$totalRetrieved = count($comments);
return (object)array("comments" => $comments, "page" => $page, "totalAvailable" => $totalAvailable, "totalRetrieved" => $totalRetrieved);
}
/**
* Return the comments associated to the current route
*/
public function fetchComments() {
$cache = $this->grav['cache'];
//search in cache
if ($comments = $cache->fetch($this->comments_cache_id)) {
return $comments;
}
$lang = $this->grav['language']->getLanguage();
$filename = $lang ? '/' . $lang : '';
$filename.= $this->grav['uri']->path() . '.yaml';
$comments = $this->getDataFromFilename($filename) ['comments'];
$comments = $this->setCommentLevels($comments);
//save to cache if enabled
$cache->save($this->comments_cache_id, $comments);
return $comments;
}
/**
* Return the latest commented pages
*/
private function setCommentLevels($comments) {
if (!is_array($comments)) {
return $comments;
}
$levelsflat = array();
foreach ($comments as $key => $comment) {
if (!empty($comment['deleted'])) {
//if field "deleted" exists and is filled with a true value then ignore the comment completely.
//TODO: This only works on this position as long as it is forbidden to delete comments that have active replies (children).
// Otherwise implement that children get the deleted flag recursively or are ignored via Comment class.
} else {
$levelsflat[$comment['id']]['parent'] = $comment['parent'];
$levelsflat[$comment['id']]['class'] = new Comment($comment['id'], $comments[$key]);
}
}
//get starting points (entries without valid parent = root element)
$leveltree = array();
foreach ($levelsflat as $id => $parent) {
$parent = $parent['parent'];
if (!isset($levelsflat[$parent])) {
$leveltree[$id] = $levelsflat[$id]['class'];
} else {
$currentParent = $levelsflat[$parent]['class'];
$currentChild = $levelsflat[$id]['class'];
$levelsflat[$id]['class']->setParent($currentParent);
$levelsflat[$parent]['class']->addSubComment($currentChild);
}
}
//youngest comments first (DESC date), only root comments. Keep replies in ASC date order.
//as long as comments are not editable, it is sufficient to reverse order from comment file
$leveltree = array_reverse($leveltree, true);
//reset comment values to nested order
$comments = array();
foreach ($leveltree as $id => $comment) {
$comments = array_merge($comments, $comment->getContent());
}
return $comments;
}
/**
* Return the pingbacks associated to the current route
*/
private function fetchPingbacks() {
$cache = $this->grav['cache'];
//search in cache
if ($pingbacks = $cache->fetch($this->pingbacks_cache_id)) {
return $pingbacks;
}
$lang = $this->grav['language']->getLanguage();
$filename = $lang ? '/' . $lang : '';
$filename.= $this->grav['uri']->path() . '.yaml';
$pingbacks = $this->getDataFromFilenameOld($filename) ['pingbacks'];
//save to cache if enabled
$cache->save($this->pingbacks_cache_id, $pingbacks);
return $pingbacks;
}
/**
* Return the latest commented pages
*/
private function fetchPages() {
$files = [];
$files = $this->getFilesOrderedByModifiedDate();
$pages = [];
foreach ($files as $file) {
$pages[] = ['title' => $file->data['title'], 'commentsCount' => count($file->data['comments']), 'lastCommentDate' => date('D, d M Y H:i:s', $file->modifiedDate) ];
}
return $pages;
}
/**
* Given a data file route, return the YAML content already parsed
*/
private function getDataFromFilename($fileRoute) {
//Single item details
//$fileInstance = CompiledYamlFile::instance(DATA_DIR . 'comments/' . $fileRoute);
//Use comment file in page folder
$fileInstance = CompiledYamlFile::instance($this->grav['page']->path() . '/comments.yaml');
if (!$fileInstance->content()) {
//Item not found
return;
}
return $fileInstance->content();
}
private function getDataFromFilenameOld($fileRoute) {
//Single item details
$fileInstance = File::instance(DATA_DIR . 'comments/' . $fileRoute);
if (!$fileInstance->content()) {
//Item not found
return;
}
return Yaml::parse($fileInstance->content());
}
/**
* Add templates directory to twig lookup paths.
*/
public function onTwigTemplatePaths() {
$this->grav['twig']->twig_paths[] = __DIR__ . '/templates';
}
/**
* Add plugin templates path
*/
public function onTwigAdminTemplatePaths() {
$this->grav['twig']->twig_paths[] = __DIR__ . '/admin/templates';
$this->grav['twig']->akismet_enabled = $this->akismet_enabled;
if ($this->akismet_enabled) {
$key = $this->grav['config']->get('plugins.comments.akismet_key_api');
$url_override = $this->grav['config']->get('plugins.comments.akismet_site');
$url = !empty($url_override) ? $url_override : $_SERVER['HTTP_HOST'];
$akismet = new \Akismet($url, $key);
if ($akismet->isKeyValid()) {
$this->grav['twig']->warning_message = "";
} else {
$this->grav['twig']->warning_message = sprintf("Akismet API key \"%s\" is invalid for the url \"%s\". Provide a correct url override or make sure you're registered. Please check to make sure the key is entered correctly.", $key, $url);
}
}
}
/**
* Handle the Reindex task from the admin
*
* @param Event $e
*/
public function onAdminTaskExecute(Event $e) {
if ($e['method'] == 'taskReindexComments') {
$controller = $e['controller'];
header('Content-type: application/json');
if (!$controller->authorizeTask('reindexComments', ['admin.configuration', 'admin.super'])) {
$json_response = ['status' => 'error', 'message' => ' Index not created', 'details' => 'Insufficient permissions to reindex the comments index file.'];
echo json_encode($json_response);
exit;
}
/*TODO // disable warnings
error_reporting(1);
// capture content
ob_start();
$this->gtnt->createIndex();
ob_get_clean();
list($status, $msg) = $this->getIndexCount();
$json_response = [
'status' => $status ? 'success' : 'error',
'message' => ' ' . $msg
];
echo json_encode($json_response);
*/
exit;
}
}
/**
* Perform an 'add' or 'update' for comment data as needed
*
* @param $event
* @return bool
*/
public function onAdminAfterSave($event) {
$obj = $event['object'];
if ($obj instanceof Page) {
//nothing to do
//save means, the page changed, but still exists
}
return true;
}
/**
* Perform an 'add' or 'update' for comment data as needed
*
* @param $event
* @return bool
*/
public function onAdminAfterDelete($event) {
$obj = $event['object'];
if ($obj instanceof Page) {
//TODO $this->deleteComment($obj);
//clear cache
$this->grav['cache']->delete(md5('comments-stats' . $this->grav['cache']->getKey()));
}
return true;
}
/**
* Add navigation item to the admin plugin
*/
public function onAdminMenu() {
$this->grav['twig']->plugins_hooked_nav['PLUGIN_COMMENTS.COMMENTS'] = ['route' => $this->route, 'icon' => 'fa-file-text'];
$options = ['authorize' => 'taskReindexComments', 'hint' => 'reindexes the comments index', 'class' => 'comments-reindex', 'icon' => 'fa-file-text'];
$this->grav['twig']->plugins_quick_tray['PLUGIN_COMMENTS.COMMENTS'] = $options;
}
/**
* Exclude comments from the Data Manager plugin
*/
public function onDataTypeExcludeFromDataManagerPluginHook() {
$this->grav['admin']->dataTypesExcludedFromDataManagerPlugin[] = 'comments';
}
}