From e8417c6a0ec0a6b6a95e6155d9b8f2ffcf99ee60 Mon Sep 17 00:00:00 2001 From: Thorsten Witteler Date: Thu, 19 Oct 2017 17:42:20 +0200 Subject: [PATCH 01/16] add index file to user data add data files to page folder --- comments.php | 120 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 88 insertions(+), 32 deletions(-) diff --git a/comments.php b/comments.php index 7a59543..df1f915 100644 --- a/comments.php +++ b/comments.php @@ -1,17 +1,19 @@ 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 * @@ -117,7 +95,6 @@ class CommentsPlugin extends Plugin $this->enable([ 'onFormProcessed' => ['onFormProcessed', 0], 'onFormPageHeaderProcessed' => ['onFormPageHeaderProcessed', 0], - 'onPageInitialized' => ['onPageInitialized', 10], 'onTwigSiteVariables' => ['onTwigSiteVariables', 0] ]); } @@ -208,13 +185,90 @@ class CommentsPlugin extends Plugin $language = $this->grav['language']; $lang = $language->getLanguage(); + /******************************/ + /** store comments with page **/ + /******************************/ + $page = $this->grav['page']; + $localfilename = $page->path() . '/comments.yaml'; + $localfile = CompiledYamlFile::instance($localfilename); + if (file_exists($localfilename)) { + $data = $localfile->content(); + $data['autoincrement']++; + $data['comments'][] = [ + 'id' => $data['autoincrement'], + 'parent' => 0, + 'lang' => $lang, + 'title' => $title, + 'text' => $text, + 'date' => date('D, d M Y H:i:s', time()), + 'author' => $name, + 'email' => $email + ]; + } else { + $data = array( + 'autoincrement' => 1, + 'comments' => array([ + 'id' => 1, + 'parent' => 0, + 'lang' => $lang, + 'title' => $title, + 'text' => $text, + 'date' => date('D, d M Y H:i:s', time()), + 'author' => $name, + 'email' => $email + ]) + ); + } + $localfile->save($data); + $localid = $data['autoincrement']; + $data = null; + /**********************************/ + /** store comments in index file **/ + /**********************************/ + $indexfilename = DATA_DIR . 'comments/index.yaml'; + $indexfile = CompiledYamlFile::instance($indexfilename); + if (file_exists($indexfilename)) { + $data = $indexfile->content(); + $data['comments'][] = [ + 'page' => $page->route(), + 'id' => $localid, + 'parent' => 0, + 'lang' => $lang, + 'title' => $title, + 'text' => $text, + 'date' => date('D, d M Y H:i:s', time()), + 'author' => $name, + 'email' => $email + ]; + } else { + $data = array( + 'comments' => array([ + 'page' => $page->route(), + 'id' => $localid, + 'parent' => 0, + 'lang' => $lang, + 'title' => $title, + 'text' => $text, + 'date' => date('D, d M Y H:i:s', time()), + 'author' => $name, + 'email' => $email + ]) + ); + } + $indexfile->save($data); + $data = null; + /**************************************/ + /** store comments in old data files **/ + /** TODO: remove as soon as admin **/ + /** panel uses new index file **/ + /**************************************/ $filename = DATA_DIR . 'comments'; $filename .= ($lang ? '/' . $lang : ''); $filename .= $path . '.yaml'; - $file = File::instance($filename); + $file = CompiledYamlFile::instance($filename); if (file_exists($filename)) { - $data = Yaml::parse($file->content()); + $data = $file->content(); $data['comments'][] = [ 'text' => $text, @@ -235,7 +289,7 @@ class CommentsPlugin extends Plugin ); } - $file->save(Yaml::dump($data)); + $file->save($data); //clear cache $this->grav['cache']->delete($this->comments_cache_id); @@ -380,14 +434,16 @@ class CommentsPlugin extends Plugin private function getDataFromFilename($fileRoute) { //Single item details - $fileInstance = File::instance(DATA_DIR . 'comments/' . $fileRoute); + //$fileInstance = CompiledYamlFile::instance(DATA_DIR . 'comments/' . $fileRoute); + //Use comment file in page folder + $fileInstance = CompiledYamlFile::instance($this->grav['page']->path() . '/comments.yaml'); if (!$fileInstance->content()) { //Item not found return; } - return Yaml::parse($fileInstance->content()); + return $fileInstance->content(); } /** From 9387fba6ed6d39ca17f8567c916203595bb9a5ad Mon Sep 17 00:00:00 2001 From: Thorsten Witteler Date: Thu, 19 Oct 2017 18:53:40 +0200 Subject: [PATCH 02/16] nested template --- templates/partials/comments.form.html.twig | 38 +++++++++++ templates/partials/comments.html.twig | 75 ++++++++-------------- 2 files changed, 64 insertions(+), 49 deletions(-) create mode 100644 templates/partials/comments.form.html.twig diff --git a/templates/partials/comments.form.html.twig b/templates/partials/comments.form.html.twig new file mode 100644 index 0000000..dab77dc --- /dev/null +++ b/templates/partials/comments.form.html.twig @@ -0,0 +1,38 @@ +

{{'PLUGIN_COMMENTS.ADD_COMMENT'|t}}

