diff --git a/README.md b/README.md
index a403d84..7c6b54d 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,9 @@
-# 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; if it won't take too much work I'll add more support for them.
+
+The main goal of this fork is to add support for Akismet. Recaptcha guards from bots, but Akismet helps deal with actual spam content.
+---
The **Comments Plugin** for [Grav](http://github.com/getgrav/grav) adds the ability to add comments to pages, and moderate them.
diff --git a/blueprints.yaml b/blueprints.yaml
index 1daf30b..9caf823 100644
--- a/blueprints.yaml
+++ b/blueprints.yaml
@@ -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,23 @@ form:
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
+ pingbacks:
+ type: toggle
+ label: Pingbacks
+ highlight: 1
+ default: 0
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
+ commenting:
+ type: toggle
+ label: Commenting
+ highlight: 1
+ default: 0
+ options:
+ 1: PLUGIN_ADMIN.ENABLED
+ 0: PLUGIN_ADMIN.DISABLED
+ validate:
+ type: bool
diff --git a/comments.php b/comments.php
index 7a59543..b1b0b7f 100644
--- a/comments.php
+++ b/comments.php
@@ -1,6 +1,9 @@
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();
}
/**
@@ -100,6 +110,13 @@ class CommentsPlugin extends Plugin
}
}
}
+
+ #$blueprint = $this->getBlueprint();
+ #$this->commenting_enabled = $blueprint->get('form/fields/commenting', false, '/');
+ #$this->pingbacks_enabled = $blueprint->get('form/fields/pingbacks' , false, '/');
+
+ $this->commenting_enabled = $this->grav['config']->get('plugins.comments.commenting');
+ $this->pingbacks_enabled = $this->grav['config']->get('plugins.comments.pingbacks');
}
/**
@@ -127,6 +144,8 @@ class CommentsPlugin extends Plugin
//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());
}
/**
@@ -185,16 +204,21 @@ class CommentsPlugin extends Plugin
return;
}
+ if (!$this->commenting_enabled) {
+ 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);
+ $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);
+ $site = filter_var(urldecode($post['site']), FILTER_SANITIZE_STRING);
if (isset($this->grav['user'])) {
$user = $this->grav['user'];
@@ -220,7 +244,8 @@ class CommentsPlugin extends Plugin
'text' => $text,
'date' => date('D, d M Y H:i:s', time()),
'author' => $name,
- 'email' => $email
+ 'email' => $email,
+ 'site' => $site
];
} else {
$data = array(
@@ -230,7 +255,8 @@ class CommentsPlugin extends Plugin
'text' => $text,
'date' => date('D, d M Y H:i:s', time()),
'author' => $name,
- 'email' => $email
+ 'email' => $email,
+ 'site' => $site
])
);
}
@@ -239,6 +265,7 @@ class CommentsPlugin extends Plugin
//clear cache
$this->grav['cache']->delete($this->comments_cache_id);
+ $this->grav['cache']->delete($this->pingbacks_cache_id);
break;
}
@@ -353,6 +380,26 @@ class CommentsPlugin extends Plugin
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->getDataFromFilename($filename)['pingbacks'];
+ //save to cache if enabled
+ $cache->save($this->pingbacks_cache_id, $pingbacks);
+ return $pingbacks;
+ }
+
/**
* Return the latest commented pages
*/
diff --git a/comments.yaml b/comments.yaml
index 5c09966..3115067 100644
--- a/comments.yaml
+++ b/comments.yaml
@@ -1,4 +1,6 @@
enabled: true
+pingbacks: true
+commenting: true
enable_on_routes:
- '/blog'
@@ -6,7 +8,6 @@ enable_on_routes:
disable_on_routes:
- /blog/blog-post-to-ignore
- /ignore-this-route
- #- '/blog/daring-fireball-link'
form:
name: comments
@@ -26,6 +27,13 @@ form:
validate:
required: true
+ - name: blah
+ label: PLUGIN_COMMENTS.EMAIL_LABEL
+ placeholder: "https://leetnightshade.com"
+ type: text
+ validate:
+ required: false
+
- name: text
label: PLUGIN_COMMENTS.MESSAGE_LABEL
placeholder: PLUGIN_COMMENTS.MESSAGE_PLACEHOLDER
@@ -50,6 +58,9 @@ form:
type: hidden
evaluateDefault: grav.uri.path
+ - name: blockme
+ type: honeypot
+
# - name: g-recaptcha-response
# label: Captcha
# type: captcha
diff --git a/extern/habariakismet/habariakismet.plugin.php b/extern/habariakismet/habariakismet.plugin.php
new file mode 100644
index 0000000..aa6399d
--- /dev/null
+++ b/extern/habariakismet/habariakismet.plugin.php
@@ -0,0 +1,148 @@
+plugin_id()) {
+ $actions[] = _t('Configure');
+ }
+
+ return $actions;
+ }
+
+ public function action_plugin_ui($plugin_id, $action)
+ {
+ if ($plugin_id == $this->plugin_id()) {
+ switch ($action) {
+ case _t('Configure'):
+
+ $form = new FormUI(strtolower(get_class($this)));
+ $form->append('select', 'provider', 'habariakismet__provider', _t('Service'));
+ $form->provider->options = array(
+ 'Akismet' => 'Akismet',
+ 'TypePad AntiSpam' => 'TypePad AntiSpam'
+ );
+ $api_key = $form->append('text', 'api_key', 'habariakismet__api_key', _t('API Key'));
+ $api_key->add_validator('validate_required');
+ $api_key->add_validator(array($this, 'validate_api_key'));
+ $form->append('submit', 'save', 'Save');
+ $form->out();
+ break;
+ }
+ }
+ }
+
+ public function validate_api_key($key, $control, $form)
+ {
+ $endpoint = ($form->provider->value == 'Akismet') ? self::SERVER_AKISMET : self::SERVER_TYPEPAD;
+
+ $a = new Akismet(Site::get_url('habari'), $key);
+ $a->setAkismetServer($endpoint);
+
+ if (!$a->isKeyValid()) {
+ return array(sprintf(_t('Sorry, the %s API key %s is invalid. Please check to make sure the key is entered correctly.'), $form->provider->value, $key));
+ }
+
+ return array();
+ }
+
+ public function set_priorities()
+ {
+ return array(
+ 'action_comment_insert_before' => 1
+ );
+ }
+
+ public function action_comment_insert_before(Comment $comment)
+ {
+ $api_key = Options::get('habariakismet__api_key');
+ $provider = Options::get('habariakismet__provider');
+
+ if ($api_key == null || $provider == null) {
+ return;
+ }
+
+ $endpoint = ($provider == 'Akismet') ? self::SERVER_AKISMET : self::SERVER_TYPEPAD;
+
+ $a = new Akismet(Site::get_url('habari'), $api_key);
+ $a->setAkismetServer($endpoint);
+ $a->setCommentAuthor($comment->name);
+ $a->setCommentAuthorEmail($comment->email);
+ $a->setCommentAuthorURL($comment->url);
+ $a->setCommentContent($comment->content);
+ $a->setPermalink($comment->post->permalink);
+
+ try {
+ $comment->status = ($a->isCommentSpam()) ? 'spam' : 'ham';
+ return;
+ } catch (Exception $e) {
+ EventLog::log($e->getMessage(), 'notice', 'comment', 'HabariAkismet');
+ }
+ }
+
+ public function action_admin_moderate_comments($action, $comments, $handler)
+ {
+ $false_negatives = 0;
+ $false_positives = 0;
+
+ $provider = Options::get('habariakismet__provider');
+ $endpoint = ($provider == 'Akismet') ? self::SERVER_AKISMET : self::SERVER_TYPEPAD;
+
+ $a = new Akismet(Site::get_url('habari'), Options::get('habariakismet__api_key'));
+ $a->setAkismetServer($endpoint);
+
+ foreach ($comments as $comment) {
+ switch ($action) {
+ case 'spam':
+ if ($comment->status == Comment::STATUS_APPROVED || $comment->status == Comment::STATUS_UNAPPROVED) {
+ $a->setCommentAuthor($comment->name);
+ $a->setCommentAuthorEmail($comment->email);
+ $a->setCommentAuthorURL($comment->url);
+ $a->setCommentContent($comment->content);
+
+ $a->submitSpam();
+
+ $false_negatives++;
+ }
+
+ break;
+ case 'approved':
+ if ($comment->status == Comment::STATUS_SPAM) {
+ $a->setCommentAuthor($comment->name);
+ $a->setCommentAuthorEmail($comment->email);
+ $a->setCommentAuthorURL($comment->url);
+ $a->setCommentContent($comment->content);
+
+ $a->submitHam();
+
+ $false_positives++;
+ }
+
+ break;
+ }
+ }
+
+ if ($false_negatives) {
+ Session::notice(_t('Reported %d false negatives to %s.', array($false_negatives, $provider)));
+ }
+
+ if ($false_positives) {
+ Session::notice(_t('Reported %d false positives to %s.', array($false_positives, $provider)));
+ }
+ }
+}
+
+?>
diff --git a/extern/habariakismet/habariakismet.plugin.xml b/extern/habariakismet/habariakismet.plugin.xml
new file mode 100644
index 0000000..b1c59a4
--- /dev/null
+++ b/extern/habariakismet/habariakismet.plugin.xml
@@ -0,0 +1,10 @@
+
+
+ * $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
+ *
+ *
+ * Optionally you may wish to check if your WordPress API key is valid as in the example below.
+ *
+ *
+ * $akismet = new Akismet('http://www.example.com/blog/', 'aoeu1aoue');
+ *
+ * if($akismet->isKeyValid()) {
+ * // api key is okay
+ * } else {
+ * // api key is invalid
+ * }
+ *
+ *
+ * @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;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/languages.yaml b/languages.yaml
index 812f22d..173ba3c 100644
--- a/languages.yaml
+++ b/languages.yaml
@@ -24,6 +24,8 @@ en:
PLUGIN_COMMENTS:
ADD_COMMENT: Add a comment
COMMENTS: Comments
+ COMMENTS_NONE: There are no comments yet.
+ COMMENTS_PINGBACKS: Pingbacks
EMAIL_NOT_CONFIGURED: Email not configured
NEW_COMMENT_EMAIL_SUBJECT: 'New comment on %1$s'
NEW_COMMENT_EMAIL_BODY: '
A new comment was made on %1$s by %3$s (%4$s).
Page: %2$s
Text: %5$s
' diff --git a/templates/partials/comments.html.twig b/templates/partials/comments.html.twig index 29d79b9..b4153a0 100644 --- a/templates/partials/comments.html.twig +++ b/templates/partials/comments.html.twig @@ -1,60 +1,114 @@ {% if grav.twig.enable_comments_plugin %} {% set scope = scope ?: 'data.' %} -
- {{comment.text}}
- - {{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}} - |
-
+ {{comment.text}}
+ + {{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} + {% if comment.site %} + {{comment.author}} + {% else %} + {{comment.author}} + {% endif %} + |
+
+ {{'PLUGIN_COMMENTS.COMMENTS_NONE'|t}} + | +