Compare commits
31 Commits
Author | SHA1 | Date |
---|---|---|
leetNightshade | 43ae3a78a6 | |
leetNightshade | 4698549b88 | |
leetNightshade | be0eca0ff5 | |
leetNightshade | 94134e2575 | |
Ernst | 43a70fe64a | |
Ernst | 3632251cea | |
Brian | b56c5927f7 | |
Brian | f6a4204ed2 | |
Ernst | 9bff3a3397 | |
Ernst | 6a6b1973a1 | |
Brian | 19d5ef9f1f | |
Ernst | 607e0fa6d4 | |
Ernst | a52c1b2f6d | |
codeshell | ee89c2bdca | |
codeshell | bd1799cc7e | |
codeshell | 9179453c85 | |
Thorsten Witteler | 905c04937c | |
Thorsten Witteler | b4ab9a5111 | |
Thorsten Witteler | 0936de7c1d | |
Thorsten Witteler | d70f109776 | |
Thorsten Witteler | cace16f606 | |
Thorsten Witteler | 868d7b1231 | |
Thorsten Witteler | de3a683cbe | |
Thorsten Witteler | 0c00764ef5 | |
Thorsten Witteler | 7d153816f9 | |
Thorsten Witteler | 6d458f8fa4 | |
Thorsten Witteler | 018a42a3dc | |
Thorsten Witteler | 1bc97a31e7 | |
Thorsten Witteler | 9387fba6ed | |
Thorsten Witteler | e8417c6a0e | |
Flavio Copes | 563d7af096 |
16
README.md
16
README.md
|
@ -1,4 +1,18 @@
|
|||
# Grav Comments Plugin
|
||||
# Grav Comments Plugin \[Fork\]
|
||||
|
||||
This plugin adds support for displaying pingbacks, toggling the display of pingbacks. I'm not yet sure how pingbacks work, so this may not include support for adding new pingbacks though I attempted to patch the php file so it could work in theory; if it won't take too much work I'll add more support for them. Also added support for user entered URLs, though I haven't gotten the form input to show up yet.
|
||||
|
||||
This fork adds support for Akismet, which is now basically working. Consider it alpha. Also has admin panel functionality, can toggle it, add api key, shows error on comment page if api key doesn't match site. Going to add amin panel checkbox for recaptcha.
|
||||
|
||||
Preview with commenting disabled but comment plugin still enabled:
|
||||
|
||||
![](http://git.leetnightshade.com/leetNightshade/git.leetnightshade.com/raw/branch/master/grav-plugin-comments/Screenshots/blog.default-theme.png)
|
||||
|
||||
Preview with nested commenting:
|
||||
|
||||
![](http://git.leetnightshade.com/leetNightshade/git.leetnightshade.com/raw/branch/master/grav-plugin-comments/Screenshots/nested.png)
|
||||
|
||||
---
|
||||
|
||||
The **Comments Plugin** for [Grav](http://github.com/getgrav/grav) adds the ability to add comments to pages, and moderate them.
|
||||
|
||||
|
|
|
@ -75,6 +75,13 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
{% if grav.twig.warning_message|length %}
|
||||
<div class="admin-block">
|
||||
<h1>{{ "PLUGIN_COMMENTS.WARNINGS"|tu }}</h1>
|
||||
<p class="center">{{ grav.twig.warning_message }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h1>Comments in the last 7 days</h1>
|
||||
|
||||
<div class="admin-block">
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
===============================================================================================================================
|
||||
Comments Plugin Styles
|
||||
===============================================================================================================================
|
||||
*/
|
||||
|
||||
.comment {
|
||||
margin-top: 0px;
|
||||
width:100%;
|
||||
}
|
||||
.comment:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
.comment,
|
||||
.comment-body {
|
||||
zoom: 1;
|
||||
}
|
||||
.comment-body {
|
||||
overflow: hidden;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.comment-object {
|
||||
display: block;
|
||||
}
|
||||
.comment-right,
|
||||
.comment > .pull-right {
|
||||
padding-left: 10px;
|
||||
}
|
||||
.comment-left,
|
||||
.comment > .pull-left {
|
||||
padding-right: 10px;
|
||||
}
|
||||
.comment-middle {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.comment-bottom {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.comment-heading {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.comment-meta {
|
||||
font-size: small;
|
||||
}
|
||||
.comment-text {
|
||||
clear: both;
|
||||
}
|
||||
.comment-list {
|
||||
padding-left: 0px;
|
||||
list-style: none;
|
||||
}
|
||||
.comment-flag-new {
|
||||
background-color: lightcyan;
|
||||
}
|
||||
.comment-wrapper {
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
}
|
||||
.comment-avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-left: -10px;
|
||||
margin-right:5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
/*.comment-thread-line:hover {
|
||||
border-left:3px solid #1BB3E9;
|
||||
}*/
|
||||
.comment-thread-top {
|
||||
margin-top:20px;
|
||||
}
|
||||
.comment-thread {
|
||||
float: left;
|
||||
border-left:3px solid #444;
|
||||
margin-left:10px;
|
||||
}
|
||||
.comment-footer {
|
||||
font-size: small;
|
||||
}
|
||||
.comment-reply {
|
||||
display: inline;
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
function escapeRegExp(str) {
|
||||
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
|
||||
}
|
||||
jQuery(document).ready(function() {
|
||||
var commentForm = $('#comments-form'); //$(document).find('.comments-form').first();
|
||||
var commentSection = $('#comments-section'); //$(document).find('.comments').first();
|
||||
var commentAlert = $('#comments-alert'); //$(document).find('.alert').first();
|
||||
|
||||
//hide form, show link
|
||||
commentForm.hide();
|
||||
$(document).find('.comment-add-new').show();
|
||||
|
||||
//show comment form above comments section (new comment thread)
|
||||
$('body').on('click', '.comment-add-new', function(e) {
|
||||
e.preventDefault();
|
||||
if ($(this).prev().filter('#comments-form').length > 0) {
|
||||
//form is already in the right place.
|
||||
//just make sure it is visible.
|
||||
commentForm.show();
|
||||
return;
|
||||
}
|
||||
commentForm.hide(); //hide it to make sure that it is not shown after move to make "show" transition work.
|
||||
$(this).before(commentForm);
|
||||
commentForm.show('slow');
|
||||
commentAlert.empty().slideUp();
|
||||
});
|
||||
|
||||
//show comment form below selected comment (reply to existing comment)
|
||||
$('body').on('click', '.comment-add-reply', function(e) {
|
||||
e.preventDefault();
|
||||
var comment = $(this).closest('.comment');
|
||||
if (comment.find('#comments-form').length > 0) {
|
||||
//form is already in the right place.
|
||||
//just make sure it is visible.
|
||||
commentForm.show();
|
||||
return;
|
||||
}
|
||||
commentForm.hide();
|
||||
comment.find('.comment-body').last().append(commentForm);
|
||||
commentForm.show('slow');
|
||||
commentAlert.empty().slideUp();
|
||||
});
|
||||
|
||||
//delete comment (authorized user only)
|
||||
$('body').on('click', '.comment-delete', function(e) {
|
||||
e.preventDefault();
|
||||
var comment = $(this).closest('.comment');
|
||||
var id = parseInt(comment.attr('data-id'), 10);
|
||||
var level = parseInt(comment.attr('data-level'), 10);
|
||||
var nonce = commentForm.find("input[name='form-nonce']").val();
|
||||
if (comment.next().filter(".comment[data-level='" + (level + 1) + "']").length > 0) {
|
||||
alert('Deletion not allowed. There are replies to this comment. Please delete them first.');
|
||||
return;
|
||||
}
|
||||
var url = commentForm.attr("action");
|
||||
var posting = $.post(url, {
|
||||
action: 'delete',
|
||||
id: id,
|
||||
nonce: nonce
|
||||
}, null, 'json');
|
||||
// Register events to ajax call
|
||||
posting.done(function(response) {
|
||||
|
||||
//make sure that commentForm is definitely not within the deleted DOM part.
|
||||
//hide
|
||||
//temporary move it outside the comment selected for deletion. (this definitely exists, not taking any chances here)
|
||||
//finally move back to start of commentSection. (preferred target)
|
||||
//Hint: Don't forget commentAlert as it is not inside the form.
|
||||
commentAlert.empty().hide();
|
||||
commentForm.hide();
|
||||
comment.before(commentForm);
|
||||
comment.before(commentAlert);
|
||||
commentSection.prepend(commentAlert);
|
||||
commentSection.prepend(commentForm);
|
||||
//remove the comment and all content from DOM.
|
||||
//detach would be a soft delete but as there is no reason to reuse the deleted comment, means should not be provided.
|
||||
comment.remove();
|
||||
});
|
||||
posting.fail(function(status, error, title) {
|
||||
//alert('error');
|
||||
//console.log("Response Data (fail)", JSON.parse(JSON.stringify(status)));
|
||||
commentForm.after(commentAlert);
|
||||
commentAlert.show();
|
||||
commentAlert.empty().append("<p>Error: </p>");
|
||||
commentAlert.append("<p>" + JSON.stringify(status) + "</p>");
|
||||
commentAlert.append("<p>" + JSON.stringify(error) + "</p>");
|
||||
commentAlert.append("<p>" + JSON.stringify(title) + "</p>");
|
||||
});
|
||||
posting.always(function() {
|
||||
//alert("finished, be it successful or not");
|
||||
});
|
||||
});
|
||||
|
||||
// Attach a submit handler to the form
|
||||
$(commentForm).on('submit', function(event) {
|
||||
event.preventDefault();
|
||||
// Get form data:
|
||||
var data = $(this).serialize();
|
||||
//console.log("Form Data (submit)", JSON.parse(JSON.stringify(data)));
|
||||
var url = $(this).attr("action");
|
||||
//var url = '/nested-comments';
|
||||
var parent = 0;
|
||||
var ownLevel = 0;
|
||||
if ($(this).parents('.comment').length > 0) {
|
||||
parent = $(this).closest('.comment').attr('data-id');
|
||||
ownLevel = parseInt($(this).closest('.comment').attr('data-level'), 10) + 1;
|
||||
}
|
||||
|
||||
// Send the data using post
|
||||
//var posting = $.post(url, { parent: parent, data: data }, null, 'json');
|
||||
var posting = $.post(url, data + '&parent=' + parent, null, 'json');
|
||||
|
||||
// Register events to ajax call
|
||||
posting.done(function(response) {
|
||||
//alert('success');
|
||||
//console.log("Response Data (done)", JSON.parse(JSON.stringify(response)));
|
||||
//response = JSON.parse(response); //not needed, post was done using json
|
||||
commentForm.after(commentAlert);
|
||||
if (!response.status) {
|
||||
//should not trigger at all, if all bad requests return the right http status code
|
||||
//i.e. <> 200 success => thus triggering posting.fail()
|
||||
//leave this check just in case
|
||||
commentAlert.stop().css('opacity', 1).text('Error: ' + response.message).fadeIn(30).fadeOut(5000);
|
||||
return;
|
||||
}
|
||||
if (response.status) {
|
||||
commentAlert.css('color', 'green').empty().append(document.createTextNode(response.message)).fadeIn(30);
|
||||
/*var newMedia = "<div class='comment comment-level-{{comment.level|e}} comment-flag-new' data-level='{{comment.level}}' data-id='{{comment.id}}' >" +
|
||||
"<div class='comment-left'>" +
|
||||
"<img class='comment-object' src='https://www.gravatar.com/avatar/{{comment.email|trim|lower|md5}}?d=identicon' alt='user icon'>" +
|
||||
"</div>" +
|
||||
"<div class='comment-body'>" +
|
||||
"<div class='comment-heading'>" +
|
||||
"<div class='comment-title'><h4>{{comment.title}}</h4></div>" +
|
||||
"<div class='comment-reply'><a class='comment-add-reply' href='#'><i class='fa fa-reply' title='{{'PLUGIN_COMMENTS.ADD_REPLY'|t}}'></i> {{'PLUGIN_COMMENTS.REPLY'|t}}</a></div>" +
|
||||
"<div class='comment-meta'>{{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}}</div>" +
|
||||
"</div>" +
|
||||
"<div class='comment-text' >" +
|
||||
"{{comment.text}}" +
|
||||
"</div>" +
|
||||
"{{nested}}" +
|
||||
"</div>" +
|
||||
"</div>";*/
|
||||
var newMedia = `<div id="comment-{{comment.id}}" class="comment comment-level-{{comment.level|e}}" data-id="{{comment.id}}" itemtype="http://schema.org/UserComments">
|
||||
<header class="comment-heading">
|
||||
<img class="comment-avatar" src="https://www.gravatar.com/avatar/{{comment.email|trim|lower|md5}}?size=20&d=identicon" alt="user icon">
|
||||
<span class="comment-meta">
|
||||
{% if comment.site %}
|
||||
<a href="{{comment.site}}">{{comment.author}}</a>
|
||||
{% else %}
|
||||
{{comment.author}}
|
||||
{% endif %}
|
||||
<a href="{{uri.url(true)}}#comment-{{comment.id}}" title="Link to this comment" itemprop="url">
|
||||
<time class="comment-date" datetime="{{comment.date|e}}" itemprop="commentTime">
|
||||
{{comment.date|nicetime(false)}}
|
||||
</time>
|
||||
</a>
|
||||
</span>
|
||||
</header>
|
||||
<div class="comment-body">
|
||||
<div class="comment-text" >
|
||||
{{comment.text}}
|
||||
</div>
|
||||
{{nested}}
|
||||
<div class="comment-footer">
|
||||
<span class="comment-reply">
|
||||
{% if grav.twig.commenting_enabled %}
|
||||
<a class="comment-add-reply" href="#"><i class="fa fa-reply" title="{{'PLUGIN_COMMENTS.ADD_REPLY'|t}}"></i> {{'PLUGIN_COMMENTS.REPLY'|t}}</a>
|
||||
{% endif %}
|
||||
{% if grav.user.access.admin.super %}
|
||||
<a class="comment-delete" href="#"><i class="fa fa-trash" title="{{'PLUGIN_COMMENTS.DELETE_COMMENT'|t}}"></i> {{'PLUGIN_COMMENTS.DELETE'|t}}</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.id}}"), 'g'), response.data.id);
|
||||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.level|e}}"), 'g'), ownLevel);
|
||||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.level}}"), 'g'), ownLevel);
|
||||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.email|trim|lower|md5}}"), 'g'), response.data.hash);
|
||||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{parent}}"), 'g'), response.data.parent);
|
||||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.title}}"), 'g'), response.data.title);
|
||||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.text}}"), 'g'), response.data.text);
|
||||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.author}}"), 'g'), response.data.name);
|
||||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.site}}"), 'g'), response.data.site);
|
||||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.date|e}}"), 'g'), response.data.date);
|
||||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{nested}}"), 'g'), '');
|
||||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{'PLUGIN_COMMENTS.ADD_REPLY'|t}}"), 'g'), response.data.ADD_REPLY);
|
||||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{'PLUGIN_COMMENTS.REPLY'|t}}"), 'g'), response.data.REPLY);
|
||||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{'PLUGIN_COMMENTS.WRITTEN_ON'|t}}"), 'g'), response.data.WRITTEN_ON);
|
||||
if ($("div[data-id='" + response.data.parent + "']").length > 0) {
|
||||
$("div[data-id='" + response.data.parent + "']").first().after(newMedia);
|
||||
} else {
|
||||
$("#comments").prepend(newMedia);
|
||||
}
|
||||
}
|
||||
setTimeout(function() {
|
||||
commentForm.slideUp();
|
||||
commentAlert.fadeOut(5000);
|
||||
}, 5000);
|
||||
});
|
||||
posting.fail(function(status, error, title) {
|
||||
//alert('error');
|
||||
//console.log("Response Data (fail)", JSON.parse(JSON.stringify(status)));
|
||||
commentForm.after(commentAlert);
|
||||
commentAlert.show();
|
||||
commentAlert.empty().append("<p>Error: </p>");
|
||||
commentAlert.append("<p>" + JSON.stringify(status) + "</p>");
|
||||
commentAlert.append("<p>" + JSON.stringify(error) + "</p>");
|
||||
commentAlert.append("<p>" + JSON.stringify(title) + "</p>");
|
||||
});
|
||||
posting.always(function() {
|
||||
//alert("finished, be it successful or not");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,5 +1,5 @@
|
|||
name: Comments
|
||||
version: 1.2.7
|
||||
version: 1.2.8
|
||||
description: Adds a commenting functionality to your site
|
||||
icon: comment
|
||||
author:
|
||||
|
@ -29,3 +29,55 @@ form:
|
|||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
commenting:
|
||||
type: toggle
|
||||
label: PLUGIN_COMMENTS.COMMENTS
|
||||
highlight: 1
|
||||
default: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
nested:
|
||||
type: toggle
|
||||
label: PLUGIN_COMMENTS.COMMENTS_NESTED
|
||||
highlight: 1
|
||||
default: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
pingbacks:
|
||||
type: toggle
|
||||
label: PLUGIN_COMMENTS.COMMENTS_PINGBACKS
|
||||
highlight: 1
|
||||
default: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
akismet:
|
||||
type: toggle
|
||||
label: PLUGIN_COMMENTS.AKISMET
|
||||
highlight: 1
|
||||
default: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.ENABLED
|
||||
0: PLUGIN_ADMIN.DISABLED
|
||||
validate:
|
||||
type: bool
|
||||
akismet_key_api:
|
||||
type: text
|
||||
label: PLUGIN_COMMENTS.AKISMET_KEY_API
|
||||
highlight: 1
|
||||
default:
|
||||
options:
|
||||
akismet_site:
|
||||
type: text
|
||||
label: PLUGIN_COMMENTS.AKISMET_SITE_OVERRIDE
|
||||
highlight: 1
|
||||
default:
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Grav\Plugin;
|
||||
|
||||
class Comment
|
||||
{
|
||||
private $id = 0;
|
||||
private $value = array();
|
||||
private $parent = null;
|
||||
private $children = array();
|
||||
|
||||
public function __construct($id, $content) {
|
||||
$this->id = $id;
|
||||
$this->value = $content;
|
||||
}
|
||||
|
||||
public function addItem($obj, $key = null) {
|
||||
}
|
||||
|
||||
public function deleteItem($key) {
|
||||
}
|
||||
|
||||
public function getItem($key) {
|
||||
}
|
||||
|
||||
public function getContent($level = 0) {
|
||||
$this->value['level'] = $level;
|
||||
$comments[] = $this->value;
|
||||
|
||||
foreach($this->children as $child) {
|
||||
//$comments[] = $child->getContent($level + 1); //produces nested result array.
|
||||
$comments = array_merge($comments, $child->getContent($level + 1)); //produces flat result array.
|
||||
}
|
||||
return $comments;
|
||||
}
|
||||
|
||||
public function setParent($parent) {
|
||||
$this->parent = $parent;
|
||||
}
|
||||
public function addSubComment($obj) {
|
||||
$this->children[] = $obj;
|
||||
}
|
||||
|
||||
}
|
770
comments.php
770
comments.php
|
@ -1,93 +1,79 @@
|
|||
<?php
|
||||
namespace Grav\Plugin;
|
||||
|
||||
use Grav\Common\Blueprint;
|
||||
use Grav\Common\Blueprints;
|
||||
use Grav\Common\BlueprintSchema;
|
||||
//use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Filesystem\RecursiveFolderFilterIterator;
|
||||
use Grav\Common\GPM\GPM;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Page;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Plugin;
|
||||
use Grav\Common\Filesystem\RecursiveFolderFilterIterator;
|
||||
use Grav\Common\User\User;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\File\File;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class CommentsPlugin extends Plugin
|
||||
{
|
||||
use Twig_SimpleFunction;
|
||||
require_once PLUGINS_DIR . 'comments/class/Comment.php';
|
||||
require_once 'extern/akismet/Akismet.class.php';
|
||||
class CommentsPlugin extends Plugin {
|
||||
protected $route = 'comments';
|
||||
protected $enable = false;
|
||||
protected $commenting_enabled = false;
|
||||
protected $pingbacks_enabled = false;
|
||||
protected $comments_cache_id;
|
||||
|
||||
protected $pingbacks_cache_id;
|
||||
protected $akismet_enabled;
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
'onPluginsInitialized' => ['onPluginsInitialized', 0]
|
||||
];
|
||||
public static function getSubscribedEvents() {
|
||||
return ['onPluginsInitialized' => ['onPluginsInitialized', 0]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize form if the page has one. Also catches form processing if user posts the form.
|
||||
*
|
||||
* Used by Form plugin < 2.0, kept for backwards compatibility
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public function onPageInitialized()
|
||||
{
|
||||
/** @var Page $page */
|
||||
$page = $this->grav['page'];
|
||||
if (!$page) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->enable) {
|
||||
$header = $page->header();
|
||||
if (!isset($header->form)) {
|
||||
$header->form = $this->grav['config']->get('plugins.comments.form');
|
||||
$page->header($header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the comment form information to the page header dynamically
|
||||
*
|
||||
* Used by Form plugin >= 2.0
|
||||
*/
|
||||
public function onFormPageHeaderProcessed(Event $event)
|
||||
{
|
||||
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;
|
||||
|
@ -100,213 +86,477 @@ class CommentsPlugin extends Plugin
|
|||
}
|
||||
}
|
||||
}
|
||||
$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
|
||||
*/
|
||||
public function initializeFrontend()
|
||||
{
|
||||
$this->calculateEnable();
|
||||
|
||||
$this->enable([
|
||||
'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0],
|
||||
]);
|
||||
|
||||
private function initializeFrontend() {
|
||||
$this->enable(['onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], ]);
|
||||
if ($this->enable) {
|
||||
$this->enable([
|
||||
'onFormProcessed' => ['onFormProcessed', 0],
|
||||
'onFormPageHeaderProcessed' => ['onFormPageHeaderProcessed', 0],
|
||||
'onPageInitialized' => ['onPageInitialized', 10],
|
||||
'onTwigSiteVariables' => ['onTwigSiteVariables', 0]
|
||||
]);
|
||||
$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
|
||||
*/
|
||||
public function initializeAdmin()
|
||||
{
|
||||
private function initializeAdmin() {
|
||||
/** @var Uri $uri */
|
||||
$uri = $this->grav['uri'];
|
||||
|
||||
$this->enable([
|
||||
'onTwigTemplatePaths' => ['onTwigAdminTemplatePaths', 0],
|
||||
'onAdminMenu' => ['onAdminMenu', 0],
|
||||
'onDataTypeExcludeFromDataManagerPluginHook' => ['onDataTypeExcludeFromDataManagerPluginHook', 0],
|
||||
]);
|
||||
|
||||
$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()
|
||||
{
|
||||
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);
|
||||
$localfile = File::instance($localfilename);
|
||||
if (file_exists($localfilename)) {
|
||||
//$data = $localfile->content();
|
||||
$data = Yaml::parse($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);
|
||||
$localfile->save(Yaml::dump($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);
|
||||
$localfile->save(Yaml::dump($data));
|
||||
$entry_removed = true;
|
||||
$message = "Deleted comment ($id) via path ($path)";
|
||||
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);
|
||||
$localfile = File::instance($localfilename);
|
||||
if (file_exists($localfilename)) {
|
||||
//$data = $localfile->content();
|
||||
$data = Yaml::parse($localfile->content());
|
||||
if (isset($data['autoincrement'])) {
|
||||
$data['autoincrement']++;
|
||||
} else {
|
||||
$data['autoincrement'] = max( sizeof($data['comments']), 1 );
|
||||
}
|
||||
|
||||
} 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);
|
||||
$localfile->save(Yaml::dump($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)
|
||||
{
|
||||
public function onFormProcessed(Event $event) {
|
||||
$form = $event['form'];
|
||||
$action = $event['action'];
|
||||
$params = $event['params'];
|
||||
|
||||
if (!$this->active) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'addComment':
|
||||
$post = isset($_POST['data']) ? $_POST['data'] : [];
|
||||
|
||||
$lang = filter_var(urldecode($post['lang']), FILTER_SANITIZE_STRING);
|
||||
$path = filter_var(urldecode($post['path']), FILTER_SANITIZE_STRING);
|
||||
$text = filter_var(urldecode($post['text']), FILTER_SANITIZE_STRING);
|
||||
$name = filter_var(urldecode($post['name']), FILTER_SANITIZE_STRING);
|
||||
$email = filter_var(urldecode($post['email']), FILTER_SANITIZE_STRING);
|
||||
$title = filter_var(urldecode($post['title']), FILTER_SANITIZE_STRING);
|
||||
|
||||
if (isset($this->grav['user'])) {
|
||||
$user = $this->grav['user'];
|
||||
if ($user->authenticated) {
|
||||
$name = $user->fullname;
|
||||
$email = $user->email;
|
||||
}
|
||||
}
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
$lang = $language->getLanguage();
|
||||
|
||||
$filename = DATA_DIR . 'comments';
|
||||
$filename .= ($lang ? '/' . $lang : '');
|
||||
$filename .= $path . '.yaml';
|
||||
$file = File::instance($filename);
|
||||
|
||||
if (file_exists($filename)) {
|
||||
$data = Yaml::parse($file->content());
|
||||
|
||||
$data['comments'][] = [
|
||||
'text' => $text,
|
||||
'date' => date('D, d M Y H:i:s', time()),
|
||||
'author' => $name,
|
||||
'email' => $email
|
||||
];
|
||||
} else {
|
||||
$data = array(
|
||||
'title' => $title,
|
||||
'lang' => $lang,
|
||||
'comments' => array([
|
||||
'text' => $text,
|
||||
'date' => date('D, d M Y H:i:s', time()),
|
||||
'author' => $name,
|
||||
'email' => $email
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
$file->save(Yaml::dump($data));
|
||||
|
||||
//clear cache
|
||||
$this->grav['cache']->delete($this->comments_cache_id);
|
||||
|
||||
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);
|
||||
$localfile = File::instance($filepath);
|
||||
//$localcomments = $localfile->content();
|
||||
$localcomments = Yaml::parse($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';
|
||||
//$path = DATA_DIR . 'comments';
|
||||
$path = $this->grav['page']->path() . '/comments.yaml';
|
||||
}
|
||||
|
||||
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))
|
||||
);
|
||||
$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;
|
||||
|
@ -315,69 +565,129 @@ class CommentsPlugin extends Plugin
|
|||
$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 (object)array("comments" => $comments, "page" => $page, "totalAvailable" => $totalAvailable, "totalRetrieved" => $totalRetrieved);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the comments associated to the current route
|
||||
*/
|
||||
private function fetchComments() {
|
||||
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';
|
||||
|
||||
//$filename = $lang ? '/' . $lang : '';
|
||||
//$filename.= $this->grav['uri']->path() . '.yaml';
|
||||
$filename = $this->grav['page']->path() . '/comments.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();
|
||||
$commentId = 0;
|
||||
foreach ($comments as $key => $comment) {
|
||||
$commentId += 1;
|
||||
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 {
|
||||
if (isset($comment['id'])) {
|
||||
$levelsflat[$comment['id']]['parent'] = $comment['parent'];
|
||||
$levelsflat[$comment['id']]['class'] = new Comment($comment['id'], $comments[$key]);
|
||||
} else {
|
||||
$levelsflat[$commentId]['parent'] = $comment['parent'];
|
||||
$levelsflat[$commentId]['class'] = new Comment($commentId, $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)
|
||||
];
|
||||
$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');
|
||||
$fileInstance = File::instance($this->grav['page']->path() . '/comments.yaml');
|
||||
if (!$fileInstance->content()) {
|
||||
//Item not found
|
||||
return;
|
||||
}
|
||||
//return $fileInstance->content();
|
||||
return Yaml::parse($fileInstance->content());
|
||||
}
|
||||
private function getDataFromFilenameOld($fileRoute) {
|
||||
|
||||
//Single item details
|
||||
$fileInstance = File::instance(DATA_DIR . 'comments/' . $fileRoute);
|
||||
|
@ -389,36 +699,102 @@ class CommentsPlugin extends Plugin
|
|||
|
||||
return Yaml::parse($fileInstance->content());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add templates directory to twig lookup paths.
|
||||
*/
|
||||
public function onTwigTemplatePaths()
|
||||
{
|
||||
public function onTwigTemplatePaths() {
|
||||
$this->grav['twig']->twig_paths[] = __DIR__ . '/templates';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add plugin templates path
|
||||
*/
|
||||
public function onTwigAdminTemplatePaths()
|
||||
{
|
||||
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' => '<i class="fa fa-warning"></i> 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' => '<i class="fa fa-book"></i> ' . $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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
public function onDataTypeExcludeFromDataManagerPluginHook() {
|
||||
$this->grav['admin']->dataTypesExcludedFromDataManagerPlugin[] = 'comments';
|
||||
}
|
||||
}
|
|
@ -1,13 +1,15 @@
|
|||
enabled: true
|
||||
|
||||
pingbacks: true
|
||||
commenting: true
|
||||
akismet_enabled: true
|
||||
warning_message: ""
|
||||
built_in_css: true
|
||||
ajax_callback: /nested-comments
|
||||
enable_on_routes:
|
||||
- '/blog'
|
||||
|
||||
disable_on_routes:
|
||||
- /blog/blog-post-to-ignore
|
||||
- /ignore-this-route
|
||||
#- '/blog/daring-fireball-link'
|
||||
|
||||
form:
|
||||
name: comments
|
||||
fields:
|
||||
|
@ -26,6 +28,13 @@ form:
|
|||
validate:
|
||||
required: true
|
||||
|
||||
- name: site
|
||||
label: PLUGIN_COMMENTS.SITE_LABEL
|
||||
placeholder:
|
||||
type: text
|
||||
validate:
|
||||
required: false
|
||||
|
||||
- name: text
|
||||
label: PLUGIN_COMMENTS.MESSAGE_LABEL
|
||||
placeholder: PLUGIN_COMMENTS.MESSAGE_PLACEHOLDER
|
||||
|
@ -38,10 +47,6 @@ form:
|
|||
process:
|
||||
fillWithCurrentDateTime: true
|
||||
|
||||
- name: title
|
||||
type: hidden
|
||||
evaluateDefault: grav.page.header.title
|
||||
|
||||
- name: lang
|
||||
type: hidden
|
||||
evaluateDefault: grav.language.getLanguage
|
||||
|
@ -50,6 +55,9 @@ form:
|
|||
type: hidden
|
||||
evaluateDefault: grav.uri.path
|
||||
|
||||
- name: blockme
|
||||
type: honeypot
|
||||
|
||||
# - name: g-recaptcha-response
|
||||
# label: Captcha
|
||||
# type: captcha
|
||||
|
|
|
@ -0,0 +1,392 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Akismet anti-comment spam service
|
||||
*
|
||||
* The class in this package allows use of the {@link http://akismet.com Akismet} anti-comment spam service in any PHP5 application.
|
||||
*
|
||||
* This service performs a number of checks on submitted data and returns whether or not the data is likely to be spam.
|
||||
*
|
||||
* Please note that in order to use this class, you must have a vaild {@link http://wordpress.com/api-keys/ WordPress API key}. They are free for non/small-profit types and getting one will only take a couple of minutes.
|
||||
*
|
||||
* For commercial use, please {@link http://akismet.com/commercial/ visit the Akismet commercial licensing page}.
|
||||
*
|
||||
* Please be aware that this class is PHP5 only. Attempts to run it under PHP4 will most likely fail.
|
||||
*
|
||||
* See the Akismet class documentation page linked to below for usage information.
|
||||
*
|
||||
* @package akismet
|
||||
* @author Alex Potsides, {@link http://www.achingbrain.net http://www.achingbrain.net}
|
||||
* @version 0.4
|
||||
* @copyright Alex Potsides, {@link http://www.achingbrain.net http://www.achingbrain.net}
|
||||
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
|
||||
*/
|
||||
|
||||
/**
|
||||
* The Akismet PHP5 Class
|
||||
*
|
||||
* This class takes the functionality from the Akismet WordPress plugin written by {@link http://photomatt.net/ Matt Mullenweg} and allows it to be integrated into any PHP5 application or website.
|
||||
*
|
||||
* The original plugin is {@link http://akismet.com/download/ available on the Akismet website}.
|
||||
*
|
||||
* <b>Usage:</b>
|
||||
* <code>
|
||||
* $akismet = new Akismet('http://www.example.com/blog/', 'aoeu1aoue');
|
||||
* $akismet->setCommentAuthor($name);
|
||||
* $akismet->setCommentAuthorEmail($email);
|
||||
* $akismet->setCommentAuthorURL($url);
|
||||
* $akismet->setCommentContent($comment);
|
||||
* $akismet->setPermalink('http://www.example.com/blog/alex/someurl/');
|
||||
* if($akismet->isCommentSpam())
|
||||
* // store the comment but mark it as spam (in case of a mis-diagnosis)
|
||||
* else
|
||||
* // store the comment normally
|
||||
* </code>
|
||||
*
|
||||
* Optionally you may wish to check if your WordPress API key is valid as in the example below.
|
||||
*
|
||||
* <code>
|
||||
* $akismet = new Akismet('http://www.example.com/blog/', 'aoeu1aoue');
|
||||
*
|
||||
* if($akismet->isKeyValid()) {
|
||||
* // api key is okay
|
||||
* } else {
|
||||
* // api key is invalid
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @package akismet
|
||||
* @name Akismet
|
||||
* @version 0.4
|
||||
* @author Alex Potsides
|
||||
* @link http://www.achingbrain.net/
|
||||
*/
|
||||
class Akismet
|
||||
{
|
||||
private $version = '0.4';
|
||||
private $wordPressAPIKey;
|
||||
private $blogURL;
|
||||
private $comment;
|
||||
private $apiPort;
|
||||
private $akismetServer;
|
||||
private $akismetVersion;
|
||||
|
||||
// This prevents some potentially sensitive information from being sent accross the wire.
|
||||
private $ignore = array('HTTP_COOKIE',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'HTTP_X_FORWARDED_HOST',
|
||||
'HTTP_MAX_FORWARDS',
|
||||
'HTTP_X_FORWARDED_SERVER',
|
||||
'REDIRECT_STATUS',
|
||||
'SERVER_PORT',
|
||||
'PATH',
|
||||
'DOCUMENT_ROOT',
|
||||
'SERVER_ADMIN',
|
||||
'QUERY_STRING',
|
||||
'PHP_SELF' );
|
||||
|
||||
/**
|
||||
* @param string $blogURL The URL of your blog.
|
||||
* @param string $wordPressAPIKey WordPress API key.
|
||||
*/
|
||||
public function __construct($blogURL, $wordPressAPIKey) {
|
||||
$this->blogURL = $blogURL;
|
||||
$this->wordPressAPIKey = $wordPressAPIKey;
|
||||
|
||||
// Set some default values
|
||||
$this->apiPort = 80;
|
||||
$this->akismetServer = 'rest.akismet.com';
|
||||
$this->akismetVersion = '1.1';
|
||||
|
||||
// Start to populate the comment data
|
||||
$this->comment['blog'] = $blogURL;
|
||||
$this->comment['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
|
||||
|
||||
if(isset($_SERVER['HTTP_REFERER'])) {
|
||||
$this->comment['referrer'] = $_SERVER['HTTP_REFERER'];
|
||||
}
|
||||
|
||||
/*
|
||||
* This is necessary if the server PHP5 is running on has been set up to run PHP4 and
|
||||
* PHP5 concurently and is actually running through a separate proxy al a these instructions:
|
||||
* http://www.schlitt.info/applications/blog/archives/83_How_to_run_PHP4_and_PHP_5_parallel.html
|
||||
* and http://wiki.coggeshall.org/37.html
|
||||
* Otherwise the user_ip appears as the IP address of the PHP4 server passing the requests to the
|
||||
* PHP5 one...
|
||||
*/
|
||||
$this->comment['user_ip'] = $_SERVER['REMOTE_ADDR'] != getenv('SERVER_ADDR') ? $_SERVER['REMOTE_ADDR'] : getenv('HTTP_X_FORWARDED_FOR');
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request to the Akismet service to see if the API key passed to the constructor is valid.
|
||||
*
|
||||
* Use this method if you suspect your API key is invalid.
|
||||
*
|
||||
* @return bool True is if the key is valid, false if not.
|
||||
*/
|
||||
public function isKeyValid() {
|
||||
// Check to see if the key is valid
|
||||
$response = $this->sendRequest('key=' . $this->wordPressAPIKey . '&blog=' . $this->blogURL, $this->akismetServer, '/' . $this->akismetVersion . '/verify-key');
|
||||
return $response[1] == 'valid';
|
||||
}
|
||||
|
||||
// makes a request to the Akismet service
|
||||
private function sendRequest($request, $host, $path) {
|
||||
$http_request = "POST " . $path . " HTTP/1.0\r\n";
|
||||
$http_request .= "Host: " . $host . "\r\n";
|
||||
$http_request .= "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n";
|
||||
$http_request .= "Content-Length: " . strlen($request) . "\r\n";
|
||||
$http_request .= "User-Agent: Akismet PHP5 Class " . $this->version . " | Akismet/1.11\r\n";
|
||||
$http_request .= "\r\n";
|
||||
$http_request .= $request;
|
||||
|
||||
$socketWriteRead = new SocketWriteRead($host, $this->apiPort, $http_request);
|
||||
$socketWriteRead->send();
|
||||
|
||||
return explode("\r\n\r\n", $socketWriteRead->getResponse(), 2);
|
||||
}
|
||||
|
||||
// Formats the data for transmission
|
||||
private function getQueryString() {
|
||||
foreach($_SERVER as $key => $value) {
|
||||
if(!in_array($key, $this->ignore)) {
|
||||
if($key == 'REMOTE_ADDR') {
|
||||
$this->comment[$key] = $this->comment['user_ip'];
|
||||
} else {
|
||||
$this->comment[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$query_string = '';
|
||||
|
||||
foreach($this->comment as $key => $data) {
|
||||
if(!is_array($data)) {
|
||||
$query_string .= $key . '=' . urlencode(stripslashes($data)) . '&';
|
||||
}
|
||||
}
|
||||
|
||||
return $query_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for spam.
|
||||
*
|
||||
* Uses the web service provided by {@link http://www.akismet.com Akismet} to see whether or not the submitted comment is spam. Returns a boolean value.
|
||||
*
|
||||
* @return bool True if the comment is spam, false if not
|
||||
* @throws Will throw an exception if the API key passed to the constructor is invalid.
|
||||
*/
|
||||
public function isCommentSpam() {
|
||||
$response = $this->sendRequest($this->getQueryString(), $this->wordPressAPIKey . '.' . $this->akismetServer, '/' . $this->akismetVersion . '/comment-check');
|
||||
|
||||
if($response[1] == 'invalid' && !$this->isKeyValid()) {
|
||||
throw new exception('The Wordpress API key passed to the Akismet constructor is invalid. Please obtain a valid one from http://wordpress.com/api-keys/');
|
||||
}
|
||||
|
||||
return ($response[1] == 'true');
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit spam that is incorrectly tagged as ham.
|
||||
*
|
||||
* Using this function will make you a good citizen as it helps Akismet to learn from its mistakes. This will improve the service for everybody.
|
||||
*/
|
||||
public function submitSpam() {
|
||||
$this->sendRequest($this->getQueryString(), $this->wordPressAPIKey . '.' . $this->akismetServer, '/' . $this->akismetVersion . '/submit-spam');
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit ham that is incorrectly tagged as spam.
|
||||
*
|
||||
* Using this function will make you a good citizen as it helps Akismet to learn from its mistakes. This will improve the service for everybody.
|
||||
*/
|
||||
public function submitHam() {
|
||||
$this->sendRequest($this->getQueryString(), $this->wordPressAPIKey . '.' . $this->akismetServer, '/' . $this->akismetVersion . '/submit-ham');
|
||||
}
|
||||
|
||||
/**
|
||||
* To override the user IP address when submitting spam/ham later on
|
||||
*
|
||||
* @param string $userip An IP address. Optional.
|
||||
*/
|
||||
public function setUserIP($userip) {
|
||||
$this->comment['user_ip'] = $userip;
|
||||
}
|
||||
|
||||
/**
|
||||
* To override the referring page when submitting spam/ham later on
|
||||
*
|
||||
* @param string $referrer The referring page. Optional.
|
||||
*/
|
||||
public function setReferrer($referrer) {
|
||||
$this->comment['referrer'] = $referrer;
|
||||
}
|
||||
|
||||
/**
|
||||
* A permanent URL referencing the blog post the comment was submitted to.
|
||||
*
|
||||
* @param string $permalink The URL. Optional.
|
||||
*/
|
||||
public function setPermalink($permalink) {
|
||||
$this->comment['permalink'] = $permalink;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of comment being submitted.
|
||||
*
|
||||
* May be blank, comment, trackback, pingback, or a made up value like "registration" or "wiki".
|
||||
*/
|
||||
public function setCommentType($commentType) {
|
||||
$this->comment['comment_type'] = $commentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name that the author submitted with the comment.
|
||||
*/
|
||||
public function setCommentAuthor($commentAuthor) {
|
||||
$this->comment['comment_author'] = $commentAuthor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The email address that the author submitted with the comment.
|
||||
*
|
||||
* The address is assumed to be valid.
|
||||
*/
|
||||
public function setCommentAuthorEmail($authorEmail) {
|
||||
$this->comment['comment_author_email'] = $authorEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL that the author submitted with the comment.
|
||||
*/
|
||||
public function setCommentAuthorURL($authorURL) {
|
||||
$this->comment['comment_author_url'] = $authorURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* The comment's body text.
|
||||
*/
|
||||
public function setCommentContent($commentBody) {
|
||||
$this->comment['comment_content'] = $commentBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults to 80
|
||||
*/
|
||||
public function setAPIPort($apiPort) {
|
||||
$this->apiPort = $apiPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults to rest.akismet.com
|
||||
*/
|
||||
public function setAkismetServer($akismetServer) {
|
||||
$this->akismetServer = $akismetServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults to '1.1'
|
||||
*/
|
||||
public function setAkismetVersion($akismetVersion) {
|
||||
$this->akismetVersion = $akismetVersion;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class used by Akismet
|
||||
*
|
||||
* This class is used by Akismet to do the actual sending and receiving of data. It opens a connection to a remote host, sends some data and the reads the response and makes it available to the calling program.
|
||||
*
|
||||
* The code that makes up this class originates in the Akismet WordPress plugin, which is {@link http://akismet.com/download/ available on the Akismet website}.
|
||||
*
|
||||
* N.B. It is not necessary to call this class directly to use the Akismet class. This is included here mainly out of a sense of completeness.
|
||||
*
|
||||
* @package akismet
|
||||
* @name SocketWriteRead
|
||||
* @version 0.1
|
||||
* @author Alex Potsides
|
||||
* @link http://www.achingbrain.net/
|
||||
*/
|
||||
class SocketWriteRead {
|
||||
private $host;
|
||||
private $port;
|
||||
private $request;
|
||||
private $response;
|
||||
private $responseLength;
|
||||
private $errorNumber;
|
||||
private $errorString;
|
||||
|
||||
/**
|
||||
* @param string $host The host to send/receive data.
|
||||
* @param int $port The port on the remote host.
|
||||
* @param string $request The data to send.
|
||||
* @param int $responseLength The amount of data to read. Defaults to 1160 bytes.
|
||||
*/
|
||||
public function __construct($host, $port, $request, $responseLength = 1160) {
|
||||
$this->host = $host;
|
||||
$this->port = $port;
|
||||
$this->request = $request;
|
||||
$this->responseLength = $responseLength;
|
||||
$this->errorNumber = 0;
|
||||
$this->errorString = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the data to the remote host.
|
||||
*
|
||||
* @throws An exception is thrown if a connection cannot be made to the remote host.
|
||||
*/
|
||||
public function send() {
|
||||
$this->response = '';
|
||||
|
||||
$fs = fsockopen($this->host, $this->port, $this->errorNumber, $this->errorString, 3);
|
||||
|
||||
if($this->errorNumber != 0) {
|
||||
throw new Exception('Error connecting to host: ' . $this->host . ' Error number: ' . $this->errorNumber . ' Error message: ' . $this->errorString);
|
||||
}
|
||||
|
||||
if($fs !== false) {
|
||||
@fwrite($fs, $this->request);
|
||||
|
||||
while(!feof($fs)) {
|
||||
$this->response .= fgets($fs, $this->responseLength);
|
||||
}
|
||||
|
||||
fclose($fs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server response text
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getResponse() {
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the error number
|
||||
*
|
||||
* If there was no error, 0 will be returned.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getErrorNumner() {
|
||||
return $this->errorNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the error string
|
||||
*
|
||||
* If there was no error, an empty string will be returned.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getErrorString() {
|
||||
return $this->errorString;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
156
languages.yaml
156
languages.yaml
|
@ -1,146 +1,218 @@
|
|||
de:
|
||||
PLUGIN_COMMENTS:
|
||||
ADD_NEW: Kommentar hinzufügen
|
||||
ADD_REPLY: Auf Kommentar antworten
|
||||
ADD_COMMENT: Kommentar hinzufügen
|
||||
AKISMET: Akismet
|
||||
AKISMET_KEY_API: Akismet API Key
|
||||
AKISMET_SITE_OVERRIDE: Akismet Site Override
|
||||
DELETE_COMMENT: Kommentar löschen
|
||||
REPLY: Antworten
|
||||
DELETE: Löschen
|
||||
SUCCESS: "Der Kommentar wurde erfolgreich gespeichert."
|
||||
COMMENTS: Kommentare
|
||||
COMMENTS_NESTED: Nested Comments
|
||||
COMMENTS_NONE: There are no comments yet.
|
||||
COMMENTS_PINGBACKS: Pingbacks
|
||||
COMMENTS_STATS: Kommentare
|
||||
RECENT_COMMENTS: Neue Kommentare
|
||||
RECENT_PAGES: Kommentierte Seiten
|
||||
EMAIL_NOT_CONFIGURED: Email nicht konfiguriert
|
||||
NEW_COMMENT_EMAIL_SUBJECT: 'Neuer Kommentar für %1$s'
|
||||
NEW_COMMENT_EMAIL_BODY: '<p>Ein neuer Kommentar am %1$s von %3$s (%4$s).</p><p>Seite: %2$s</p><p>Text: %5$s</p>'
|
||||
EMAIL_FOOTER: ''
|
||||
NAME: Name:
|
||||
EMAIL: Email:
|
||||
WRITTEN_ON: geschrieben am
|
||||
BY: von
|
||||
WRITTEN_ON: am
|
||||
BY: Von
|
||||
NAME_LABEL: "Name"
|
||||
NAME_PLACEHOLDER: "Namen eingeben"
|
||||
EMAIL_LABEL: "Email"
|
||||
EMAIL_PLACEHOLDER: "Email-Adresse eingeben"
|
||||
MESSAGE_LABEL: "Kommentar"
|
||||
MESSAGE_PLACEHOLDER: "Kommentar eingeben"
|
||||
SITE_LABEL: Site
|
||||
SUBMIT_COMMENT_BUTTON_TEXT: "Absenden"
|
||||
EMAIL_NEW_COMMENT_SUBJECT: "[Neuer Kommentar] von {{ form.value.name|e }}"
|
||||
THANK_YOU_MESSAGE: "Vielen Dank für den Kommentar!"
|
||||
WARNINGS: Warnings
|
||||
|
||||
en:
|
||||
PLUGIN_COMMENTS:
|
||||
ADD_NEW: Add a comment
|
||||
ADD_REPLY: Reply to comment
|
||||
ADD_COMMENT: Add a comment
|
||||
AKISMET: Akismet
|
||||
AKISMET_KEY_API: Akismet API Key
|
||||
AKISMET_SITE_OVERRIDE: Akismet Site Override
|
||||
DELETE_COMMENT: Delete comment
|
||||
REPLY: Reply
|
||||
DELETE: Delete
|
||||
SUCCESS: "Comment has been saved successfully."
|
||||
COMMENTS: Comments
|
||||
COMMENTS_NESTED: Nested Comments
|
||||
COMMENTS_NONE: There are no comments yet.
|
||||
COMMENTS_PINGBACKS: Pingbacks
|
||||
COMMENTS_STATS: Comments
|
||||
RECENT_COMMENTS: Recent comments
|
||||
RECENT_PAGES: Commented pages
|
||||
EMAIL_NOT_CONFIGURED: Email not configured
|
||||
NEW_COMMENT_EMAIL_SUBJECT: 'New comment on %1$s'
|
||||
NEW_COMMENT_EMAIL_BODY: '<p>A new comment was made on %1$s by %3$s (%4$s).</p><p>Page: %2$s</p><p>Text: %5$s</p>'
|
||||
EMAIL_FOOTER: ''
|
||||
NAME: Name:
|
||||
EMAIL: Email:
|
||||
WRITTEN_ON: Written on
|
||||
BY: by
|
||||
WRITTEN_ON: on
|
||||
BY: By
|
||||
NAME_LABEL: "Name"
|
||||
NAME_PLACEHOLDER: "Enter your name"
|
||||
EMAIL_LABEL: "Email"
|
||||
EMAIL_PLACEHOLDER: "Enter your email address"
|
||||
MESSAGE_LABEL: "Comment"
|
||||
MESSAGE_PLACEHOLDER: "Enter your comment"
|
||||
SITE_LABEL: Site
|
||||
SUBMIT_COMMENT_BUTTON_TEXT: "Submit"
|
||||
EMAIL_NEW_COMMENT_SUBJECT: "[New Comment] from {{ form.value.name|e }}"
|
||||
THANK_YOU_MESSAGE: "Thank you for writing your comment!"
|
||||
WARNINGS: Warnings
|
||||
|
||||
es:
|
||||
PLUGIN_COMMENTS:
|
||||
ADD_COMMENT: Agregar un comentario
|
||||
AKISMET: Akismet
|
||||
AKISMET_KEY_API: Akismet API Key
|
||||
AKISMET_SITE_OVERRIDE: Akismet Site Override
|
||||
COMMENTS: Comentarios
|
||||
COMMENTS_NESTED: Nested Comments
|
||||
COMMENTS_NONE: There are no comments yet.
|
||||
COMMENTS_PINGBACKS: Pingbacks
|
||||
EMAIL_NOT_CONFIGURED: El Email no está configurado
|
||||
NEW_COMMENT_EMAIL_SUBJECT: 'Nuevo comentario en %1$s'
|
||||
NEW_COMMENT_EMAIL_BODY: '<p>Un nuevo comentario se hizo en %1$s por %3$s (%4$s).</p><p>Page: %2$s</p><p>Text: %5$s</p>'
|
||||
EMAIL_FOOTER: ''
|
||||
NAME: Nombre:
|
||||
EMAIL: Email:
|
||||
WRITTEN_ON: Escrito en
|
||||
BY: por
|
||||
WRITTEN_ON: en
|
||||
BY: Por
|
||||
NAME_LABEL: "Nombre"
|
||||
NAME_PLACEHOLDER: "Escriba su nombre"
|
||||
EMAIL_LABEL: "Email"
|
||||
EMAIL_PLACEHOLDER: "Escriba su email"
|
||||
MESSAGE_LABEL: "Comentario"
|
||||
MESSAGE_PLACEHOLDER: "Escriba su comentario"
|
||||
SITE_LABEL: Site
|
||||
SUBMIT_COMMENT_BUTTON_TEXT: "Enviar"
|
||||
EMAIL_NEW_COMMENT_SUBJECT: "[Nuevo comentario] de {{ form.value.name|e }}"
|
||||
THANK_YOU_MESSAGE: "Gracias por escribir su comentario!"
|
||||
WARNINGS: Warnings
|
||||
|
||||
fr:
|
||||
PLUGIN_COMMENTS:
|
||||
ADD_COMMENT: Ajouter un commentaire
|
||||
AKISMET: Akismet
|
||||
AKISMET_KEY_API: Akismet API Key
|
||||
AKISMET_SITE_OVERRIDE: Akismet Site Override
|
||||
COMMENTS: Commentaires
|
||||
COMMENTS_NESTED: Nested Comments
|
||||
COMMENTS_NONE: There are no comments yet.
|
||||
COMMENTS_PINGBACKS: Pingbacks
|
||||
EMAIL_NOT_CONFIGURED: E-mail non configuré
|
||||
NEW_COMMENT_EMAIL_SUBJECT: 'Nouveau commentaire sur %1$s'
|
||||
NEW_COMMENT_EMAIL_BODY: '<p>Un nouveau commentaire a été publié sur %1$s par %3$s (%4$s).</p><p>Page : %2$s</p><p>Texte : %5$s</p>'
|
||||
EMAIL_FOOTER: ''
|
||||
NAME: Nom :
|
||||
EMAIL: E-mail :
|
||||
WRITTEN_ON: Écrit le
|
||||
BY: par
|
||||
WRITTEN_ON: le
|
||||
BY: Par
|
||||
NAME_LABEL: "Nom"
|
||||
NAME_PLACEHOLDER: "Indiquez votre nom"
|
||||
EMAIL_LABEL: "E-mail"
|
||||
EMAIL_PLACEHOLDER: "Indiquez votre adresse e-mail"
|
||||
MESSAGE_LABEL: "Commentaire"
|
||||
MESSAGE_PLACEHOLDER: "Rédigez votre commentaire"
|
||||
SITE_LABEL: Site
|
||||
SUBMIT_COMMENT_BUTTON_TEXT: "Envoyer"
|
||||
EMAIL_NEW_COMMENT_SUBJECT: "[Nouveau commentaire] de {{ form.value.name|e }}"
|
||||
THANK_YOU_MESSAGE: "Merci d'avoir rédigé votre commentaire !"
|
||||
WARNINGS: Warnings
|
||||
|
||||
hr:
|
||||
PLUGIN_COMMENTS:
|
||||
ADD_COMMENT: Dodaj komentar
|
||||
AKISMET: Akismet
|
||||
AKISMET_KEY_API: Akismet API Key
|
||||
AKISMET_SITE_OVERRIDE: Akismet Site Override
|
||||
COMMENTS: Komentari
|
||||
COMMENTS_NESTED: Nested Comments
|
||||
COMMENTS_NONE: There are no comments yet.
|
||||
COMMENTS_PINGBACKS: Pingbacks
|
||||
EMAIL_NOT_CONFIGURED: Email adresa nije podešena
|
||||
NEW_COMMENT_EMAIL_SUBJECT: 'Novi komentar na %1$s'
|
||||
NEW_COMMENT_EMAIL_BODY: '<p>Novi komentar je napisan na %1$s od %3$s (%4$s).</p><p>Stranica:: %2$s</p><p>Tekst: %5$s</p>'
|
||||
EMAIL_FOOTER: ''
|
||||
NAME: Ime:
|
||||
EMAIL: Email:
|
||||
WRITTEN_ON: Napisano je na
|
||||
BY: od
|
||||
WRITTEN_ON: na
|
||||
BY: Od
|
||||
NAME_LABEL: "Ime"
|
||||
NAME_PLACEHOLDER: "Unesite ime"
|
||||
EMAIL_LABEL: "Email adresa"
|
||||
EMAIL_PLACEHOLDER: "Unesite email adresu"
|
||||
MESSAGE_LABEL: "Komentar"
|
||||
MESSAGE_PLACEHOLDER: "Unesite komentar"
|
||||
SITE_LABEL: Site
|
||||
SUBMIT_COMMENT_BUTTON_TEXT: "Pošalji"
|
||||
EMAIL_NEW_COMMENT_SUBJECT: "[Novi komentar] od {{ form.value.name|e }}"
|
||||
THANK_YOU_MESSAGE: "Hvala Vam što ste napisali svoj komentar!"
|
||||
WARNINGS: Warnings
|
||||
|
||||
it:
|
||||
PLUGIN_COMMENTS:
|
||||
ADD_COMMENT: Aggiungi un commento
|
||||
AKISMET: Akismet
|
||||
AKISMET_KEY_API: Akismet API Key
|
||||
AKISMET_SITE_OVERRIDE: Akismet Site Override
|
||||
COMMENTS: Commenti
|
||||
COMMENTS_NESTED: Nested Comments
|
||||
COMMENTS_NONE: There are no comments yet.
|
||||
COMMENTS_PINGBACKS: Pingbacks
|
||||
EMAIL_NOT_CONFIGURED: Email non configurata
|
||||
NEW_COMMENT_EMAIL_SUBJECT: 'Nuovo commento su %1$s'
|
||||
NEW_COMMENT_EMAIL_BODY: '<p>Un nuovo commento è stato postato su %1$s da %3$s (%4$s).</p><p>Pagina: %2$s</p><p>Testo: %5$s</p>'
|
||||
EMAIL_FOOTER: ''
|
||||
NAME: Nome:
|
||||
EMAIL: Email:
|
||||
WRITTEN_ON: Scritto il
|
||||
BY: da
|
||||
WRITTEN_ON: il
|
||||
BY: Da
|
||||
NAME_LABEL: "Nome"
|
||||
NAME_PLACEHOLDER: "Inserisci il tuo nome"
|
||||
EMAIL_LABEL: "Email"
|
||||
EMAIL_PLACEHOLDER: "Inserisci il tuo indirizzo email"
|
||||
MESSAGE_LABEL: "Messaggio"
|
||||
MESSAGE_PLACEHOLDER: "Inserisci il tuo commento"
|
||||
SITE_LABEL: Site
|
||||
SUBMIT_COMMENT_BUTTON_TEXT: "Invia"
|
||||
EMAIL_NEW_COMMENT_SUBJECT: "[Nuovo commento] da {{ form.value.name|e }}"
|
||||
THANK_YOU_MESSAGE: "Grazie per il tuo commento!"
|
||||
WARNINGS: Warnings
|
||||
|
||||
ja:
|
||||
PLUGIN_COMMENTS:
|
||||
ADD_COMMENT: コメントを追加する
|
||||
AKISMET: Akismet
|
||||
AKISMET_KEY_API: Akismet API Key
|
||||
AKISMET_SITE_OVERRIDE: Akismet Site Override
|
||||
COMMENTS: コメント
|
||||
COMMENTS_NESTED: Nested Comments
|
||||
COMMENTS_NONE: There are no comments yet.
|
||||
COMMENTS_PINGBACKS: Pingbacks
|
||||
EMAIL_NOT_CONFIGURED: メールアドレスは設定さていません
|
||||
NEW_COMMENT_EMAIL_SUBJECT: '%1$sについて新しいコメント'
|
||||
NEW_COMMENT_EMAIL_BODY: '<p>新しいコメントが%1$sについて%3$sから(%4$s)書かれた.</p><p>ページー : %2$s</p><p>文書 : %5$s</p>'
|
||||
EMAIL_FOOTER: ''
|
||||
NAME: 名前 :
|
||||
EMAIL: メールアドレス :
|
||||
WRITTEN_ON: に書かれた
|
||||
WRITTEN_ON: に
|
||||
BY: に
|
||||
NAME_LABEL: "名前"
|
||||
NAME_PLACEHOLDER: "お名前を"
|
||||
|
@ -148,98 +220,136 @@ ja:
|
|||
EMAIL_PLACEHOLDER: "ご自分のメールアドレスをここに..."
|
||||
MESSAGE_LABEL: "コメント"
|
||||
MESSAGE_PLACEHOLDER: "コメントをここに"
|
||||
SITE_LABEL: Site
|
||||
SUBMIT_COMMENT_BUTTON_TEXT: "送信する"
|
||||
EMAIL_NEW_COMMENT_SUBJECT: "[新しいコメント]、 {{ form.value.name|e }}から"
|
||||
THANK_YOU_MESSAGE: "コメントを書いてくださいましてありがとうございました!"
|
||||
WARNINGS: Warnings
|
||||
|
||||
pl:
|
||||
PLUGIN_COMMENTS:
|
||||
ADD_COMMENT: Dodaj komentarz
|
||||
AKISMET: Akismet
|
||||
AKISMET_KEY_API: Akismet API Key
|
||||
AKISMET_SITE_OVERRIDE: Akismet Site Override
|
||||
COMMENTS: Komentarzy
|
||||
COMMENTS_NESTED: Nested Comments
|
||||
COMMENTS_NONE: There are no comments yet.
|
||||
COMMENTS_PINGBACKS: Pingbacks
|
||||
EMAIL_NOT_CONFIGURED: Email jest nie skofigurowany
|
||||
NEW_COMMENT_EMAIL_SUBJECT: 'Nowy komentarz %1$s'
|
||||
NEW_COMMENT_EMAIL_BODY: '<p>Pojawił się nowy komentarz, napisany %1$s przez %3$s (%4$s).</p><p>Strona: %2$s</p><p>Treść: %5$s</p>'
|
||||
EMAIL_FOOTER: ''
|
||||
NAME: Imię:
|
||||
EMAIL: Email:
|
||||
WRITTEN_ON: Napisany przez
|
||||
BY: przez
|
||||
WRITTEN_ON: na
|
||||
BY: Przez
|
||||
|
||||
ru:
|
||||
PLUGIN_COMMENTS:
|
||||
ADD_COMMENT: Добавить комментарий
|
||||
AKISMET: Akismet
|
||||
AKISMET_KEY_API: Akismet API Key
|
||||
AKISMET_SITE_OVERRIDE: Akismet Site Override
|
||||
COMMENTS: Комментарии
|
||||
COMMENTS_NESTED: Nested Comments
|
||||
COMMENTS_NONE: There are no comments yet.
|
||||
COMMENTS_PINGBACKS: Pingbacks
|
||||
EMAIL_NOT_CONFIGURED: Email не настроен
|
||||
NEW_COMMENT_EMAIL_SUBJECT: 'Новый комментарий к %1$s'
|
||||
NEW_COMMENT_EMAIL_BODY: '<p>Новый комментарий был сделан на %1$s by %3$s (%4$s).</p><p>Страница: %2$s</p><p>Текст: %5$s</p>'
|
||||
EMAIL_FOOTER: ''
|
||||
NAME: Имя:
|
||||
EMAIL: Email:
|
||||
WRITTEN_ON: Написан в
|
||||
BY: от
|
||||
WRITTEN_ON: на
|
||||
BY: От
|
||||
|
||||
pt-br:
|
||||
PLUGIN_COMMENTS:
|
||||
ADD_COMMENT: Escreva um comentário
|
||||
AKISMET: Akismet
|
||||
AKISMET_KEY_API: Akismet API Key
|
||||
AKISMET_SITE_OVERRIDE: Akismet Site Override
|
||||
COMMENTS: Comentários
|
||||
COMMENTS_NESTED: Nested Comments
|
||||
COMMENTS_NONE: There are no comments yet.
|
||||
COMMENTS_PINGBACKS: Pingbacks
|
||||
EMAIL_NOT_CONFIGURED: E-mail não configurado
|
||||
NEW_COMMENT_EMAIL_SUBJECT: 'Novo comentário em %1$s'
|
||||
NEW_COMMENT_EMAIL_BODY: '<p>Um novo comentário foi feito em %1$s por %3$s (%4$s).</p><p>Página: %2$s</p><p>Texto: %5$s</p>'
|
||||
EMAIL_FOOTER: ''
|
||||
NAME: Name:
|
||||
EMAIL: Email:
|
||||
WRITTEN_ON: Publicado em
|
||||
BY: por
|
||||
WRITTEN_ON: em
|
||||
BY: Por
|
||||
NAME_LABEL: "Nome"
|
||||
NAME_PLACEHOLDER: "Escreva seu nome"
|
||||
EMAIL_LABEL: "E-mail"
|
||||
EMAIL_PLACEHOLDER: "Escreva seu e-mail. Ex.: seunome@provedor.com.br"
|
||||
MESSAGE_LABEL: "Comentário"
|
||||
MESSAGE_PLACEHOLDER: "Escreva seu comentário"
|
||||
SITE_LABEL: Site
|
||||
SUBMIT_COMMENT_BUTTON_TEXT: "Enviar"
|
||||
EMAIL_NEW_COMMENT_SUBJECT: "[Novo comentário] de {{ form.value.name|e }}"
|
||||
THANK_YOU_MESSAGE: "Obrigada por enviar seu comentário!"
|
||||
WARNINGS: Warnings
|
||||
|
||||
ro:
|
||||
PLUGIN_COMMENTS:
|
||||
ADD_COMMENT: 'Adăugați un comentariu'
|
||||
AKISMET: Akismet
|
||||
AKISMET_KEY_API: Akismet API Key
|
||||
AKISMET_SITE_OVERRIDE: Akismet Site Override
|
||||
COMMENTS: 'Comentarii'
|
||||
COMMENTS_NESTED: Nested Comments
|
||||
COMMENTS_NONE: There are no comments yet.
|
||||
COMMENTS_PINGBACKS: Pingbacks
|
||||
EMAIL_NOT_CONFIGURED: 'Adresa de email nu este configurată'
|
||||
NEW_COMMENT_EMAIL_SUBJECT: 'Comentariu nou pentru %1$s'
|
||||
NEW_COMMENT_EMAIL_BODY: '<p>Un nou comentariu a fost adăugat la %1$s de către %3$s (%4$s).</p><p>Pagină: %2$s</p><p>Text: %5$s</p>'
|
||||
EMAIL_FOOTER: ''
|
||||
NAME: 'Nume:'
|
||||
EMAIL: 'Adresă de email:'
|
||||
WRITTEN_ON: 'Scris în data de'
|
||||
BY: 'de către'
|
||||
WRITTEN_ON: 'pe'
|
||||
BY: 'De'
|
||||
NAME_LABEL: "Numele"
|
||||
NAME_PLACEHOLDER: "Introduceți numele Dvs."
|
||||
EMAIL_LABEL: "Email"
|
||||
EMAIL_PLACEHOLDER: "Introduceți adresa Dvs. de email"
|
||||
MESSAGE_LABEL: "Comentariu"
|
||||
MESSAGE_PLACEHOLDER: "Scrieți comentariul Dvs."
|
||||
SITE_LABEL: Site
|
||||
SUBMIT_COMMENT_BUTTON_TEXT: "Trimiteți"
|
||||
EMAIL_NEW_COMMENT_SUBJECT: "[Comentariu nou] from {{ form.value.name|e }}"
|
||||
THANK_YOU_MESSAGE: "Vă mulțumim pentru comentariu!"
|
||||
WARNINGS: Warnings
|
||||
|
||||
no:
|
||||
PLUGIN_COMMENTS:
|
||||
ADD_COMMENT: Skriv en kommentar
|
||||
AKISMET: Akismet
|
||||
AKISMET_KEY_API: Akismet API Key
|
||||
AKISMET_SITE_OVERRIDE: Akismet Site Override
|
||||
COMMENTS: Kommentarer
|
||||
COMMENTS_NESTED: Nested Comments
|
||||
COMMENTS_NONE: There are no comments yet.
|
||||
COMMENTS_PINGBACKS: Pingbacks
|
||||
EMAIL_NOT_CONFIGURED: Epost er ikke konfigurert
|
||||
NEW_COMMENT_EMAIL_SUBJECT: 'Ny kommentar på %1$s'
|
||||
NEW_COMMENT_EMAIL_BODY: '<p>En ny kommentar er skrevet på %1$s av %3$s (%4$s).</p><p>Side: %2$s</p><p>Tekst: %5$s</p>'
|
||||
EMAIL_FOOTER: ''
|
||||
NAME: Navn:
|
||||
EMAIL: Epost:
|
||||
WRITTEN_ON: Skrevet på
|
||||
BY: av
|
||||
WRITTEN_ON: på
|
||||
BY: Av
|
||||
NAME_LABEL: "Navn"
|
||||
NAME_PLACEHOLDER: "Skriv ditt navn"
|
||||
EMAIL_LABEL: "Epost"
|
||||
EMAIL_PLACEHOLDER: "Skriv din epost adresse"
|
||||
MESSAGE_LABEL: "Kommentar"
|
||||
MESSAGE_PLACEHOLDER: "Skriv din kommentar"
|
||||
SITE_LABEL: Site
|
||||
SUBMIT_COMMENT_BUTTON_TEXT: "Send"
|
||||
EMAIL_NEW_COMMENT_SUBJECT: "[Ny kommentar] fra {{ form.value.name|e }}"
|
||||
THANK_YOU_MESSAGE: "Takk for din kommentar!"
|
||||
WARNINGS: Warnings
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<form id="comments-form" name="{{ grav.config.plugins.comments.form.name }}" class="comments-form"
|
||||
action="{{ grav.config.plugins.comments.form.action ? base_url ~ grav.config.plugins.comments.form.action : page.url }}"
|
||||
method="{{ grav.config.plugins.comments.form.method|upper|default('POST') }}">
|
||||
|
||||
{% for field in grav.config.plugins.comments.form.fields %}
|
||||
{% set value = form.value(field.name) %}
|
||||
{% if field.evaluateDefault %}
|
||||
{% set value = evaluate(field.evaluateDefault) %}
|
||||
{% endif %}
|
||||
{% if config.plugins.login.enabled and grav.user.authenticated %}
|
||||
{% if field.name == 'name' %}
|
||||
<input type="hidden" name="{{ (scope ~ field.name)|fieldName }}" value="{{grav.user.fullname}}">
|
||||
{% elseif field.name == 'email' %}
|
||||
<input type="hidden" name="{{ (scope ~ field.name)|fieldName }}" value="{{grav.user.email}}">
|
||||
{% else %}
|
||||
<div>
|
||||
{% include "forms/fields/#{field.type}/#{field.type}.html.twig" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div>
|
||||
{% include "forms/fields/#{field.type}/#{field.type}.html.twig" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% include "forms/fields/formname/formname.html.twig" %}
|
||||
|
||||
<div class="buttons">
|
||||
{% for button in grav.config.plugins.comments.form.buttons %}
|
||||
<button class="button" type="{{ button.type|default('submit') }}">{{ button.value|t|default('Submit') }}</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{{ nonce_field('comments', 'form-nonce')|raw }}
|
||||
</form>
|
||||
|
||||
<div id="comments-alert" class="alert">{{ form.message }}</div>
|
|
@ -1,60 +1,75 @@
|
|||
{% if grav.twig.enable_comments_plugin %}
|
||||
{% set scope = scope ?: 'data.' %}
|
||||
<section id="comments-section">
|
||||
<h2>{{'PLUGIN_COMMENTS.COMMENTS'|t}}</h2>
|
||||
|
||||
<h3>{{'PLUGIN_COMMENTS.ADD_COMMENT'|t}}</h3>
|
||||
|
||||
<form name="{{ grav.config.plugins.comments.form.name }}"
|
||||
action="{{ grav.config.plugins.comments.form.action ? base_url ~ grav.config.plugins.comments.form.action : page.url }}"
|
||||
method="{{ grav.config.plugins.comments.form.method|upper|default('POST') }}">
|
||||
|
||||
{% for field in grav.config.plugins.comments.form.fields %}
|
||||
{% set value = form.value(field.name) %}
|
||||
{% if field.evaluateDefault %}
|
||||
{% set value = evaluate(field.evaluateDefault) %}
|
||||
{% if grav.twig.commenting_enabled %}
|
||||
<a class="comment-add-new" href="#"><i class="fa fa-plus" title="{{'PLUGIN_COMMENTS.ADD_NEW'|t}}"></i> {{'PLUGIN_COMMENTS.ADD_NEW'|t}}</a>
|
||||
{% endif %}
|
||||
{% if config.plugins.login.enabled and grav.user.authenticated %}
|
||||
{% if field.name == 'name' %}
|
||||
<input type="hidden" name="{{ (scope ~ field.name)|fieldName }}" value="{{grav.user.fullname}}">
|
||||
{% elseif field.name == 'email' %}
|
||||
<input type="hidden" name="{{ (scope ~ field.name)|fieldName }}" value="{{grav.user.email}}">
|
||||
{% else %}
|
||||
<div>
|
||||
{% include "forms/fields/#{field.type}/#{field.type}.html.twig" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div>
|
||||
{% include "forms/fields/#{field.type}/#{field.type}.html.twig" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% include "forms/fields/formname/formname.html.twig" %}
|
||||
|
||||
<div class="buttons">
|
||||
{% for button in grav.config.plugins.comments.form.buttons %}
|
||||
<button class="button" type="{{ button.type|default('submit') }}">{{ button.value|t|default('Submit') }}</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{{ nonce_field('form', 'form-nonce')|raw }}
|
||||
</form>
|
||||
|
||||
<div class="alert">{{ form.message }}</div>
|
||||
{% include 'partials/comments.form.html.twig' %}
|
||||
|
||||
<div id="comments" class="row comments">
|
||||
{% if grav.twig.comments|length %}
|
||||
{% set comments_visible = false %}
|
||||
{% for comment in grav.twig.comments %}
|
||||
{% if comment.approved == "true" %}
|
||||
<div class="comment-wrapper">
|
||||
{% set comments_visible = true %}
|
||||
{% for level in range(0, comment.level|e) %}
|
||||
{% if level == comment.level|e %}
|
||||
<div id="thread-{{level}}" class="comment-thread-top comment-thread">
|
||||
</div>
|
||||
{% else %}
|
||||
<div id="thread-{{level}}" class="comment-thread">
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div id="comment-{{comment.id}}" class="comment comment-level-{{comment.level|e}}" data-id="{{comment.id}}" itemtype="http://schema.org/UserComments">
|
||||
<header class="comment-heading">
|
||||
<img class="comment-avatar" src="https://www.gravatar.com/avatar/{{comment.email|trim|lower|md5}}?size=20&d=identicon" alt="user icon">
|
||||
<span class="comment-meta">
|
||||
{% if comment.site %}
|
||||
<a href="{{comment.site}}">{{comment.author}}</a>
|
||||
{% else %}
|
||||
{{comment.author}}
|
||||
{% endif %}
|
||||
<a href="{{uri.url(true)}}#comment-{{comment.id}}" title="Link to this comment" itemprop="url">
|
||||
<time class="comment-date" datetime="{{comment.date|e}}" itemprop="commentTime">
|
||||
{{comment.date|nicetime(false)}}
|
||||
</time>
|
||||
</a>
|
||||
</span>
|
||||
</header>
|
||||
<div class="comment-body">
|
||||
<div class="comment-text" >
|
||||
{{comment.text}}
|
||||
</div>
|
||||
{{nested}}
|
||||
<div class="comment-footer">
|
||||
<span class="comment-reply">
|
||||
{% if grav.twig.commenting_enabled and comment.id %}
|
||||
<a class="comment-add-reply" href="#"><i class="fa fa-reply" title="{{'PLUGIN_COMMENTS.ADD_REPLY'|t}}"></i> {{'PLUGIN_COMMENTS.REPLY'|t}}</a>
|
||||
{% endif %}
|
||||
{% if grav.user.access.admin.super %}
|
||||
<a class="comment-delete" href="#"><i class="fa fa-trash" title="{{'PLUGIN_COMMENTS.DELETE_COMMENT'|t}}"></i> {{'PLUGIN_COMMENTS.DELETE'|t}}</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<h3>{{'PLUGIN_COMMENTS.COMMENTS'|t}}</h3>
|
||||
|
||||
<table>
|
||||
{% for comment in grav.twig.comments|array_reverse %}
|
||||
{% if not comments_visible %}
|
||||
<tr>
|
||||
<td>
|
||||
{{comment.text}}
|
||||
<br />
|
||||
{{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}}
|
||||
{{'PLUGIN_COMMENTS.COMMENTS_NONE'|t}}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
{# you may set options when using this partial. Example: include 'partials/recentcomments.html.twig' with {'limit': 5, 'pages_limit': 3} #}
|
||||
{% if grav.twig.enable_comments_plugin %}
|
||||
<h1>{{'PLUGIN_COMMENTS.COMMENTS_STATS'|t}}</h1>
|
||||
{% set stats = recent_comments(limit|default(5), pages_limit|default(3)) %}
|
||||
{% if stats.global_stats.active_entries %}
|
||||
<i class="fa fa-comments" title="active_entries"></i> {{stats.global_stats.active_entries}}
|
||||
(<i class="fa fa-trash" title="deleted_entries"></i> {{stats.global_stats.deleted_entries}})
|
||||
- <i class="fa fa-comment" title="active_comments"></i>{{stats.global_stats.active_comments}}
|
||||
(<i class="fa fa-trash" title="deleted_comments"></i> {{stats.global_stats.deleted_comments}})
|
||||
- <i class="fa fa-commenting" title="active_replies"></i>{{stats.global_stats.active_replies}}
|
||||
(<i class="fa fa-trash" title="deleted_replies"></i> {{stats.global_stats.deleted_replies}})
|
||||
- <i class="fa fa-files-o" title="pages_with_active_entries"></i> {{stats.global_stats.pages_with_active_entries}}
|
||||
{% endif %}
|
||||
{% for key, entry in stats.pages %}
|
||||
{% if loop.first %}
|
||||
<h2>{{'PLUGIN_COMMENTS.RECENT_PAGES'|t}} (limit {{stats.options.pages_limit}})</h2>
|
||||
<ul class="fa-ul">
|
||||
{% endif %}
|
||||
<li><i class="fa-li fa fa-file" title="{{entry.route}}"></i>
|
||||
{% if entry.route %}
|
||||
<a href="{{entry.route}}#comments">
|
||||
{% endif %}
|
||||
{{entry.active_entries}}
|
||||
{% if entry.route %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% if loop.last %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for key, entry in stats.comments %}
|
||||
{% if loop.first %}
|
||||
<h2>{{'PLUGIN_COMMENTS.RECENT_COMMENTS'|t}} (limit {{stats.options.comments_limit}})</h2>
|
||||
<ul class="fa-ul">
|
||||
{% endif %}
|
||||
{% set entry_icon = 'fa-comment' %}
|
||||
{% if not empty(entry.parent) %}
|
||||
{% set entry_icon = 'fa-commenting' %}
|
||||
{% endif %}
|
||||
<li><i class="fa-li fa {{entry_icon}}" title="{{key}}: {{entry.id}}, {{entry.parent}}"></i>
|
||||
{% if entry.route %}
|
||||
<a href="{{entry.route}}#comments">
|
||||
{% endif %}
|
||||
{{entry.date}}, {{entry.author}}, {{entry.text|truncate(15)}}
|
||||
{% if entry.route %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% if loop.last %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
Loading…
Reference in New Issue