+
+ + {% 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' %} + + {% elseif field.name == 'email' %} + + {% else %} +
+ {% include "forms/fields/#{field.type}/#{field.type}.html.twig" %} +
+ {% endif %} + {% else %} +
+ {% include "forms/fields/#{field.type}/#{field.type}.html.twig" %} +
+ {% endif %} + {% endfor %} + {% include "forms/fields/formname/formname.html.twig" %} + +
+ {% for button in grav.config.plugins.comments.form.buttons %} + + {% endfor %} +
+ + {{ nonce_field('form', 'form-nonce')|raw }} +
+ +
{{ form.message }}
diff --git a/templates/partials/comments.html.twig b/templates/partials/comments.html.twig index 29d79b9..3eeb423 100644 --- a/templates/partials/comments.html.twig +++ b/templates/partials/comments.html.twig @@ -1,60 +1,37 @@ {% if grav.twig.enable_comments_plugin %} {% set scope = scope ?: 'data.' %} -

{{'PLUGIN_COMMENTS.ADD_COMMENT'|t}}

- -
- - {% 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' %} - - {% elseif field.name == 'email' %} - - {% else %} -
- {% include "forms/fields/#{field.type}/#{field.type}.html.twig" %} -
- {% endif %} - {% else %} -
- {% include "forms/fields/#{field.type}/#{field.type}.html.twig" %} -
- {% endif %} - {% endfor %} - {% include "forms/fields/formname/formname.html.twig" %} - -
- {% for button in grav.config.plugins.comments.form.buttons %} - - {% endfor %} -
- - {{ nonce_field('form', 'form-nonce')|raw }} -
- -
{{ form.message }}
+ {% include 'partials/comments.form.html.twig' %} {% if grav.twig.comments|length %}

{{'PLUGIN_COMMENTS.COMMENTS'|t}}

- - +
{% for comment in grav.twig.comments|array_reverse %} -
- - +
+
+ + user icon + +
+
+

{{comment.title}}

+
+ Reply +
+
+ {{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}} +
+
+ {{comment.text}} +
+ {{nested}} +
+
{% endfor %} -
- {{comment.text}} -
- {{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}} -
+ + {% endif %} {% endif %} + + From 1bc97a31e7edf2cad3643c13d8cbe983d4ddf187 Mon Sep 17 00:00:00 2001 From: Thorsten Witteler Date: Thu, 19 Oct 2017 19:40:02 +0200 Subject: [PATCH 03/16] nested template --- templates/partials/comments.html.twig | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/templates/partials/comments.html.twig b/templates/partials/comments.html.twig index 3eeb423..a481604 100644 --- a/templates/partials/comments.html.twig +++ b/templates/partials/comments.html.twig @@ -8,21 +8,19 @@

{{'PLUGIN_COMMENTS.COMMENTS'|t}}

{% for comment in grav.twig.comments|array_reverse %} -
-
+
+ -
-

{{comment.title}}

-
- Reply +
+
+

{{comment.title}}

+ +
{{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}}
-
- {{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}} -
-
+
{{comment.text}}
{{nested}} From 018a42a3dc3c8855a374b41723f2d7dea0d28ae1 Mon Sep 17 00:00:00 2001 From: Thorsten Witteler Date: Thu, 19 Oct 2017 22:00:00 +0200 Subject: [PATCH 04/16] add level calculation --- class/Comment.php | 41 +++++++++++++++++++++++++++++++++++++++++ comments.php | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 class/Comment.php diff --git a/class/Comment.php b/class/Comment.php new file mode 100644 index 0000000..63c4c3a --- /dev/null +++ b/class/Comment.php @@ -0,0 +1,41 @@ +id = $id; + $this->value = $content; + } + + public function addItem($obj, $key = null) { + } + + public function deleteItem($key) { + } + + public function getItem($key) { + } + + public function getContent($level = 0) { + $comments = $this->value; + $comments['level'] = $level; + + foreach($this->children as $child) { + array_merge($comments, $child->getContent($level + 1)); + } + return $comments; + } + + public function setParent($parent) { + $this->parent = $parent; + } + public function addSubComment($obj) { + $this->children[] = $obj; + } + +} \ No newline at end of file diff --git a/comments.php b/comments.php index df1f915..7b9e312 100644 --- a/comments.php +++ b/comments.php @@ -16,6 +16,8 @@ use RocketTheme\Toolbox\Event\Event; use RocketTheme\Toolbox\File\File; use Symfony\Component\Yaml\Yaml; +require_once 'class\Comment.php'; + class CommentsPlugin extends Plugin { protected $route = 'comments'; @@ -402,11 +404,48 @@ class CommentsPlugin extends Plugin $filename .= $this->grav['uri']->path() . '.yaml'; $comments = $this->getDataFromFilename($filename)['comments']; + $comments = 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) { + $levels = array(); + $levelsflat = array(); + foreach($comments as $key => $comment) { + //$comments[$key]['level'] = 0; + $levels[$comment['parent']][] = $comment['id']; + $levelsflat[$comment['id']]['parent'] = $comment['parent']; + $levelsflat[$comment['id']]['class'] = new Comment($comment['id'], $comments[$key]); + } + //get starting points (entries without valid parent = root element) + $leveltree = array(); + foreach($levelsflat as $id => $parent) { + $parent_id = $parent['parent']; + if(!isset($levelsflat[$parent_id]){ + $leveltree[$id] = $levelsflat[$id]['class']; + //$leveltree[$id] = array(); + //$leveltree[$id]['level'] = 0; + //$leveltree[$id]['children'] = array(); + } else { + $currentParent = $levelsflat[$parent_id]['class']; + $currentChild = $levelsflat[$id]['class']; + $levelsflat[$id]['class']->setParent($currentParent); + $levelsflat[$parent_id]['class']->addSubComment($currentChild); + } + } + //reset comment values to nested order + $comments = array(); + foreach($leveltree as $id => $comment) { + array_merge($comments, $comment->getContent); + } + return $comments; + } + /** * Return the latest commented pages */ From 6d458f8fa4cb3ebbd22506d98aa584e67eef04c8 Mon Sep 17 00:00:00 2001 From: Thorsten Witteler Date: Thu, 19 Oct 2017 23:06:47 +0200 Subject: [PATCH 05/16] add level calculation --- class/Comment.php | 8 +++++--- comments.php | 15 +++++---------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/class/Comment.php b/class/Comment.php index 63c4c3a..affe1e9 100644 --- a/class/Comment.php +++ b/class/Comment.php @@ -1,5 +1,7 @@ value; - $comments['level'] = $level; + $this->value['level'] = $level; + $comments[] = $this->value; foreach($this->children as $child) { - array_merge($comments, $child->getContent($level + 1)); + $comments[] = $child->getContent($level + 1); } return $comments; } diff --git a/comments.php b/comments.php index 7b9e312..4771e57 100644 --- a/comments.php +++ b/comments.php @@ -16,7 +16,8 @@ use RocketTheme\Toolbox\Event\Event; use RocketTheme\Toolbox\File\File; use Symfony\Component\Yaml\Yaml; -require_once 'class\Comment.php'; +require_once '/html/apps/grav/user/plugins/comments/class/Comment.php'; +use Grav\Plugin\Comment; class CommentsPlugin extends Plugin { @@ -404,7 +405,7 @@ class CommentsPlugin extends Plugin $filename .= $this->grav['uri']->path() . '.yaml'; $comments = $this->getDataFromFilename($filename)['comments']; - $comments = setCommentLevels($comments); + $comments = $this->setCommentLevels($comments); //save to cache if enabled $cache->save($this->comments_cache_id, $comments); return $comments; @@ -414,11 +415,8 @@ class CommentsPlugin extends Plugin * Return the latest commented pages */ private function setCommentLevels($comments) { - $levels = array(); $levelsflat = array(); foreach($comments as $key => $comment) { - //$comments[$key]['level'] = 0; - $levels[$comment['parent']][] = $comment['id']; $levelsflat[$comment['id']]['parent'] = $comment['parent']; $levelsflat[$comment['id']]['class'] = new Comment($comment['id'], $comments[$key]); } @@ -426,11 +424,8 @@ class CommentsPlugin extends Plugin $leveltree = array(); foreach($levelsflat as $id => $parent) { $parent_id = $parent['parent']; - if(!isset($levelsflat[$parent_id]){ + if(!isset($levelsflat[$parent_id])){ $leveltree[$id] = $levelsflat[$id]['class']; - //$leveltree[$id] = array(); - //$leveltree[$id]['level'] = 0; - //$leveltree[$id]['children'] = array(); } else { $currentParent = $levelsflat[$parent_id]['class']; $currentChild = $levelsflat[$id]['class']; @@ -441,7 +436,7 @@ class CommentsPlugin extends Plugin //reset comment values to nested order $comments = array(); foreach($leveltree as $id => $comment) { - array_merge($comments, $comment->getContent); + $comments = array_merge($comments, $comment->getContent()); } return $comments; } From 7d153816f9abb5a9b235511ce46f6acea3c5bcb0 Mon Sep 17 00:00:00 2001 From: Thorsten Witteler Date: Fri, 20 Oct 2017 14:06:02 +0200 Subject: [PATCH 06/16] prepare ajax --- assets/comments.js | 83 ++++++++++++++++++++++++++++++++++++++++++++++ comments.php | 26 +++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 assets/comments.js diff --git a/assets/comments.js b/assets/comments.js new file mode 100644 index 0000000..bd499bd --- /dev/null +++ b/assets/comments.js @@ -0,0 +1,83 @@ +$(document).ready(function () { + + function PhpComment(element) { + this.element = element; + this.init(); + } + + PhpComment.prototype.init = function () { + this.setupVariables(); + this.setupEvents(); + } + + PhpComment.prototype.setupVariables = function () { + this.commentForm = this.element.find(".comment-form"); + this.titleField = this.element.find("#comment_title"); + this.bodyField = this.element.find("#comment_body"); + } + + PhpComment.prototype.setupEvents = function () { + var phpComment = this, + newMedia; + + $.ajax({ + url: '/media_template.php', + method: 'GET', + dataType: 'html', + success: function (data) { + newMedia = data; + } + }); + + phpComment.commentForm.on("submit", function (e) { + e.preventDefault(); + var parentId = 0, + title = phpComment.titleField.val(), + body = phpComment.bodyField.val(); + + if(phpComment.commentForm.parents(".media").length > 0){ + parentId = phpComment.commentForm.closest(".media").attr("data-Id"); + } + + $.ajax({ + url: phpComment.commentForm.attr("action"), + method: 'POST', + dataType: 'json', + data: {title: title, body: body, parentId: parentId}, + success: function (data) { + if(!data.created){ + alert("Couldn't create comment"); + return; + } + + newMedia = newMedia.replace("{{id}}", data.id); + newMedia = newMedia.replace("{{title}}", title); + newMedia = newMedia.replace("{{body}}", body); + newMedia = newMedia.replace("{{nested}}", ''); + phpComment.commentForm.before(newMedia); + phpComment.titleField.val(""); + phpComment.bodyField.val(""); + } + }); + }); + + $(document).on("click", ".comment-add-new", function (e) { + e.preventDefault(); + var media = $(this).closest(".comments"); + media.find(">.comment-body>.comment-text").after(phpComment.commentForm); + }); + $(document).on("click", ".comment-add-reply", function (e) { + e.preventDefault(); + var media = $(this).closest(".comment"); + media.find(">.comment-body>.comment-text").after(phpComment.commentForm); + }); + } + + $.fn.phpComment = function (options) { + new PhpComment(this); + return this; + } + + $(".comments").phpComment(); + +}); diff --git a/comments.php b/comments.php index 4771e57..832e6a9 100644 --- a/comments.php +++ b/comments.php @@ -96,6 +96,7 @@ class CommentsPlugin extends Plugin if ($this->enable) { $this->enable([ + 'onPageInitialized' => ['onPageInitialized', 0], 'onFormProcessed' => ['onFormProcessed', 0], 'onFormPageHeaderProcessed' => ['onFormPageHeaderProcessed', 0], 'onTwigSiteVariables' => ['onTwigSiteVariables', 0] @@ -150,6 +151,31 @@ class CommentsPlugin extends Plugin } } + /** + * Handle ajax call. + */ + public function onPageInitialized() + { + // initialize with page settings (post-cache) +// if (!$this->isAdmin() && isset($this->grav['page']->header()->{'star-ratings'})) { +// // if not in admin merge potential page-level configs +// $this->config->set('plugins.star-ratings', $this->mergeConfig($page)); +// } + $this->callback = 'nested-comments'; +// $this->callback = $this->config->get('plugins.star-ratings.callback'); +// $this->total_stars = $this->config->get('plugins.star-ratings.total_stars'); +// $this->only_full_stars = $this->config->get('plugins.star-ratings.only_full_stars'); + + // Process vote if required + if ($this->callback === $this->grav['uri']->path()) { + // try to add the vote + $result = $this->addVote(); + echo json_encode(['status' => $result[0], 'message' => $result[1], 'data' => ['score' => $result[2][0], 'count' => $result[2][1]]]); + exit(); + } + } + + /** * Handle form processing instructions. * From de3a683cbe162aea20e542b4ff62d801b8f4f671 Mon Sep 17 00:00:00 2001 From: Thorsten Witteler Date: Fri, 20 Oct 2017 15:08:26 +0200 Subject: [PATCH 07/16] prepare ajax --- assets/comments.js | 9 ++-- comments.php | 50 ++++++++++++++++++++-- templates/partials/comments.form.html.twig | 2 +- templates/partials/comments.html.twig | 3 +- 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/assets/comments.js b/assets/comments.js index bd499bd..ab811ff 100644 --- a/assets/comments.js +++ b/assets/comments.js @@ -11,7 +11,7 @@ $(document).ready(function () { } PhpComment.prototype.setupVariables = function () { - this.commentForm = this.element.find(".comment-form"); + this.commentForm = this.element.find(".comments-form"); this.titleField = this.element.find("#comment_title"); this.bodyField = this.element.find("#comment_body"); } @@ -35,8 +35,8 @@ $(document).ready(function () { title = phpComment.titleField.val(), body = phpComment.bodyField.val(); - if(phpComment.commentForm.parents(".media").length > 0){ - parentId = phpComment.commentForm.closest(".media").attr("data-Id"); + if(phpComment.commentForm.parents(".comment").length > 0){ + parentId = phpComment.commentForm.closest(".comment").attr("data-Id"); } $.ajax({ @@ -63,8 +63,7 @@ $(document).ready(function () { $(document).on("click", ".comment-add-new", function (e) { e.preventDefault(); - var media = $(this).closest(".comments"); - media.find(">.comment-body>.comment-text").after(phpComment.commentForm); + $(this).find(".comments").before(phpComment.commentForm); }); $(document).on("click", ".comment-add-reply", function (e) { e.preventDefault(); diff --git a/comments.php b/comments.php index 832e6a9..a6fc7ec 100644 --- a/comments.php +++ b/comments.php @@ -56,6 +56,13 @@ class CommentsPlugin extends Plugin public function onTwigSiteVariables() { $this->grav['twig']->enable_comments_plugin = $this->enable; $this->grav['twig']->comments = $this->fetchComments(); + 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'); } /** @@ -166,15 +173,52 @@ class CommentsPlugin extends Plugin // $this->total_stars = $this->config->get('plugins.star-ratings.total_stars'); // $this->only_full_stars = $this->config->get('plugins.star-ratings.only_full_stars'); - // Process vote if required + // Process comment if required if ($this->callback === $this->grav['uri']->path()) { - // try to add the vote - $result = $this->addVote(); + // try to add the comment + $result = $this->addComment(); echo json_encode(['status' => $result[0], 'message' => $result[1], 'data' => ['score' => $result[2][0], 'count' => $result[2][1]]]); exit(); } } + public function addComment() + { + $nonce = $this->grav['uri']->param('nonce'); + if (!Utils::verifyNonce($nonce, 'comments')) { + return [false, 'Invalid security nonce', [0, 0]]; + } + $language = $this->grav['language']; + // get and filter the data + $parent_id = filter_input(INPUT_POST, 'parent_id', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION); + $email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_STRING); + $text = filter_input(INPUT_POST, 'text', FILTER_SANITIZE_STRING); + $title = filter_input(INPUT_POST, 'title', FILTER_SANITIZE_STRING); + $name = filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING); + //$data = $this->getStars($id); + $data = array( + ['parent_id'] => $parent_id, + ['email'] => $email, + ['text'] => $text, + ['title'] => $title, + ['name'] => $name, + ); + // ensure both values are sent + if (is_null($title) || is_null($text)) { + return [false, 'missing either text or title', [0, 0]]; + //return [false, $language->translate('PLUGIN_COMMENTS.FAIL'), $data]; + } + // sanity checks for parents + if ($parent_id < 0) { + $parent_id = 0; + } elseif ($parent_id > 999 ) { //TODO: Change to 'exists in list of comment ids + $parent_id = 0; + } + //$this->saveVoteData($id, $rating); + //$data = $this->getStars($id); + return [true, $language->translate('PLUGIN_COMMENTS.SUCCESS'), $data]; + } + /** * Handle form processing instructions. diff --git a/templates/partials/comments.form.html.twig b/templates/partials/comments.form.html.twig index dab77dc..78a2a15 100644 --- a/templates/partials/comments.form.html.twig +++ b/templates/partials/comments.form.html.twig @@ -1,5 +1,5 @@

{{'PLUGIN_COMMENTS.ADD_COMMENT'|t}}

-
diff --git a/templates/partials/comments.html.twig b/templates/partials/comments.html.twig index a481604..5f11eec 100644 --- a/templates/partials/comments.html.twig +++ b/templates/partials/comments.html.twig @@ -6,6 +6,7 @@ {% if grav.twig.comments|length %}

{{'PLUGIN_COMMENTS.COMMENTS'|t}}

+ {{'PLUGIN_COMMENTS.ADD_NEW'|t}}
{% for comment in grav.twig.comments|array_reverse %}
@@ -17,7 +18,7 @@

{{comment.title}}

- +
{{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}}
From 868d7b1231bdb35240b36425d59d0981c4208d87 Mon Sep 17 00:00:00 2001 From: Thorsten Witteler Date: Mon, 23 Oct 2017 13:37:52 +0200 Subject: [PATCH 08/16] add built in css --- assets/comments.css | 68 +++++++++++++++++++++++++++++++++++++++++++++ comments.yaml | 5 ++-- 2 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 assets/comments.css diff --git a/assets/comments.css b/assets/comments.css new file mode 100644 index 0000000..4105deb --- /dev/null +++ b/assets/comments.css @@ -0,0 +1,68 @@ +/* +=============================================================================================================================== +Comments Plugin Styles +=============================================================================================================================== +*/ + +.comment { + margin-top: 15px; + border-top: gray solid 2px; +} +.comment:first-child { + margin-top: 0; +} +.comment, +.comment-body { + zoom: 1; + overflow: hidden; +} +.comment-body { + width: 10000px; +} +.comment-object { + display: block; +} +.comment-right, +.comment > .pull-right { + padding-left: 10px; +} +.comment-left, +.comment > .pull-left { + padding-right: 10px; +} +.comment-left, +.comment-right, +.comment-body { + display: table-cell; + vertical-align: top; +} +.comment-middle { + vertical-align: middle; +} +.comment-bottom { + vertical-align: bottom; +} +.comment-heading { + margin-top: 0; + margin-bottom: 5px; + border-bottom: gray dashed 1px; +} +.comment-title, .comment-title h4 { + padding: 0px; + margin: 0px; +} +.comment-meta { + display: inline; + font-size: small; +} +.comment-reply { + display: inline; + float: right; +} +.comment-text { + clear: both; +} +.comment-list { + padding-left: 0; + list-style: none; +} diff --git a/comments.yaml b/comments.yaml index 5c09966..3c7a312 100644 --- a/comments.yaml +++ b/comments.yaml @@ -1,13 +1,12 @@ enabled: true - +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: From cace16f6060263a2649d8c55c7dd51b3d5fb2a6d Mon Sep 17 00:00:00 2001 From: Thorsten Witteler Date: Mon, 23 Oct 2017 13:39:31 +0200 Subject: [PATCH 09/16] change nonce action from "form" to "comments" --- templates/partials/comments.form.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/partials/comments.form.html.twig b/templates/partials/comments.form.html.twig index 78a2a15..5be563e 100644 --- a/templates/partials/comments.form.html.twig +++ b/templates/partials/comments.form.html.twig @@ -32,7 +32,7 @@ {% endfor %}
- {{ nonce_field('form', 'form-nonce')|raw }} + {{ nonce_field('comments', 'form-nonce')|raw }}
{{ form.message }}
From d70f10977613f06616c4ee8506308290d2d932f0 Mon Sep 17 00:00:00 2001 From: Thorsten Witteler Date: Mon, 23 Oct 2017 13:39:49 +0200 Subject: [PATCH 10/16] validate ajax input --- assets/comments.js | 210 ++++++++++++++++++++++++++++----------------- comments.php | 133 ++++++++++++++++++++++------ 2 files changed, 234 insertions(+), 109 deletions(-) diff --git a/assets/comments.js b/assets/comments.js index ab811ff..0964879 100644 --- a/assets/comments.js +++ b/assets/comments.js @@ -1,82 +1,130 @@ -$(document).ready(function () { - - function PhpComment(element) { - this.element = element; - this.init(); - } - - PhpComment.prototype.init = function () { - this.setupVariables(); - this.setupEvents(); - } - - PhpComment.prototype.setupVariables = function () { - this.commentForm = this.element.find(".comments-form"); - this.titleField = this.element.find("#comment_title"); - this.bodyField = this.element.find("#comment_body"); - } - - PhpComment.prototype.setupEvents = function () { - var phpComment = this, - newMedia; - - $.ajax({ - url: '/media_template.php', - method: 'GET', - dataType: 'html', - success: function (data) { - newMedia = data; - } - }); - - phpComment.commentForm.on("submit", function (e) { - e.preventDefault(); - var parentId = 0, - title = phpComment.titleField.val(), - body = phpComment.bodyField.val(); - - if(phpComment.commentForm.parents(".comment").length > 0){ - parentId = phpComment.commentForm.closest(".comment").attr("data-Id"); - } - - $.ajax({ - url: phpComment.commentForm.attr("action"), - method: 'POST', - dataType: 'json', - data: {title: title, body: body, parentId: parentId}, - success: function (data) { - if(!data.created){ - alert("Couldn't create comment"); - return; - } - - newMedia = newMedia.replace("{{id}}", data.id); - newMedia = newMedia.replace("{{title}}", title); - newMedia = newMedia.replace("{{body}}", body); - newMedia = newMedia.replace("{{nested}}", ''); - phpComment.commentForm.before(newMedia); - phpComment.titleField.val(""); - phpComment.bodyField.val(""); - } - }); - }); - - $(document).on("click", ".comment-add-new", function (e) { - e.preventDefault(); - $(this).find(".comments").before(phpComment.commentForm); - }); - $(document).on("click", ".comment-add-reply", function (e) { - e.preventDefault(); - var media = $(this).closest(".comment"); - media.find(">.comment-body>.comment-text").after(phpComment.commentForm); - }); - } - - $.fn.phpComment = function (options) { - new PhpComment(this); - return this; - } - - $(".comments").phpComment(); - +jQuery(document).ready(function () { + var commentForm = $(document).find('.comments-form'); + var commentSection = $(document).find('.comments').first(); + var commentAlert = commentForm.closest('.alert'); + //var newMedia; + //hide form, show link + commentForm.hide(); + $(document).find('.comment-add-new').show(); + //get template for inserting new comments + /* +$.ajax({ + url: '/media_template.php', + method: 'GET', + dataType: 'html', + success: function (data) { + newMedia = data; + } +}); + */ + $('body').on('click', '.comment-add-new-sadf', function (e) { + e.preventDefault(); + alert('asdf'); + $('span').stop().css('opacity', 1).text('myName = ' + e.name).fadeIn(30).fadeOut(1000); + }); + //show comment form above comments section (new comment thread) + $('body').on('click', '.comment-add-new', function (e) { + e.preventDefault(); + //commentForm.hide(1000); + //commentSection.before(commentForm); + $(this).before(commentForm); + commentForm.show('slow'); + //$(this).slideUp(); + }); + //show comment form below selected comment (reply to existing comment) + $('body').on('click', '.comment-add-reply', function (e) { + e.preventDefault(); + var media = $(this).closest('.comment'); + commentForm.hide(); + media.find('>.comment-body>.comment-text').after(commentForm); + commentForm.show('slow'); + }); + // Attach a submit handler to the form + $(commentForm).on('submit', function (event) { + event.preventDefault(); + // Get some values from elements on the page: + //var term = $(this).find( "input[name='s']" ).val(); + //var data = $(this).serializeArray(); + var data = $(this).serialize(); + console.log("Form Data (submit)", JSON.parse(JSON.stringify(data))); + //var url = $(this).attr( "action" ); + var url = '/nested-comments'; + var parentId = 0; + if ($(this).parents('.comment').length > 0) { + parentId = $(this).closest('.comment').attr('data-Id'); + } + // Send the data using post + + //var posting = $.post(url, { parentId: parentId, data: data }, null, 'json'); + var posting = $.post(url, data + '&parentID=' + parentId, null, 'json'); + //$.post( "test.php", $( "#testform" ).serialize() ); + // Put the results in a div + posting.done(function (response) { + alert('success'); + console.log("Response Data (done)", JSON.parse(JSON.stringify(response))); + //response = JSON.parse(response); + var message = response.status ? response.message : 'Error: ' + response.message; + commentForm.after(commentAlert); + commentAlert.empty().append(message); + if (!response.status) { + return; + } + if (response.status) { + var newMedia = ` +
+
+ + user icon + +
+
+
+

{{comment.title}}

+ +
{{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}}
+
+
+ {{comment.text}} +
+ {{nested}} +
+
+`; + newMedia = newMedia.replace('{{comment.id}}', response.id); + newMedia = newMedia.replace('{{comment.level|e}}', response.level); + newMedia = newMedia.replace('{{comment.email|trim|lower|md5}}', response.hash); + newMedia = newMedia.replace('{{parent_id}}', response.data.parent_id); + newMedia = newMedia.replace('{{comment.title}}', response.data.title); + newMedia = newMedia.replace('{{comment.text}}', response.data.text); + newMedia = newMedia.replace('{{comment.author}}', response.data.name); + //newMedia = newMedia.replace('{{comment.date|e}}', response.data.name); + if ($( "div[data-Id='" + response.data.parent_id + "']" ).length > 0) { + $( "div[data-Id='" + response.data.parent_id + "']" ).first().after(newMedia); + } else { + $( "div.comments" ).last().prepend(newMedia); + } + //phpComment.commentForm.before(newMedia); + //phpComment.titleField.val(""); + //phpComment.bodyField.val(""); + } + setTimeout(function () { + commentForm.hide(3000); + }, 5000); + }); + posting.fail(function (status, error, title) { + alert('error'); + console.log("Response Data (fail)", JSON.parse(JSON.stringify(status))); + commentForm.after(commentAlert); + commentAlert.empty().append("

TEST

"); + commentAlert.append("

" + status + "

"); + commentAlert.append("

" + error + "

"); + commentAlert.append("

" + title + "

"); + }); + posting.always(function (test) { + //alert("finished, be it successful or not"); + //test = JSON.parse(test); + //test = test.serialize(); + //alert(test); + }); + }); }); diff --git a/comments.php b/comments.php index a6fc7ec..e0b6352 100644 --- a/comments.php +++ b/comments.php @@ -73,9 +73,15 @@ class CommentsPlugin extends Plugin $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; @@ -163,58 +169,126 @@ class CommentsPlugin extends Plugin */ public function onPageInitialized() { + $is_ajax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'; // initialize with page settings (post-cache) // if (!$this->isAdmin() && isset($this->grav['page']->header()->{'star-ratings'})) { // // if not in admin merge potential page-level configs // $this->config->set('plugins.star-ratings', $this->mergeConfig($page)); // } - $this->callback = 'nested-comments'; // $this->callback = $this->config->get('plugins.star-ratings.callback'); // $this->total_stars = $this->config->get('plugins.star-ratings.total_stars'); // $this->only_full_stars = $this->config->get('plugins.star-ratings.only_full_stars'); - + $callback = $this->config->get('plugins.comments.ajax_callback'); // Process comment if required - if ($this->callback === $this->grav['uri']->path()) { + if ($is_ajax || $callback === $this->grav['uri']->path()) { // try to add the comment $result = $this->addComment(); - echo json_encode(['status' => $result[0], 'message' => $result[1], 'data' => ['score' => $result[2][0], 'count' => $result[2][1]]]); - exit(); + echo json_encode([ + 'status' => $result[0], + 'message' => $result[1], + 'data' => $result[2], +// 'data' => [ +// 'score' => $result[2][0], +// 'count' => $result[2][1] +// ] + ]); + exit(); //prevents the page frontend from beeing displayed. } } public function addComment() { - $nonce = $this->grav['uri']->param('nonce'); - if (!Utils::verifyNonce($nonce, 'comments')) { - return [false, 'Invalid security nonce', [0, 0]]; - } - $language = $this->grav['language']; + if (!$_SERVER["REQUEST_METHOD"] == "POST") { + // Not a POST request, set a 403 (forbidden) response code. + http_response_code(403); + return [false, 'There was a problem with your submission, please try again.', [0, 0]]; + } // get and filter the data - $parent_id = filter_input(INPUT_POST, 'parent_id', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION); - $email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_STRING); - $text = filter_input(INPUT_POST, 'text', FILTER_SANITIZE_STRING); - $title = filter_input(INPUT_POST, 'title', FILTER_SANITIZE_STRING); - $name = filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING); - //$data = $this->getStars($id); - $data = array( - ['parent_id'] => $parent_id, - ['email'] => $email, - ['text'] => $text, - ['title'] => $title, - ['name'] => $name, - ); + if (!isset($_POST['data']) || !is_array($_POST['data'])) { + // Set a 400 (bad request) response code and exit. + http_response_code(400); + return [false, 'missing data', [0, 0]]; + } + $input = array(); + $input['parent_id'] = filter_input(INPUT_POST, 'parentID', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION); + $input['name'] = isset($_POST['data']['name']) ? filter_var($_POST['data']['name'], FILTER_SANITIZE_STRING) : null; + $input['email'] = isset($_POST['data']['email']) ? filter_var($_POST['data']['email'], FILTER_SANITIZE_EMAIL) : null; + $input['text'] = isset($_POST['data']['text']) ? filter_var($_POST['data']['text'], FILTER_SANITIZE_STRING) : null; + $input['date'] = isset($_POST['data']['date']) ? filter_var($_POST['data']['date'], FILTER_SANITIZE_STRING) : null; + $input['title'] = isset($_POST['data']['title']) ? filter_var($_POST['data']['title'], FILTER_SANITIZE_STRING) : null; + $input['lang'] = isset($_POST['data']['lang']) ? filter_var($_POST['data']['lang'], FILTER_SANITIZE_STRING) : null; + $input['path'] = isset($_POST['data']['path']) ? filter_var($_POST['data']['path'], FILTER_SANITIZE_STRING) : null; + $input['form-name'] = filter_input(INPUT_POST, 'form-name', FILTER_SANITIZE_STRING); + $input['form-nonce'] = filter_input(INPUT_POST, 'form-nonce', FILTER_SANITIZE_STRING); +/* + foreach ($_POST['data'] as $field) { + if (isset($field['name']) && isset($field['value'])) { + switch ($field['name']) { + case 'data[name]': + $input['name'] = filter_var($field['value'], FILTER_SANITIZE_STRING); + break; + case 'data[email]': + $input['email'] = filter_var($field['value'], FILTER_SANITIZE_EMAIL); + break; + case 'data[text]': + $input['text'] = filter_var($field['value'], FILTER_SANITIZE_STRING); + break; + case 'data[date]': + $input['date'] = filter_var($field['value'], FILTER_SANITIZE_STRING); + break; + case 'data[title]': + $input['title'] = filter_var($field['value'], FILTER_SANITIZE_STRING); + break; + case 'data[lang]': + $input['lang'] = filter_var($field['value'], FILTER_SANITIZE_STRING); + break; + case 'data[path]': + $input['path'] = filter_var($field['value'], FILTER_SANITIZE_STRING); + break; + case '__form-name__': + $input['form-name'] = filter_var($field['value'], FILTER_SANITIZE_STRING); + break; + case 'form-nonce': + $input['form-nonce'] = filter_var($field['value'], FILTER_SANITIZE_STRING); //$this->grav['uri']->param('nonce'); + break; + default: + //ignore unexpected fields. + } + } + } +*/ + if (!Utils::verifyNonce($input['form-nonce'], 'comments')) { + http_response_code(403); + return [false, 'Invalid security nonce', [$_POST, $input['form-nonce']]]; + } // ensure both values are sent - if (is_null($title) || is_null($text)) { + if (is_null($input['title']) || is_null($input['text'])) { + // Set a 400 (bad request) response code and exit. + http_response_code(400); return [false, 'missing either text or title', [0, 0]]; //return [false, $language->translate('PLUGIN_COMMENTS.FAIL'), $data]; } + $language = $this->grav['language']; + //$data = $this->getStars($id); + $data = array( + 'parent_id' => $input['parent_id'], + 'email' => $input['email'], + 'text' => $input['text'], + 'title' => $input['title'], + 'name' => $input['name'], + 'id' => 99, + 'level' => 0, + 'hash' => md5(strtolower(trim($input['email']))), + ); // sanity checks for parents - if ($parent_id < 0) { - $parent_id = 0; - } elseif ($parent_id > 999 ) { //TODO: Change to 'exists in list of comment ids - $parent_id = 0; + if ($data['parent_id'] < 0) { + $data['parent_id'] = 0; + } elseif ($data['parent_id'] > 999 ) { //TODO: Change to 'exists in list of comment ids + $data['parent_id'] = 0; } //$this->saveVoteData($id, $rating); + // Set a 500 (internal server error) response code. +// http_response_code(500); //$data = $this->getStars($id); return [true, $language->translate('PLUGIN_COMMENTS.SUCCESS'), $data]; } @@ -485,6 +559,9 @@ class CommentsPlugin extends Plugin * Return the latest commented pages */ private function setCommentLevels($comments) { + if(!is_array($comments)) { + return $comments; + } $levelsflat = array(); foreach($comments as $key => $comment) { $levelsflat[$comment['id']]['parent'] = $comment['parent']; From 0936de7c1d07e66fe5df6646a90c08d2e76400ca Mon Sep 17 00:00:00 2001 From: Thorsten Witteler Date: Mon, 23 Oct 2017 16:56:13 +0200 Subject: [PATCH 11/16] remove sorting from twig template (breaks nested comments) --- templates/partials/comments.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/partials/comments.html.twig b/templates/partials/comments.html.twig index 5f11eec..1f61461 100644 --- a/templates/partials/comments.html.twig +++ b/templates/partials/comments.html.twig @@ -8,7 +8,7 @@

{{'PLUGIN_COMMENTS.COMMENTS'|t}}

{{'PLUGIN_COMMENTS.ADD_NEW'|t}}
- {% for comment in grav.twig.comments|array_reverse %} + {% for comment in grav.twig.comments %}
From b4ab9a5111661dc0c26cca59dd1facfaa1679874 Mon Sep 17 00:00:00 2001 From: Thorsten Witteler Date: Tue, 24 Oct 2017 02:57:44 +0200 Subject: [PATCH 12/16] finish nested comment implementation --- assets/comments.css | 55 ++++- assets/comments.js | 147 ++++++------ class/Comment.php | 3 +- comments.php | 309 ++++++++++++-------------- languages.yaml | 8 + templates/partials/comments.html.twig | 11 +- 6 files changed, 279 insertions(+), 254 deletions(-) diff --git a/assets/comments.css b/assets/comments.css index 4105deb..ad04314 100644 --- a/assets/comments.css +++ b/assets/comments.css @@ -9,7 +9,7 @@ Comments Plugin Styles border-top: gray solid 2px; } .comment:first-child { - margin-top: 0; + margin-top: 0px; } .comment, .comment-body { @@ -43,7 +43,7 @@ Comments Plugin Styles vertical-align: bottom; } .comment-heading { - margin-top: 0; + margin-top: 0px; margin-bottom: 5px; border-bottom: gray dashed 1px; } @@ -63,6 +63,55 @@ Comments Plugin Styles clear: both; } .comment-list { - padding-left: 0; + padding-left: 0px; list-style: none; } +.comment-flag-new { + background-color: lightcyan; +} +.comment-level-1 { margin-left: 20px; padding-left: 0px; border-left: gray solid 0px; } +.comment-level-2 { margin-left: 40px; padding-left: 0px; border-left: gray solid 0px; } +.comment-level-3 { margin-left: 60px; padding-left: 0px; border-left: gray solid 0px; } +.comment-level-4 { margin-left: 80px; padding-left: 0px; border-left: gray solid 0px; } +.comment-level-5 { margin-left: 100px; padding-left: 0px; border-left: gray solid 0px; } +.row.comments { position: relative; } +.comment-level-1::before { + content: "\f105"; + font-family: FontAwesome; + position: absolute; + left: 0px; + font-size: 3rem; + color: lightgray; +} +.comment-level-2::before { + content: "\f105\f105"; + font-family: FontAwesome; + position: absolute; + left: 0px; + font-size: 3rem; + color: lightgray; +} +.comment-level-3::before { + content: "\f105\f105\f105"; + font-family: FontAwesome; + position: absolute; + left: 0px; + font-size: 3rem; + color: lightgray; +} +.comment-level-4::before { + content: "\f105\f105\f105\f105"; + font-family: FontAwesome; + position: absolute; + left: 0px; + font-size: 3rem; + color: lightgray; +} +.comment-level-5::before { + content: "\f105\f105\f105\f105\f105"; + font-family: FontAwesome; + position: absolute; + left: 0px; + font-size: 3rem; + color: lightgray; +} diff --git a/assets/comments.js b/assets/comments.js index 0964879..3d63de1 100644 --- a/assets/comments.js +++ b/assets/comments.js @@ -1,36 +1,24 @@ +function escapeRegExp(str) { + return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); +} jQuery(document).ready(function () { var commentForm = $(document).find('.comments-form'); var commentSection = $(document).find('.comments').first(); - var commentAlert = commentForm.closest('.alert'); - //var newMedia; + var commentAlert = $(document).find('.alert').first(); + //hide form, show link commentForm.hide(); $(document).find('.comment-add-new').show(); - //get template for inserting new comments - /* -$.ajax({ - url: '/media_template.php', - method: 'GET', - dataType: 'html', - success: function (data) { - newMedia = data; - } -}); - */ - $('body').on('click', '.comment-add-new-sadf', function (e) { - e.preventDefault(); - alert('asdf'); - $('span').stop().css('opacity', 1).text('myName = ' + e.name).fadeIn(30).fadeOut(1000); - }); + //show comment form above comments section (new comment thread) $('body').on('click', '.comment-add-new', function (e) { e.preventDefault(); //commentForm.hide(1000); - //commentSection.before(commentForm); $(this).before(commentForm); commentForm.show('slow'); - //$(this).slideUp(); + commentAlert.slideUp(); }); + //show comment form below selected comment (reply to existing comment) $('body').on('click', '.comment-add-reply', function (e) { e.preventDefault(); @@ -38,93 +26,96 @@ $.ajax({ commentForm.hide(); media.find('>.comment-body>.comment-text').after(commentForm); commentForm.show('slow'); + commentAlert.slideUp(); }); + // Attach a submit handler to the form $(commentForm).on('submit', function (event) { event.preventDefault(); - // Get some values from elements on the page: - //var term = $(this).find( "input[name='s']" ).val(); - //var data = $(this).serializeArray(); + // 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'; +//console.log("Form Data (submit)", JSON.parse(JSON.stringify(data))); + var url = $(this).attr( "action" ); + //var url = '/nested-comments'; var parentId = 0; + var ownLevel = 0; if ($(this).parents('.comment').length > 0) { - parentId = $(this).closest('.comment').attr('data-Id'); + parentId = $(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, { parentId: parentId, data: data }, null, 'json'); var posting = $.post(url, data + '&parentID=' + parentId, null, 'json'); - //$.post( "test.php", $( "#testform" ).serialize() ); - // Put the results in a div + + // 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); - var message = response.status ? response.message : 'Error: ' + response.message; +//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); - commentAlert.empty().append(message); if (!response.status) { - return; + //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) { - var newMedia = ` -
- -
-
-

{{comment.title}}

- -
{{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}}
-
-
- {{comment.text}} -
- {{nested}} -
-
-`; - newMedia = newMedia.replace('{{comment.id}}', response.id); - newMedia = newMedia.replace('{{comment.level|e}}', response.level); - newMedia = newMedia.replace('{{comment.email|trim|lower|md5}}', response.hash); - newMedia = newMedia.replace('{{parent_id}}', response.data.parent_id); - newMedia = newMedia.replace('{{comment.title}}', response.data.title); - newMedia = newMedia.replace('{{comment.text}}', response.data.text); - newMedia = newMedia.replace('{{comment.author}}', response.data.name); - //newMedia = newMedia.replace('{{comment.date|e}}', response.data.name); - if ($( "div[data-Id='" + response.data.parent_id + "']" ).length > 0) { - $( "div[data-Id='" + response.data.parent_id + "']" ).first().after(newMedia); - } else { + commentAlert.css('color', 'green').empty().append(document.createTextNode( response.message )).fadeIn(30); + var newMedia = "
" + + "
" + + "" + + "user icon" + + "" + + "
" + + "
" + + "
" + + "

{{comment.title}}

" + + "" + + "
{{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}}
" + + "
" + + "
" + + "{{comment.text}}" + + "
" + + "{{nested}}" + + "
" + + "
"; + 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_id}}"), 'g'), response.data.parent_id); + 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.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.WRITTEN_ON'|t}}"), 'g'), response.data.WRITTEN_ON); + newMedia = newMedia.replace(new RegExp(escapeRegExp("{{'PLUGIN_COMMENTS.BY'|t}}"), 'g'), response.data.BY); + if ($( "div[data-id='" + response.data.parent_id + "']" ).length > 0) { + $( "div[data-id='" + response.data.parent_id + "']" ).first().after(newMedia); + } else { $( "div.comments" ).last().prepend(newMedia); - } - //phpComment.commentForm.before(newMedia); - //phpComment.titleField.val(""); - //phpComment.bodyField.val(""); + } } setTimeout(function () { - commentForm.hide(3000); + commentForm.hide(2000); + commentAlert.fadeOut(5000); }, 5000); }); posting.fail(function (status, error, title) { - alert('error'); - console.log("Response Data (fail)", JSON.parse(JSON.stringify(status))); +//alert('error'); +//console.log("Response Data (fail)", JSON.parse(JSON.stringify(status))); commentForm.after(commentAlert); commentAlert.empty().append("

TEST

"); commentAlert.append("

" + status + "

"); commentAlert.append("

" + error + "

"); commentAlert.append("

" + title + "

"); }); - posting.always(function (test) { + posting.always(function () { //alert("finished, be it successful or not"); - //test = JSON.parse(test); - //test = test.serialize(); - //alert(test); }); }); }); diff --git a/class/Comment.php b/class/Comment.php index affe1e9..5219f53 100644 --- a/class/Comment.php +++ b/class/Comment.php @@ -28,7 +28,8 @@ class Comment $comments[] = $this->value; foreach($this->children as $child) { - $comments[] = $child->getContent($level + 1); + //$comments[] = $child->getContent($level + 1); //produces nested result array. + $comments = array_merge($comments, $child->getContent($level + 1)); //produces flat result array. } return $comments; } diff --git a/comments.php b/comments.php index e0b6352..ecfbe11 100644 --- a/comments.php +++ b/comments.php @@ -182,7 +182,7 @@ class CommentsPlugin extends Plugin // Process comment if required if ($is_ajax || $callback === $this->grav['uri']->path()) { // try to add the comment - $result = $this->addComment(); + $result = $this->addComment(true); echo json_encode([ 'status' => $result[0], 'message' => $result[1], @@ -196,8 +196,10 @@ class CommentsPlugin extends Plugin } } - public function addComment() + public function addComment($is_ajax = false) { + if($is_ajax) { + $language = $this->grav['language']; if (!$_SERVER["REQUEST_METHOD"] == "POST") { // Not a POST request, set a 403 (forbidden) response code. http_response_code(403); @@ -220,43 +222,6 @@ class CommentsPlugin extends Plugin $input['path'] = isset($_POST['data']['path']) ? filter_var($_POST['data']['path'], FILTER_SANITIZE_STRING) : null; $input['form-name'] = filter_input(INPUT_POST, 'form-name', FILTER_SANITIZE_STRING); $input['form-nonce'] = filter_input(INPUT_POST, 'form-nonce', FILTER_SANITIZE_STRING); -/* - foreach ($_POST['data'] as $field) { - if (isset($field['name']) && isset($field['value'])) { - switch ($field['name']) { - case 'data[name]': - $input['name'] = filter_var($field['value'], FILTER_SANITIZE_STRING); - break; - case 'data[email]': - $input['email'] = filter_var($field['value'], FILTER_SANITIZE_EMAIL); - break; - case 'data[text]': - $input['text'] = filter_var($field['value'], FILTER_SANITIZE_STRING); - break; - case 'data[date]': - $input['date'] = filter_var($field['value'], FILTER_SANITIZE_STRING); - break; - case 'data[title]': - $input['title'] = filter_var($field['value'], FILTER_SANITIZE_STRING); - break; - case 'data[lang]': - $input['lang'] = filter_var($field['value'], FILTER_SANITIZE_STRING); - break; - case 'data[path]': - $input['path'] = filter_var($field['value'], FILTER_SANITIZE_STRING); - break; - case '__form-name__': - $input['form-name'] = filter_var($field['value'], FILTER_SANITIZE_STRING); - break; - case 'form-nonce': - $input['form-nonce'] = filter_var($field['value'], FILTER_SANITIZE_STRING); //$this->grav['uri']->param('nonce'); - break; - default: - //ignore unexpected fields. - } - } - } -*/ if (!Utils::verifyNonce($input['form-nonce'], 'comments')) { http_response_code(403); return [false, 'Invalid security nonce', [$_POST, $input['form-nonce']]]; @@ -268,31 +233,138 @@ class CommentsPlugin extends Plugin return [false, 'missing either text or title', [0, 0]]; //return [false, $language->translate('PLUGIN_COMMENTS.FAIL'), $data]; } - $language = $this->grav['language']; - //$data = $this->getStars($id); - $data = array( - 'parent_id' => $input['parent_id'], - 'email' => $input['email'], - 'text' => $input['text'], - 'title' => $input['title'], - 'name' => $input['name'], - 'id' => 99, - 'level' => 0, - 'hash' => md5(strtolower(trim($input['email']))), - ); // sanity checks for parents - if ($data['parent_id'] < 0) { - $data['parent_id'] = 0; - } elseif ($data['parent_id'] > 999 ) { //TODO: Change to 'exists in list of comment ids - $data['parent_id'] = 0; + if ($input['parent_id'] < 0) { + $input['parent_id'] = 0; + } elseif ($input['parent_id'] > 999 ) { //TODO: Change to 'exists in list of comment ids + $input['parent_id'] = 0; } - //$this->saveVoteData($id, $rating); - // Set a 500 (internal server error) response code. -// http_response_code(500); - //$data = $this->getStars($id); + $lang = $this->grav['language']->getLanguage(); + $path = $this->grav['page']->path(); + $route = $this->grav['page']->route(); + $comment = $this->saveComment($route, $path, $input['parent_id'], $lang, $input['text'], $input['name'], $input['email'], $input['title']); + //$comments = $this->fetchComments(); + $data = array( + 'parent_id' => $comment['parent'], + 'id' => $comment['id'], + 'text' => $comment['text'], + 'title' => $comment['title'], + 'name' => $comment['author'], + 'date' => $comment['date'], + 'level' => 0, + 'hash' => md5(strtolower(trim($comment['email']))), + 'ADD_REPLY' => $language->translate('PLUGIN_COMMENTS.ADD_REPLY'), + 'WRITTEN_ON' => $language->translate('PLUGIN_COMMENTS.WRITTEN_ON'), + 'BY' => $language->translate('PLUGIN_COMMENTS.BY'), + ); return [true, $language->translate('PLUGIN_COMMENTS.SUCCESS'), $data]; + } else { +// Set a 500 (internal server error) response code. +// http_response_code(500); + + } } + /** + * Handle form processing instructions. + * + * @param Event $event + */ + public function saveComment($route, $path, $parent_id, $lang, $text, $name, $email, $title) + { + $date = date('D, d M Y H:i:s', time()); + + /******************************/ + /** store comments with page **/ + /******************************/ + $localfilename = $path . '/comments.yaml'; + $localfile = CompiledYamlFile::instance($localfilename); + if (file_exists($localfilename)) { + $data = $localfile->content(); + $data['autoincrement']++; + } else { + $data = array( + 'autoincrement' => 1, + 'comments' => array() + ); + } + $localid = $data['autoincrement']; + $newComment = [ + 'id' => $data['autoincrement'], + 'parent' => $parent_id, + 'lang' => $lang, + 'title' => $title, + 'text' => $text, + 'date' => $date, + 'author' => $name, + 'email' => $email + ]; + $data['comments'][] = $newComment; + $localfile->save($data); + /**********************************/ + /** store comments in index file **/ + /**********************************/ + $indexfilename = DATA_DIR . 'comments/index.yaml'; + $indexfile = CompiledYamlFile::instance($indexfilename); + if (file_exists($indexfilename)) { + $dataIndex = $indexfile->content(); + } else { + $dataIndex = array( + 'comments' => array() + ); + } + $dataIndex['comments'][] = [ + 'page' => $route, + 'id' => $localid, + 'parent' => $parent_id, + 'lang' => $lang, + 'title' => $title, + 'text' => $text, + 'date' => $date, + 'author' => $name, + 'email' => $email + ]; + $indexfile->save($dataIndex); + + /**************************************/ + /** store comments in old data files **/ + /** TODO: remove as soon as admin **/ + /** panel uses new index file **/ + /**************************************/ + $filename = DATA_DIR . 'comments'; + $filename .= ($lang ? '/' . $lang : ''); + $filename .= $path . '.yaml'; + $file = CompiledYamlFile::instance($filename); + + if (file_exists($filename)) { + $dataLegacy = $file->content(); + + $dataLegacy['comments'][] = [ + 'text' => $text, + 'date' => $date, + 'author' => $name, + 'email' => $email + ]; + } else { + $dataLegacy = array( + 'title' => $title, + 'lang' => $lang, + 'comments' => array([ + 'text' => $text, + 'date' => $date, + 'author' => $name, + 'email' => $email + ]) + ); + } + + $file->save($dataLegacy); + + //clear cache + $this->grav['cache']->delete($this->comments_cache_id); + + return $newComment; + } /** * Handle form processing instructions. @@ -319,6 +391,7 @@ class CommentsPlugin extends Plugin $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); + $parent_id = 0; if (isset($this->grav['user'])) { $user = $this->grav['user']; @@ -329,117 +402,12 @@ class CommentsPlugin extends Plugin } /** @var Language $language */ - $language = $this->grav['language']; - $lang = $language->getLanguage(); + $lang = $this->grav['language']->getLanguage(); + + $path = $this->grav['page']->path(); + $route = $this->grav['page']->route(); - /******************************/ - /** store comments with page **/ - /******************************/ - $page = $this->grav['page']; - $localfilename = $page->path() . '/comments.yaml'; - $localfile = CompiledYamlFile::instance($localfilename); - if (file_exists($localfilename)) { - $data = $localfile->content(); - $data['autoincrement']++; - $data['comments'][] = [ - 'id' => $data['autoincrement'], - 'parent' => 0, - 'lang' => $lang, - 'title' => $title, - 'text' => $text, - 'date' => date('D, d M Y H:i:s', time()), - 'author' => $name, - 'email' => $email - ]; - } else { - $data = array( - 'autoincrement' => 1, - 'comments' => array([ - 'id' => 1, - 'parent' => 0, - 'lang' => $lang, - 'title' => $title, - 'text' => $text, - 'date' => date('D, d M Y H:i:s', time()), - 'author' => $name, - 'email' => $email - ]) - ); - } - $localfile->save($data); - $localid = $data['autoincrement']; - $data = null; - /**********************************/ - /** store comments in index file **/ - /**********************************/ - $indexfilename = DATA_DIR . 'comments/index.yaml'; - $indexfile = CompiledYamlFile::instance($indexfilename); - if (file_exists($indexfilename)) { - $data = $indexfile->content(); - $data['comments'][] = [ - 'page' => $page->route(), - 'id' => $localid, - 'parent' => 0, - 'lang' => $lang, - 'title' => $title, - 'text' => $text, - 'date' => date('D, d M Y H:i:s', time()), - 'author' => $name, - 'email' => $email - ]; - } else { - $data = array( - 'comments' => array([ - 'page' => $page->route(), - 'id' => $localid, - 'parent' => 0, - 'lang' => $lang, - 'title' => $title, - 'text' => $text, - 'date' => date('D, d M Y H:i:s', time()), - 'author' => $name, - 'email' => $email - ]) - ); - } - $indexfile->save($data); - $data = null; - /**************************************/ - /** store comments in old data files **/ - /** TODO: remove as soon as admin **/ - /** panel uses new index file **/ - /**************************************/ - $filename = DATA_DIR . 'comments'; - $filename .= ($lang ? '/' . $lang : ''); - $filename .= $path . '.yaml'; - $file = CompiledYamlFile::instance($filename); - - if (file_exists($filename)) { - $data = $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($data); - - //clear cache - $this->grav['cache']->delete($this->comments_cache_id); + $this->saveComment($route, $path, $parent_id, $lang, $text, $name, $email, $title); break; } @@ -580,6 +548,9 @@ class CommentsPlugin extends Plugin $levelsflat[$parent_id]['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) { diff --git a/languages.yaml b/languages.yaml index 812f22d..65b8db7 100644 --- a/languages.yaml +++ b/languages.yaml @@ -1,6 +1,10 @@ de: PLUGIN_COMMENTS: + ADD_NEW: Kommentar hinzufügen + ADD_REPLY: Antworten ADD_COMMENT: Kommentar hinzufügen + DELETE_COMMENT: Kommentar löschen + SUCCESS: Der Kommentar wurde erfolgreich gespeichert. COMMENTS: Kommentare EMAIL_NOT_CONFIGURED: Email nicht konfiguriert NEW_COMMENT_EMAIL_SUBJECT: 'Neuer Kommentar für %1$s' @@ -22,7 +26,11 @@ de: en: PLUGIN_COMMENTS: + ADD_NEW: Add a comment + ADD_REPLY: Reply ADD_COMMENT: Add a comment + DELETE_COMMENT: Delete comment + SUCCESS: Comment has been saved successfully. COMMENTS: Comments EMAIL_NOT_CONFIGURED: Email not configured NEW_COMMENT_EMAIL_SUBJECT: 'New comment on %1$s' diff --git a/templates/partials/comments.html.twig b/templates/partials/comments.html.twig index 1f61461..e50ead3 100644 --- a/templates/partials/comments.html.twig +++ b/templates/partials/comments.html.twig @@ -6,10 +6,10 @@ {% if grav.twig.comments|length %}

{{'PLUGIN_COMMENTS.COMMENTS'|t}}

- {{'PLUGIN_COMMENTS.ADD_NEW'|t}} + {{'PLUGIN_COMMENTS.ADD_NEW'|t}}
{% for comment in grav.twig.comments %} -
+
user icon @@ -18,7 +18,12 @@

{{comment.title}}

-
+
+ {{'PLUGIN_COMMENTS.ADD_REPLY'|t}} + {% if grav.user.access.admin.super %} + {{'PLUGIN_COMMENTS.DELETE'|t}} + {% endif %} +
{{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}}
From 905c04937c29119efc0951c6dce06ce02926f374 Mon Sep 17 00:00:00 2001 From: Thorsten Witteler Date: Wed, 25 Oct 2017 02:58:07 +0200 Subject: [PATCH 13/16] + ajax-delete comments via front end (if logged in with super admin priviledges) --- assets/comments.js | 92 ++++++++-- comments.php | 201 +++++++++++++++++++-- languages.yaml | 12 +- templates/partials/comments.form.html.twig | 5 +- templates/partials/comments.html.twig | 11 +- 5 files changed, 278 insertions(+), 43 deletions(-) diff --git a/assets/comments.js b/assets/comments.js index 3d63de1..c366189 100644 --- a/assets/comments.js +++ b/assets/comments.js @@ -2,9 +2,9 @@ function escapeRegExp(str) { return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); } jQuery(document).ready(function () { - var commentForm = $(document).find('.comments-form'); - var commentSection = $(document).find('.comments').first(); - var commentAlert = $(document).find('.alert').first(); + 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(); @@ -13,20 +13,78 @@ jQuery(document).ready(function () { //show comment form above comments section (new comment thread) $('body').on('click', '.comment-add-new', function (e) { e.preventDefault(); - //commentForm.hide(1000); + 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.slideUp(); + commentAlert.empty().slideUp(); }); //show comment form below selected comment (reply to existing comment) $('body').on('click', '.comment-add-reply', function (e) { e.preventDefault(); - var media = $(this).closest('.comment'); + 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(); - media.find('>.comment-body>.comment-text').after(commentForm); + comment.find('.comment-body').last().append(commentForm); commentForm.show('slow'); - commentAlert.slideUp(); + 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("

Error:

"); + commentAlert.append("

" + JSON.stringify(status) + "

"); + commentAlert.append("

" + JSON.stringify(error) + "

"); + commentAlert.append("

" + JSON.stringify(title) + "

"); + }); + posting.always(function () { + //alert("finished, be it successful or not"); + }); }); // Attach a submit handler to the form @@ -65,14 +123,12 @@ jQuery(document).ready(function () { commentAlert.css('color', 'green').empty().append(document.createTextNode( response.message )).fadeIn(30); var newMedia = "
" + "
" + - "" + - "user icon" + - "" + + "user icon" + "
" + "
" + "
" + "

{{comment.title}}

" + - "" + + "" + "
{{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}}
" + "
" + "
" + @@ -92,6 +148,7 @@ jQuery(document).ready(function () { 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); newMedia = newMedia.replace(new RegExp(escapeRegExp("{{'PLUGIN_COMMENTS.BY'|t}}"), 'g'), response.data.BY); if ($( "div[data-id='" + response.data.parent_id + "']" ).length > 0) { @@ -101,7 +158,7 @@ jQuery(document).ready(function () { } } setTimeout(function () { - commentForm.hide(2000); + commentForm.slideUp(); commentAlert.fadeOut(5000); }, 5000); }); @@ -109,10 +166,11 @@ jQuery(document).ready(function () { //alert('error'); //console.log("Response Data (fail)", JSON.parse(JSON.stringify(status))); commentForm.after(commentAlert); - commentAlert.empty().append("

TEST

"); - commentAlert.append("

" + status + "

"); - commentAlert.append("

" + error + "

"); - commentAlert.append("

" + title + "

"); + commentAlert.show(); + commentAlert.empty().append("

Error:

"); + commentAlert.append("

" + JSON.stringify(status) + "

"); + commentAlert.append("

" + JSON.stringify(error) + "

"); + commentAlert.append("

" + JSON.stringify(title) + "

"); }); posting.always(function () { //alert("finished, be it successful or not"); diff --git a/comments.php b/comments.php index ecfbe11..5e9a67e 100644 --- a/comments.php +++ b/comments.php @@ -180,22 +180,71 @@ class CommentsPlugin extends Plugin // $this->only_full_stars = $this->config->get('plugins.star-ratings.only_full_stars'); $callback = $this->config->get('plugins.comments.ajax_callback'); // Process comment if required - if ($is_ajax || $callback === $this->grav['uri']->path()) { - // try to add the comment - $result = $this->addComment(true); - echo json_encode([ - 'status' => $result[0], - 'message' => $result[1], - 'data' => $result[2], -// 'data' => [ -// 'score' => $result[2][0], -// 'count' => $result[2][1] -// ] - ]); + 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. } } + public 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]]; + } + } + public function addComment($is_ajax = false) { if($is_ajax) { @@ -254,6 +303,7 @@ class CommentsPlugin extends Plugin 'level' => 0, 'hash' => md5(strtolower(trim($comment['email']))), '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'), ); @@ -265,6 +315,123 @@ class CommentsPlugin extends Plugin } } + /** + * Handle form processing instructions. + * + * @param Event $event + */ + public function removeComment($route, $path, $id, $lang) + { + $entry_removed = false; + $message = ''; + $date = time();//date('D, d M Y H:i:s', time()); + + /******************************/ + /** store comments with page **/ + /******************************/ + $localfilename = $path . '/comments.yaml'; + $localfile = CompiledYamlFile::instance($localfilename); + if (file_exists($localfilename)) { + $data = $localfile->content(); + if(isset($data['comments']) && is_array($data['comments'])) { + foreach($data['comments'] as $key => $comment) { + if(!empty($comment['parent_id']) && $comment['parent_id'] == $id) { + //hit an existing comment that is a reply to comment selected for deletion. + //deletion of "parent" comment not allowed to preserve integrity of nested comments. + //TODO: Alternatively allow it to mark parent comments as deleted + // and make sure (via Comment class / setCommentLevels) that children are + // filtered out from fetch regardless of their own deletion state. + $data['comments'][$key] = array_merge(array('deleted' => ''), $comment); + //set date after merge + //reason: could be possible that "deleted" already exists (e.g. false or '') in $comment which would overwrite the first (newly added) occurence + $data['comments'][$key]['deleted'] = $date; + //no need to look further as ids are supposed to be unique. + $localfile->save($data); + $entry_removed = false; + $reply_id = empty($comment['id']) ? '' : $comment['id']; + $message = "Found active reply ($reply_id) for selected comment ($id)."; + return [$entry_removed, $message]; + break; + } + } + foreach($data['comments'] as $key => $comment) { + if(!empty($comment['id']) && $comment['id'] == $id) { + //add deleted as first item in array (better readability in file) + $data['comments'][$key] = array_merge(array('deleted' => ''), $comment); + //set date after merge + //reason: could be possible that "deleted" already exists (e.g. false or '') in $comment which would overwrite the first (newly added) occurence + $data['comments'][$key]['deleted'] = $date; + //no need to look further as ids are supposed to be unique. + $localfile->save($data); + $entry_removed = true; + $message = "Deleted comment ($id) via path ($path)"; + break; + } + } + } + } else { + //nothing + } + /**********************************/ + /** store comments in index file **/ + /**********************************/ + $indexfilename = DATA_DIR . 'comments/index.yaml'; + $indexfile = CompiledYamlFile::instance($indexfilename); + if (file_exists($indexfilename)) { + $dataIndex = $indexfile->content(); + if(isset($dataIndex['comments']) && is_array($dataIndex['comments'])) { + foreach($dataIndex['comments'] as $key => $comment) { + if(!empty($comment['page']) && !empty($comment['id']) && $comment['page'] == $route && $comment['id'] == $id) { + //add deleted as first item in array (better readability in file) + $dataIndex['comments'][$key] = array_merge(array('deleted' => ''), $comment); + //set date after merge + //reason: could be possible that "deleted" already exists (e.g. false or '') in $comment which would overwrite the first (newly added) occurence + $dataIndex['comments'][$key]['deleted'] = $date; + //no need to look further as ids are supposed to be unique. + $indexfile->save($dataIndex); + break; + } + } + } + } else { + //nothing + } + /**************************************/ + /** store comments in old data files **/ + /** TODO: remove as soon as admin **/ + /** panel uses new index file **/ + /**************************************/ + $filename = DATA_DIR . 'comments'; + $filename .= ($lang ? '/' . $lang : ''); + $filename .= $path . '.yaml'; + $file = CompiledYamlFile::instance($filename); + + if (file_exists($filename)) { + $dataLegacy = $file->content(); + if(isset($dataLegacy['comments']) && is_array($dataLegacy['comments'])) { + foreach($dataLegacy['comments'] as $key => $comment) { + if(!empty($comment['id']) && $comment['id'] == $id) { + //add deleted as first item in array (better readability in file) + $dataLegacy['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 + $dataLegacy['comments'][$key]['deleted'] = $date; + //no need to look further as ids are supposed to be unique. + $file->save($dataLegacy); + break; + } + } + } + } else { + //nothing + } + + //clear cache + $this->grav['cache']->delete($this->comments_cache_id); + + return [$entry_removed, $message]; + } + /** * Handle form processing instructions. * @@ -532,8 +699,14 @@ class CommentsPlugin extends Plugin } $levelsflat = array(); foreach($comments as $key => $comment) { - $levelsflat[$comment['id']]['parent'] = $comment['parent']; - $levelsflat[$comment['id']]['class'] = new Comment($comment['id'], $comments[$key]); + if(!empty($comment['deleted'])) { + //if field "deleted" exists and is filled with a true value then ignore the comment completely. + //TODO: This only works on this position as long as it is forbidden to delete comments that have active replies (children). + // Otherwise implement that children get the deleted flag recursively or are ignored via Comment class. + } else { + $levelsflat[$comment['id']]['parent'] = $comment['parent']; + $levelsflat[$comment['id']]['class'] = new Comment($comment['id'], $comments[$key]); + } } //get starting points (entries without valid parent = root element) $leveltree = array(); diff --git a/languages.yaml b/languages.yaml index 65b8db7..efc3d5a 100644 --- a/languages.yaml +++ b/languages.yaml @@ -1,10 +1,12 @@ de: PLUGIN_COMMENTS: ADD_NEW: Kommentar hinzufügen - ADD_REPLY: Antworten + ADD_REPLY: Auf Kommentar antworten ADD_COMMENT: Kommentar hinzufügen DELETE_COMMENT: Kommentar löschen - SUCCESS: Der Kommentar wurde erfolgreich gespeichert. + REPLY: Antworten + DELETE: Löschen + SUCCESS: "Der Kommentar wurde erfolgreich gespeichert." COMMENTS: Kommentare EMAIL_NOT_CONFIGURED: Email nicht konfiguriert NEW_COMMENT_EMAIL_SUBJECT: 'Neuer Kommentar für %1$s' @@ -27,10 +29,12 @@ de: en: PLUGIN_COMMENTS: ADD_NEW: Add a comment - ADD_REPLY: Reply + ADD_REPLY: Reply to comment ADD_COMMENT: Add a comment DELETE_COMMENT: Delete comment - SUCCESS: Comment has been saved successfully. + REPLY: Reply + DELETE: Delete + SUCCESS: "Comment has been saved successfully." COMMENTS: Comments EMAIL_NOT_CONFIGURED: Email not configured NEW_COMMENT_EMAIL_SUBJECT: 'New comment on %1$s' diff --git a/templates/partials/comments.form.html.twig b/templates/partials/comments.form.html.twig index 5be563e..be8d586 100644 --- a/templates/partials/comments.form.html.twig +++ b/templates/partials/comments.form.html.twig @@ -1,5 +1,4 @@ -

{{'PLUGIN_COMMENTS.ADD_COMMENT'|t}}

-
@@ -35,4 +34,4 @@ {{ nonce_field('comments', 'form-nonce')|raw }}
-
{{ form.message }}
+
{{ form.message }}
diff --git a/templates/partials/comments.html.twig b/templates/partials/comments.html.twig index e50ead3..9728aa0 100644 --- a/templates/partials/comments.html.twig +++ b/templates/partials/comments.html.twig @@ -1,27 +1,27 @@ {% if grav.twig.enable_comments_plugin %} {% set scope = scope ?: 'data.' %} +
+

{{'PLUGIN_COMMENTS.COMMENTS'|t}}

+ {% include 'partials/comments.form.html.twig' %} {% if grav.twig.comments|length %} -

{{'PLUGIN_COMMENTS.COMMENTS'|t}}

{{'PLUGIN_COMMENTS.ADD_NEW'|t}}
{% for comment in grav.twig.comments %}
- user icon -

{{comment.title}}

{{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}}
@@ -36,6 +36,7 @@
{% endif %} +
{% endif %} From 9179453c85acb88d223ba71db51e4ccdef1a17ec Mon Sep 17 00:00:00 2001 From: codeshell Date: Sun, 29 Oct 2017 01:49:09 +0200 Subject: [PATCH 14/16] fix absolute class path add recent comments widget as twig function add save authentication and admin status with comments --- comments.php | 327 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 225 insertions(+), 102 deletions(-) diff --git a/comments.php b/comments.php index 5e9a67e..0d9813d 100644 --- a/comments.php +++ b/comments.php @@ -1,23 +1,15 @@ grav['twig']->enable_comments_plugin = $this->enable; $this->grav['twig']->comments = $this->fetchComments(); + $this->grav['twig']->recentComments = $this->getRecentComments(); if ($this->config->get('plugins.comments.built_in_css')) { $this->grav['assets'] ->addCss('plugin://comments/assets/comments.css'); @@ -99,7 +92,7 @@ class CommentsPlugin extends Plugin /** * Frontend side initialization */ - public function initializeFrontend() + private function initializeFrontend() { $this->calculateEnable(); @@ -126,7 +119,7 @@ class CommentsPlugin extends Plugin /** * Admin side initialization */ - public function initializeAdmin() + private function initializeAdmin() { /** @var Uri $uri */ $uri = $this->grav['uri']; @@ -134,6 +127,9 @@ class CommentsPlugin extends Plugin $this->enable([ 'onTwigTemplatePaths' => ['onTwigAdminTemplatePaths', 0], 'onAdminMenu' => ['onAdminMenu', 0], + 'onAdminTaskExecute' => ['onAdminTaskExecute', 0], + 'onAdminAfterSave' => ['onAdminAfterSave', 0], + 'onAdminAfterDelete' => ['onAdminAfterDelete', 0], 'onDataTypeExcludeFromDataManagerPluginHook' => ['onDataTypeExcludeFromDataManagerPluginHook', 0], ]); @@ -157,6 +153,22 @@ class CommentsPlugin extends Plugin */ public function onPluginsInitialized() { + if ('/recent' === $this->grav['uri']->path()) { + //TODO TEST + echo PAGES_DIR; + + echo "
"; + + $test = $this->getRecentComments(25); + var_dump($test[0]); + echo '


'; + var_dump($test[1]); + echo '


'; + var_dump($test[2]); + echo '


'; + exit(); + } + if ($this->isAdmin()) { $this->initializeAdmin(); } else { @@ -170,15 +182,7 @@ class CommentsPlugin extends Plugin public function onPageInitialized() { $is_ajax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'; - // initialize with page settings (post-cache) -// if (!$this->isAdmin() && isset($this->grav['page']->header()->{'star-ratings'})) { -// // if not in admin merge potential page-level configs -// $this->config->set('plugins.star-ratings', $this->mergeConfig($page)); -// } -// $this->callback = $this->config->get('plugins.star-ratings.callback'); -// $this->total_stars = $this->config->get('plugins.star-ratings.total_stars'); -// $this->only_full_stars = $this->config->get('plugins.star-ratings.only_full_stars'); - $callback = $this->config->get('plugins.comments.ajax_callback'); + //$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); @@ -187,7 +191,7 @@ class CommentsPlugin extends Plugin case '': case null: // try to add the comment - $result = $this->addComment(true); + $result = $this->addCommentAjax(true); echo json_encode([ 'status' => $result[0], 'message' => $result[1], @@ -214,7 +218,12 @@ class CommentsPlugin extends Plugin } } - public function deleteComment() + /** + * Validate ajax input before deleting comment + * + * @return boolean[]|string[]|array[][] + */ + private function deleteComment() { $language = $this->grav['language']; if (!$this->grav['user']->authorize('admin.super')) { @@ -237,7 +246,7 @@ class CommentsPlugin extends Plugin $path = $this->grav['page']->path(); $route = $this->grav['page']->route(); $data = $this->removeComment($route, $path, $id, $lang); - if ($data[0]) { + if ($data[0]) { return [true, $language->translate('PLUGIN_COMMENTS.DELETE_SUCCESS'), $data[1]]; } else { http_response_code(403); //forbidden @@ -245,9 +254,13 @@ class CommentsPlugin extends Plugin } } - public function addComment($is_ajax = false) + /** + * Validate ajax input before adding comment + * + * @return boolean[]|string[]|array[][] + */ + private function addCommentAjax() { - if($is_ajax) { $language = $this->grav['language']; if (!$_SERVER["REQUEST_METHOD"] == "POST") { // Not a POST request, set a 403 (forbidden) response code. @@ -273,7 +286,7 @@ class CommentsPlugin extends Plugin $input['form-nonce'] = filter_input(INPUT_POST, 'form-nonce', FILTER_SANITIZE_STRING); if (!Utils::verifyNonce($input['form-nonce'], 'comments')) { http_response_code(403); - return [false, 'Invalid security nonce', [$_POST, $input['form-nonce']]]; + return [false, 'Invalid security nonce', [0, $input['form-nonce']]]; } // ensure both values are sent if (is_null($input['title']) || is_null($input['text'])) { @@ -291,7 +304,9 @@ class CommentsPlugin extends Plugin $lang = $this->grav['language']->getLanguage(); $path = $this->grav['page']->path(); $route = $this->grav['page']->route(); - $comment = $this->saveComment($route, $path, $input['parent_id'], $lang, $input['text'], $input['name'], $input['email'], $input['title']); + $user = $this->grav['user']->authenticated ? $this->grav['user']->username : ''; + $isAdmin = $this->grav['user']->authorize('admin.login'); + $comment = $this->saveComment($route, $path, $input['parent_id'], $lang, $input['text'], $input['name'], $input['email'], $input['title'], $user, $isAdmin); //$comments = $this->fetchComments(); $data = array( 'parent_id' => $comment['parent'], @@ -300,19 +315,15 @@ class CommentsPlugin extends Plugin 'title' => $comment['title'], 'name' => $comment['author'], 'date' => $comment['date'], - 'level' => 0, 'hash' => md5(strtolower(trim($comment['email']))), + '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]; - } else { -// Set a 500 (internal server error) response code. -// http_response_code(500); - - } } /** @@ -320,7 +331,7 @@ class CommentsPlugin extends Plugin * * @param Event $event */ - public function removeComment($route, $path, $id, $lang) + private function removeComment($route, $path, $id, $lang) { $entry_removed = false; $message = ''; @@ -396,36 +407,6 @@ class CommentsPlugin extends Plugin } else { //nothing } - /**************************************/ - /** store comments in old data files **/ - /** TODO: remove as soon as admin **/ - /** panel uses new index file **/ - /**************************************/ - $filename = DATA_DIR . 'comments'; - $filename .= ($lang ? '/' . $lang : ''); - $filename .= $path . '.yaml'; - $file = CompiledYamlFile::instance($filename); - - if (file_exists($filename)) { - $dataLegacy = $file->content(); - if(isset($dataLegacy['comments']) && is_array($dataLegacy['comments'])) { - foreach($dataLegacy['comments'] as $key => $comment) { - if(!empty($comment['id']) && $comment['id'] == $id) { - //add deleted as first item in array (better readability in file) - $dataLegacy['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 - $dataLegacy['comments'][$key]['deleted'] = $date; - //no need to look further as ids are supposed to be unique. - $file->save($dataLegacy); - break; - } - } - } - } else { - //nothing - } - //clear cache $this->grav['cache']->delete($this->comments_cache_id); @@ -437,7 +418,7 @@ class CommentsPlugin extends Plugin * * @param Event $event */ - public function saveComment($route, $path, $parent_id, $lang, $text, $name, $email, $title) + private function saveComment($route, $path, $parent_id, $lang, $text, $name, $email, $title, $user = "", $isAdmin = false) { $date = date('D, d M Y H:i:s', time()); @@ -464,7 +445,9 @@ class CommentsPlugin extends Plugin 'text' => $text, 'date' => $date, 'author' => $name, - 'email' => $email + 'email' => $email, + 'user' => $user, + 'isAdmin' => !empty($isAdmin), ]; $data['comments'][] = $newComment; $localfile->save($data); @@ -493,39 +476,6 @@ class CommentsPlugin extends Plugin ]; $indexfile->save($dataIndex); - /**************************************/ - /** store comments in old data files **/ - /** TODO: remove as soon as admin **/ - /** panel uses new index file **/ - /**************************************/ - $filename = DATA_DIR . 'comments'; - $filename .= ($lang ? '/' . $lang : ''); - $filename .= $path . '.yaml'; - $file = CompiledYamlFile::instance($filename); - - if (file_exists($filename)) { - $dataLegacy = $file->content(); - - $dataLegacy['comments'][] = [ - 'text' => $text, - 'date' => $date, - 'author' => $name, - 'email' => $email - ]; - } else { - $dataLegacy = array( - 'title' => $title, - 'lang' => $lang, - 'comments' => array([ - 'text' => $text, - 'date' => $date, - 'author' => $name, - 'email' => $email - ]) - ); - } - - $file->save($dataLegacy); //clear cache $this->grav['cache']->delete($this->comments_cache_id); @@ -560,12 +510,16 @@ class CommentsPlugin extends Plugin $title = filter_var(urldecode($post['title']), FILTER_SANITIZE_STRING); $parent_id = 0; + $username = ''; + $isAdmin = false; if (isset($this->grav['user'])) { $user = $this->grav['user']; if ($user->authenticated) { $name = $user->fullname; $email = $user->email; } + $username = $this->grav['user']->authenticated ? $this->grav['user']->username : ''; + $isAdmin = $this->grav['user']->authorize('admin.login'); } /** @var Language $language */ @@ -574,12 +528,109 @@ class CommentsPlugin extends Plugin $path = $this->grav['page']->path(); $route = $this->grav['page']->route(); - $this->saveComment($route, $path, $parent_id, $lang, $text, $name, $email, $title); + $this->saveComment($route, $path, $parent_id, $lang, $text, $name, $email, $title, $username, $isAdmin); break; } } + private function getRecentComments($limit = 10) { + //TODO +/* + //init cache id + $cache = $this->grav['cache']; + $comments_cache_id = md5('comments-data' . $cache->getKey() . '-' . $uri->url()); + $uri = $this->grav['uri']; + + //search in cache + if ($comments = $cache->fetch($comments_cache_id)) { + return $comments; + } + + $comments = $this->getDataFromFilename($filename)['comments']; + $comments = $this->setCommentLevels($comments); + //save to cache if enabled + $cache->save($this->comments_cache_id, $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 { + $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, + ); + $localfile = CompiledYamlFile::instance($filepath); + $localcomments = $localfile->content(); + if (!empty($localcomments['comments']) && is_array($localcomments['comments'])) { + foreach ($localcomments['comments'] as $comment) { + if (!empty($comment['deleted'])) { + empty($comment['parent']) ? $page_stats[$filepath]['deleted_comments']++ : $page_stats[$filepath]['deleted_replies']++; + empty($comment['parent']) ? $global_stats['deleted_comments']++ : $global_stats['deleted_replies']++; + $page_stats[$filepath]['deleted_entries']++; + $global_stats['deleted_entries']++; + } else { + empty($comment['parent']) ? $page_stats[$filepath]['active_comments']++ : $page_stats[$filepath]['active_replies']++; + empty($comment['parent']) ? $global_stats['active_comments']++ : $global_stats['active_replies']++; + $page_stats[$filepath]['active_entries']++; + $global_stats['active_entries']++; + + //use unix timestamp for comparing and sorting + if(is_int($comment['date'])) { + $time = $comment['date']; + } else { + $time = \DateTime::createFromFormat('D, d M Y H:i:s', $comment['date'])->getTimestamp(); + } + + if (empty($page_stats[$filepath]['latest_active_entry']) || $page_stats[$filepath]['latest_active_entry'] < $time) { + $page_stats[$filepath]['latest_active_entry'] = $time; + } + + $comments[] = array_merge(array( + 'path' => $filepath, + 'time' => $time, + ), $comment); + } + } + } + if (!empty($page_stats[$filepath]['latest_active_entry'])) { + $global_stats['pages_with_active_entries']++; + } + } + } + usort($comments, function($a, $b) { + if ($a['time'] === $b['time']) return 0; + if ($a['time'] < $b['time']) return 1; + return -1; + }); + if (!empty($limit) && $limit > 0 && $limit < count($comments)) { + return [$global_stats, $page_stats, array_slice($comments, 0, $limit)]; + } else { + return [$global_stats, $page_stats, $comments]; + } + } + private function getFilesOrderedByModifiedDate($path = '') { $files = []; @@ -787,12 +838,84 @@ class CommentsPlugin extends Plugin $this->grav['twig']->twig_paths[] = __DIR__ . '/admin/templates'; } + /** + * Handle the Reindex task from the admin + * + * @param Event $e + */ + public function onAdminTaskExecute(Event $e) + { + if ($e['method'] == 'taskReindexComments') { + $controller = $e['controller']; + header('Content-type: application/json'); + if (!$controller->authorizeTask('reindexComments', ['admin.configuration', 'admin.super'])) { + $json_response = [ + 'status' => 'error', + 'message' => ' Index not created', + 'details' => 'Insufficient permissions to reindex the comments index file.' + ]; + echo json_encode($json_response); + exit; + } +/*TODO // disable warnings + error_reporting(1); + // capture content + ob_start(); + $this->gtnt->createIndex(); + ob_get_clean(); + list($status, $msg) = $this->getIndexCount(); + $json_response = [ + 'status' => $status ? 'success' : 'error', + 'message' => ' ' . $msg + ]; + echo json_encode($json_response); + */ exit; + } + } + + /** + * Perform an 'add' or 'update' for comment data as needed + * + * @param $event + * @return bool + */ + public function onAdminAfterSave($event) + { + $obj = $event['object']; + if ($obj instanceof Page) { + //nothing to do + //save means, the page changed, but still exists + } + return true; + } + /** + * Perform an 'add' or 'update' for comment data as needed + * + * @param $event + * @return bool + */ + public function onAdminAfterDelete($event) + { + $obj = $event['object']; + if ($obj instanceof Page) { + //TODO $this->deleteComment($obj); + } + return true; + } + /** * Add navigation item to the admin plugin */ public function onAdminMenu() { $this->grav['twig']->plugins_hooked_nav['PLUGIN_COMMENTS.COMMENTS'] = ['route' => $this->route, 'icon' => 'fa-file-text']; + $options = [ + 'authorize' => 'taskReindexComments', + 'hint' => 'reindexes the comments index', + 'class' => 'comments-reindex', + 'icon' => 'fa-file-text' + ]; + $this->grav['twig']->plugins_quick_tray['PLUGIN_COMMENTS.COMMENTS'] = $options; } /** From bd1799cc7e377d9770fab3e1ba2ca71b9209679b Mon Sep 17 00:00:00 2001 From: codeshell Date: Mon, 30 Oct 2017 12:18:50 +0100 Subject: [PATCH 15/16] add recent comments widget as plugin template (partials/recentcomments.html.twig) --- comments.php | 103 ++++++++++++-------- languages.yaml | 6 ++ templates/partials/recentcomments.html.twig | 54 ++++++++++ 3 files changed, 124 insertions(+), 39 deletions(-) create mode 100644 templates/partials/recentcomments.html.twig diff --git a/comments.php b/comments.php index 0d9813d..0a6a9d8 100644 --- a/comments.php +++ b/comments.php @@ -9,6 +9,7 @@ use Grav\Common\Filesystem\RecursiveFolderFilterIterator; use Grav\Common\Page\Page; use RocketTheme\Toolbox\Event\Event; use Symfony\Component\Yaml\Yaml; +use Twig_SimpleFunction; require_once PLUGINS_DIR . 'comments/class/Comment.php'; class CommentsPlugin extends Plugin @@ -48,7 +49,10 @@ class CommentsPlugin extends Plugin public function onTwigSiteVariables() { $this->grav['twig']->enable_comments_plugin = $this->enable; $this->grav['twig']->comments = $this->fetchComments(); - $this->grav['twig']->recentComments = $this->getRecentComments(); + //$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'); @@ -153,22 +157,6 @@ class CommentsPlugin extends Plugin */ public function onPluginsInitialized() { - if ('/recent' === $this->grav['uri']->path()) { - //TODO TEST - echo PAGES_DIR; - - echo "
"; - - $test = $this->getRecentComments(25); - var_dump($test[0]); - echo '


'; - var_dump($test[1]); - echo '


'; - var_dump($test[2]); - echo '


'; - exit(); - } - if ($this->isAdmin()) { $this->initializeAdmin(); } else { @@ -181,7 +169,7 @@ class CommentsPlugin extends Plugin */ public function onPageInitialized() { - $is_ajax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'; + $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() @@ -534,24 +522,34 @@ class CommentsPlugin extends Plugin } } - private function getRecentComments($limit = 10) { - //TODO -/* - //init cache id + /** + * 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']; - $comments_cache_id = md5('comments-data' . $cache->getKey() . '-' . $uri->url()); - $uri = $this->grav['uri']; - - //search in cache - if ($comments = $cache->fetch($comments_cache_id)) { - return $comments; + $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; + } } - $comments = $this->getDataFromFilename($filename)['comments']; - $comments = $this->setCommentLevels($comments); - //save to cache if enabled - $cache->save($this->comments_cache_id, $comments); - */ $path = PAGES_DIR; $dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS); $itrFilter = new \RecursiveIteratorIterator($dirItr, \RecursiveIteratorIterator::SELF_FIRST); @@ -570,8 +568,12 @@ class CommentsPlugin extends Plugin $comments = array(); foreach ($filesItr as $filepath => $file) { if ($file->isDir()) { - // this should never trigger as we are looking vor yamls only + // 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, @@ -580,6 +582,7 @@ class CommentsPlugin extends Plugin 'active_replies' => 0, 'deleted_replies' => 0, 'latest_active_entry' => 0, + 'route' => $route, ); $localfile = CompiledYamlFile::instance($filepath); $localcomments = $localfile->content(); @@ -609,6 +612,7 @@ class CommentsPlugin extends Plugin $comments[] = array_merge(array( 'path' => $filepath, + 'route' => $route, 'time' => $time, ), $comment); } @@ -619,16 +623,34 @@ class CommentsPlugin extends Plugin } } } - usort($comments, function($a, $b) { + + //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)) { - return [$global_stats, $page_stats, array_slice($comments, 0, $limit)]; - } else { - return [$global_stats, $page_stats, $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 = '') { @@ -723,7 +745,7 @@ class CommentsPlugin extends Plugin /** * 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)) { @@ -899,6 +921,9 @@ class CommentsPlugin extends Plugin $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; } diff --git a/languages.yaml b/languages.yaml index efc3d5a..81d7de5 100644 --- a/languages.yaml +++ b/languages.yaml @@ -8,6 +8,9 @@ de: DELETE: Löschen SUCCESS: "Der Kommentar wurde erfolgreich gespeichert." COMMENTS: Kommentare + 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: '

Ein neuer Kommentar am %1$s von %3$s (%4$s).

Seite: %2$s

Text: %5$s

' @@ -36,6 +39,9 @@ en: DELETE: Delete SUCCESS: "Comment has been saved successfully." COMMENTS: Comments + 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: '

A new comment was made on %1$s by %3$s (%4$s).

Page: %2$s

Text: %5$s

' diff --git a/templates/partials/recentcomments.html.twig b/templates/partials/recentcomments.html.twig new file mode 100644 index 0000000..d8084e3 --- /dev/null +++ b/templates/partials/recentcomments.html.twig @@ -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 %} +

{'PLUGIN_COMMENTS.COMMENTS_STATS'|t}}

+ {% set stats = recent_comments(limit|default(5), pages_limit|default(3)) %} + {% if stats.global_stats.active_entries %} + {{stats.global_stats.active_entries}} + ( {{stats.global_stats.deleted_entries}}) + - {{stats.global_stats.active_comments}} + ( {{stats.global_stats.deleted_comments}}) + - {{stats.global_stats.active_replies}} + ( {{stats.global_stats.deleted_replies}}) + - {{stats.global_stats.pages_with_active_entries}} + {% endif %} + {% for key, entry in stats.pages %} + {% if loop.first %} +

{'PLUGIN_COMMENTS.RECENT_PAGES'|t}} (limit {{stats.options.pages_limit}})

+ + {% endif %} + {% endfor %} + {% for key, entry in stats.comments %} + {% if loop.first %} +

{'PLUGIN_COMMENTS.RECENT_COMMENTS'|t}} (limit {{stats.options.comments_limit}})

+ + {% endif %} + {% endfor %} +{% endif %} From ee89c2bdcae8c1995513f90217438a4bfa1a4477 Mon Sep 17 00:00:00 2001 From: codeshell Date: Mon, 30 Oct 2017 12:53:13 +0100 Subject: [PATCH 16/16] fix ajax display when posting first comment on a page --- assets/comments.js | 2 +- templates/partials/comments.html.twig | 19 +++++++------------ templates/partials/recentcomments.html.twig | 6 +++--- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/assets/comments.js b/assets/comments.js index c366189..11d6225 100644 --- a/assets/comments.js +++ b/assets/comments.js @@ -154,7 +154,7 @@ jQuery(document).ready(function () { if ($( "div[data-id='" + response.data.parent_id + "']" ).length > 0) { $( "div[data-id='" + response.data.parent_id + "']" ).first().after(newMedia); } else { - $( "div.comments" ).last().prepend(newMedia); + $( "#comments" ).prepend(newMedia); } } setTimeout(function () { diff --git a/templates/partials/comments.html.twig b/templates/partials/comments.html.twig index 9728aa0..a39d91f 100644 --- a/templates/partials/comments.html.twig +++ b/templates/partials/comments.html.twig @@ -2,14 +2,12 @@ {% set scope = scope ?: 'data.' %}

{{'PLUGIN_COMMENTS.COMMENTS'|t}}

- - - {% include 'partials/comments.form.html.twig' %} - - {% if grav.twig.comments|length %} - {{'PLUGIN_COMMENTS.ADD_NEW'|t}} -
+ + {% include 'partials/comments.form.html.twig' %} + +
+ {% if grav.twig.comments|length %} {% for comment in grav.twig.comments %}
@@ -33,10 +31,7 @@
{% endfor %} -
- - {% endif %} + {% endif %} +
{% endif %} - - diff --git a/templates/partials/recentcomments.html.twig b/templates/partials/recentcomments.html.twig index d8084e3..296535d 100644 --- a/templates/partials/recentcomments.html.twig +++ b/templates/partials/recentcomments.html.twig @@ -1,6 +1,6 @@ {# 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 %} -

{'PLUGIN_COMMENTS.COMMENTS_STATS'|t}}

+

{{'PLUGIN_COMMENTS.COMMENTS_STATS'|t}}

{% set stats = recent_comments(limit|default(5), pages_limit|default(3)) %} {% if stats.global_stats.active_entries %} {{stats.global_stats.active_entries}} @@ -13,7 +13,7 @@ {% endif %} {% for key, entry in stats.pages %} {% if loop.first %} -

{'PLUGIN_COMMENTS.RECENT_PAGES'|t}} (limit {{stats.options.pages_limit}})

+

{{'PLUGIN_COMMENTS.RECENT_PAGES'|t}} (limit {{stats.options.pages_limit}})

    {% endif %}
  • @@ -31,7 +31,7 @@ {% endfor %} {% for key, entry in stats.comments %} {% if loop.first %} -

    {'PLUGIN_COMMENTS.RECENT_COMMENTS'|t}} (limit {{stats.options.comments_limit}})

    +

    {{'PLUGIN_COMMENTS.RECENT_COMMENTS'|t}} (limit {{stats.options.comments_limit}})

      {% endif %} {% set entry_icon = 'fa-comment' %}