Merge pull request #1 from getgrav/feature/use-form-plugin

Work with the Form plugin
This commit is contained in:
Flavio Copes 2015-10-15 18:05:24 +02:00
commit 05ba2c0215
8 changed files with 298 additions and 420 deletions

View File

@ -45,9 +45,9 @@ For example, in Antimatter, in `templates/item.html.twig`:
The comment form will appear to the blog post items. The comment form will appear to the blog post items.
# Enable the Captcha anti-spam filter # Enabling Recaptcha
To reduce spam in your comments, enable the Google Recaptcha integration we added. Copy the plugin's `comments.yaml` to `user/config/plugins/comments.yaml` and enable `use_captcha`. Also add the Google Recaptcha API keys to allow it to work correctly. The plugin comes with Recaptcha integration. To make it work, add your own Recaptcha `site` and `secret` keys the the plugin yaml config file.
# Where are the comments stored? # Where are the comments stored?
@ -61,7 +61,7 @@ Further improvements to the comments visualization will be added in the next rel
# Email notifications # Email notifications
Upon receiving a comment, if `enable_email_notifications` is enabled, the Comments plugin will send an email to the `notifications_email_to` address set in the plugin options. The plugin interacts with the Email plugin to send emails upon receiving a comment.
# Things still missing # Things still missing

View File

@ -105,6 +105,33 @@
<p class="center">Showing <span class="totalRetrieved">{{grav.twig.comments.totalRetrieved}}</span> comments of <span class="totalAvailable">{{grav.twig.comments.totalAvailable}}</span></p> <p class="center">Showing <span class="totalRetrieved">{{grav.twig.comments.totalRetrieved}}</span> comments of <span class="totalAvailable">{{grav.twig.comments.totalAvailable}}</span></p>
</div> </div>
<h1>Recently commented pages</h1>
<div class="admin-block">
<table>
<tbody class="js__pages-container">
<tr class="h">
<th class="page">Page</th>
<th class="number-of-comments">Number of comments</th>
<th class="last-comment-date">Last commented on</th>
</tr>
{% for page in grav.twig.pages %}
<tr>
<td class="page">{{page.title}}</td>
<td class="number-of-comments">{{page.commentsCount}}</td>
<td class="last-comment-date"><strong>Page</strong>: {{page.lastCommentDate}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if grav.twig.comments.totalRetrieved < grav.twig.comments.totalAvailable %}
<button type="button" class="button center js__load-more">
Load more
</button>
{% endif %}
</div>
{% endblock %} {% endblock %}

31
blueprints.yaml Normal file
View File

@ -0,0 +1,31 @@
name: Comments
version: 0.1.0
description: Adds a commenting functionality to your site
icon: comment
author:
name: Team Grav
email: devs@getgrav.org
url: http://getgrav.org
homepage: https://github.com/getgrav/grav-plugin-comments
keywords: guestbook, plugin
bugs: https://github.com/getgrav/grav-plugin-comments/issues
readme: https://github.com/getgrav/grav-plugin-comments/blob/develop/README.md
license: MIT
dependencies:
- form
- email
form:
validation: loose
fields:
enabled:
type: toggle
label: Plugin status
highlight: 1
default: 0
options:
1: Enabled
0: Disabled
validate:
type: bool

View File

@ -15,6 +15,7 @@ use Symfony\Component\Yaml\Yaml;
class CommentsPlugin extends Plugin class CommentsPlugin extends Plugin
{ {
protected $route = 'comments'; protected $route = 'comments';
protected $enable = false;
/** /**
* @return array * @return array
@ -23,29 +24,86 @@ class CommentsPlugin extends Plugin
{ {
return [ return [
'onPluginsInitialized' => ['onPluginsInitialized', 0], 'onPluginsInitialized' => ['onPluginsInitialized', 0],
'onFormProcessed' => ['onFormProcessed', 0],
'onPageInitialized' => ['onPageInitialized', 0],
'onTwigSiteVariables' => ['onTwigSiteVariables', 0]
]; ];
} }
/**
* Initialize form if the page has one. Also catches form processing if user posts the form.
*/
public function onPageInitialized()
{
if (!$this->isAdmin()) {
/** @var Page $page */
$page = $this->grav['page'];
if (!$page) {
return;
}
$header = $page->header();
if (!isset($header->form)) {
$header->form = $this->grav['config']->get('plugins.comments.form');
$page->header($header);
}
}
}
public function onTwigSiteVariables() {
if (!$this->isAdmin()) {
$this->grav['twig']->enable = $this->enable;
if ($this->enable) {
$this->grav['twig']->comments = $this->fetchComments();
}
}
}
/**
* Determine if $haystack starts with $needle. Credit: http://stackoverflow.com/a/10473026/205039
*/
private function startsWith($haystack, $needle) {
return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== FALSE;
}
/**
* Determine if the plugin should be enabled based on the enable_on_routes and disable_on_routes config options
*/
private function calculateEnable() {
$uri = $this->grav['uri'];
$disable_on_routes = (array) $this->config->get('plugins.comments.disable_on_routes');
$enable_on_routes = (array) $this->config->get('plugins.comments.enable_on_routes');
$path = $uri->path();
if (!in_array($path, $disable_on_routes)) {
if (in_array($path, $enable_on_routes)) {
$this->enable = true;
} else {
foreach($enable_on_routes as $route) {
if ($this->startsWith($path, $route)) {
$this->enable = true;
break;
}
}
}
}
}
/** /**
*/ */
public function onPluginsInitialized() public function onPluginsInitialized()
{ {
if (!$this->isAdmin()) { if (!$this->isAdmin()) {
$this->calculateEnable();
$this->enable([ $this->enable([
'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0],
]); ]);
$this->addCommentURL = $this->config->get('plugins.comments.addCommentURL', '/add-comment');
if ($this->addCommentURL && $this->addCommentURL == $this->grav['uri']->path()) {
$this->enable([
'onPagesInitialized' => ['addComment', 0]
]);
} else {
$this->grav['twig']->comments = $this->fetchComments();
}
} else { } else {
/** @var Uri $uri */ /** @var Uri $uri */
@ -74,8 +132,23 @@ class CommentsPlugin extends Plugin
} }
} }
public function addComment() /**
* Handle form processing instructions.
*
* @param Event $event
*/
public function onFormProcessed(Event $event)
{ {
$form = $event['form'];
$action = $event['action'];
$params = $event['params'];
if (!$this->active) {
return;
}
switch ($action) {
case 'addComment':
$post = !empty($_POST) ? $_POST : []; $post = !empty($_POST) ? $_POST : [];
$lang = filter_var(urldecode($post['lang']), FILTER_SANITIZE_STRING); $lang = filter_var(urldecode($post['lang']), FILTER_SANITIZE_STRING);
@ -85,19 +158,9 @@ class CommentsPlugin extends Plugin
$email = filter_var(urldecode($post['email']), FILTER_SANITIZE_STRING); $email = filter_var(urldecode($post['email']), FILTER_SANITIZE_STRING);
$title = filter_var(urldecode($post['title']), FILTER_SANITIZE_STRING); $title = filter_var(urldecode($post['title']), FILTER_SANITIZE_STRING);
if ($this->config->get('plugins.comments.use_captcha')) { /** @var Language $language */
//Validate the captcha $language = $this->grav['language'];
$recaptchaResponse = filter_var(urldecode($post['recaptchaResponse']), FILTER_SANITIZE_STRING); $lang = $language->getLanguage();
$url = 'https://www.google.com/recaptcha/api/siteverify?secret=';
$url .= $this->config->get('plugins.comments.recatpcha_secret');
$url .= '&response=' . $recaptchaResponse;
$response = json_decode(file_get_contents($url), true);
if ($response['success'] == false) {
throw new \RuntimeException('Error validating the Captcha');
}
}
$filename = DATA_DIR . 'comments'; $filename = DATA_DIR . 'comments';
$filename .= ($lang ? '/' . $lang : ''); $filename .= ($lang ? '/' . $lang : '');
@ -127,41 +190,8 @@ class CommentsPlugin extends Plugin
} }
$file->save(Yaml::dump($data)); $file->save(Yaml::dump($data));
break;
if (isset($this->grav['Email']) && $this->grav['config']->get('plugins.comments.enable_email_notifications')) {
$this->sendEmailNotification(array(
'title' => $title,
'comment' => array(
'text' => $text,
'date' => gmdate('D, d M Y H:i:s', time()),
'author' => $name,
'email' => $email
)
));
} }
exit();
}
private function sendEmailNotification($comment) {
/** @var Language $l */
$l = $this->grav['language'];
$sitename = $this->grav['config']->get('site.title', 'Website');
$from = $this->grav['config']->get('plugins.email.from', 'noreply@getgrav.org');
$to = $this->grav['config']->get('plugins.email.email');
$subject = $l->translate(['PLUGIN_COMMENTS.NEW_COMMENT_EMAIL_SUBJECT', $sitename]);
$content = $l->translate(['PLUGIN_COMMENTS.NEW_COMMENT_EMAIL_BODY', $sitename, $comment['title'], $comment['comment']['text'], $comment['comment']['author'], $comment['comment']['email']]);
$twig = $this->grav['twig'];
$body = $twig->processTemplate('email/base.html.twig', ['content' => $content]);
$message = $this->grav['Email']->message($subject, $body, 'text/html')
->setFrom($from)
->setTo($to);
$sent = $this->grav['Email']->send($message);
} }
private function getFilesOrderedByModifiedDate($path = '') { private function getFilesOrderedByModifiedDate($path = '') {
@ -242,15 +272,8 @@ class CommentsPlugin extends Plugin
}); });
$totalAvailable = count($comments); $totalAvailable = count($comments);
$comments = array_slice($comments, $page * $number, $number); $comments = array_slice($comments, $page * $number, $number);
$totalRetrieved = count($comments); $totalRetrieved = count($comments);
$hasMore = false;
if ($totalAvailable > $totalRetrieved) {
$hasMore = true;
}
return (object)array( return (object)array(
"comments" => $comments, "comments" => $comments,
@ -264,7 +287,7 @@ class CommentsPlugin extends Plugin
* Return the comments associated to the current route * Return the comments associated to the current route
*/ */
private function fetchComments() { private function fetchComments() {
$lang = $this->grav['language']->getActive(); $lang = $this->grav['language']->getLanguage();
$filename = $lang ? '/' . $lang : ''; $filename = $lang ? '/' . $lang : '';
$filename .= $this->grav['uri']->path() . '.yaml'; $filename .= $this->grav['uri']->path() . '.yaml';

View File

@ -1,6 +1,77 @@
enabled: true enabled: true
use_captcha: false
recatpcha_site_key: '' enable_on_routes:
recatpcha_secret: '' - '/blog'
enable_email_notifications: false
notifications_email_to: 'noreply@getgrav.org' disable_on_routes:
- /blog/blog-post-to-ignore
- /ignore-this-route
#- '/blog/daring-fireball-link'
form:
name: comments
fields:
- name: name
label: Name
placeholder: Enter your name
autofocus: on
autocomplete: on
type: text
validate:
required: true
- name: email
label: Email
placeholder: Enter your email address
type: email
validate:
required: true
- name: text
label: Message
placeholder: Enter your message
type: textarea
validate:
required: true
- name: date
type: hidden
process:
fillWithCurrentDateTime: true
- name: title
type: hidden
evaluateDefault: grav.page.header.title
- name: lang
type: hidden
evaluateDefault: grav.language.getLanguage
- name: path
type: hidden
evaluateDefault: grav.uri.path
# - name: g-recaptcha-response
# label: Captcha
# type: captcha
# recatpcha_site_key: e32iojeoi32jeoi32jeoij32oiej32oiej3
# recaptcha_not_validated: 'Captcha not valid!'
# validate:
# required: true
# process:
# ignore: true
buttons:
- type: submit
value: Submit
process:
# - email:
# subject: "[Site Guestbook] {{ form.value.name|e }}"
# body: "{% include 'forms/data.html.twig' %}"
# - captcha:
# recatpcha_secret: ej32oiej23oiej32oijeoi32jeio32je
- addComment:
- message: Thank you for writing your comment!

View File

@ -1,5 +1,6 @@
en: en:
PLUGIN_COMMENTS: PLUGIN_COMMENTS:
ADD_COMMENT: Add a comment
COMMENTS: Comments COMMENTS: Comments
EMAIL_NOT_CONFIGURED: Email not configured EMAIL_NOT_CONFIGURED: Email not configured
NEW_COMMENT_EMAIL_SUBJECT: 'New comment on %1$s' NEW_COMMENT_EMAIL_SUBJECT: 'New comment on %1$s'
@ -11,6 +12,7 @@ en:
BY: by BY: by
it: it:
PLUGIN_COMMENTS: PLUGIN_COMMENTS:
ADD_COMMENT: Aggiungi un commento
COMMENTS: Commenti COMMENTS: Commenti
EMAIL_NOT_CONFIGURED: Email non configurata EMAIL_NOT_CONFIGURED: Email non configurata
NEW_COMMENT_EMAIL_SUBJECT: 'Nuovo commento su %1$s' NEW_COMMENT_EMAIL_SUBJECT: 'Nuovo commento su %1$s'

View File

@ -1,194 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Comments Email Template</title>
<style>
/* -------------------------------------
GLOBAL
------------------------------------- */
* {
margin: 0;
padding: 0;
font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
font-size: 100%;
line-height: 1.6;
}
img {
max-width: 100%;
}
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100%!important;
height: 100%;
}
/* -------------------------------------
ELEMENTS
------------------------------------- */
a {
color: #348eda;
}
.btn-primary {
text-decoration: none;
color: #FFF;
background-color: #348eda;
border: solid #348eda;
border-width: 10px 20px;
line-height: 2;
font-weight: bold;
margin-right: 10px;
text-align: center;
cursor: pointer;
display: inline-block;
border-radius: 25px;
}
.btn-secondary {
text-decoration: none;
color: #FFF;
background-color: #aaa;
border: solid #aaa;
border-width: 10px 20px;
line-height: 2;
font-weight: bold;
margin-right: 10px;
text-align: center;
cursor: pointer;
display: inline-block;
border-radius: 25px;
}
.last {
margin-bottom: 0;
}
.first {
margin-top: 0;
}
.padding {
padding: 10px 0;
}
/* -------------------------------------
BODY
------------------------------------- */
table.body-wrap {
width: 100%;
padding: 20px;
}
table.body-wrap .container {
border: 1px solid #f0f0f0;
}
/* -------------------------------------
FOOTER
------------------------------------- */
table.footer-wrap {
width: 100%;
clear: both!important;
}
.footer-wrap .container p {
font-size: 12px;
color: #666;
}
table.footer-wrap a {
color: #999;
}
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1, h2, h3 {
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
color: #000;
margin: 40px 0 10px;
line-height: 1.2;
font-weight: 200;
}
h1 {
font-size: 36px;
}
h2 {
font-size: 28px;
}
h3 {
font-size: 22px;
}
p, ul, ol {
margin-bottom: 10px;
font-weight: normal;
font-size: 14px;
}
ul li, ol li {
margin-left: 5px;
list-style-position: inside;
}
/* ---------------------------------------------------
RESPONSIVENESS
Nuke it from orbit. It's the only way to be sure.
------------------------------------------------------ */
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container {
display: block!important;
max-width: 600px!important;
margin: 0 auto!important; /* makes it centered */
clear: both!important;
}
/* Set the padding on the td rather than the div for Outlook compatibility */
.body-wrap .container {
padding: 20px;
}
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
max-width: 600px;
margin: 0 auto;
display: block;
}
/* Let's make sure tables in the content area are 100% wide */
.content table {
width: 100%;
}
</style>
</head>
<body bgcolor="#f6f6f6">
<!-- body -->
<table class="body-wrap" bgcolor="#f6f6f6">
<tr>
<td></td>
<td class="container" bgcolor="#FFFFFF">
<div class="content">
<table>
<tr>
<td>
{{ content }}
</td>
</tr>
</table>
</div>
</td>
<td></td>
</tr>
</table>
<!-- /body -->
<!-- footer -->
<table class="footer-wrap">
<tr>
<td></td>
<td class="container">
<div class="content">
<table>
<tr>
<td align="center">
{{ 'PLUGIN_COMMENTS.EMAIL_FOOTER'|tu }}
</td>
</tr>
</table>
</div>
</td>
<td></td>
</tr>
</table>
<!-- /footer -->
</body>
</html>

View File

@ -1,113 +1,30 @@
{% set use_captcha = grav.config.plugins.comments.use_captcha %} {% if grav.twig.enable %}
<h3>Add a Comment</h3> <h3>{{'PLUGIN_COMMENTS.ADD_COMMENT'|t}}</h3>
<script> <form name="{{ grav.config.plugins.comments.form.name }}"
$(function() { action="{{ uri.rootUrl ~ (grav.config.plugins.comments.form.action|default(page.route)) }}"
function validateEmail(email) { method="{{ grav.config.plugins.comments.form.method|upper|default('POST') }}">
var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i; {% for field in grav.config.plugins.comments.form.fields %}
return re.test(email);
}
$(document).on('click tap', '.js__add-new-comment', function(event) { {% set value = form.value(field.name) %}
event.preventDefault(); {% if field.evaluateDefault %}
{% set value = evaluate(field.evaluateDefault) %}
var text = $('.js__new-comment-text').val();
var name = $('.js__new-comment-name').val();
var email = $('.js__new-comment-email').val();
var captcha = $('#g-recaptcha-response').val();
if (text.length == 0 || email.length == 0 || name.length == 0) {
$('.alert').html('Please fill all the fields');
$('.alert-container').show();
return;
}
if (!validateEmail(email)) {
$('.alert').html('Please enter a valid email');
$('.alert-container').show();
return;
}
{% if use_captcha %}
if (!captcha) {
$('.alert').html("Error validating the security code");
$('.alert-container').show();
return;
}
{% endif %} {% endif %}
<div>
$.ajax({ {% include "forms/fields/#{field.type}/#{field.type}.html.twig" %}
url: "{{ grav.uri.rootUrl }}/add-comment",
data: {
text: $('.js__new-comment-text').val(),
name: $('.js__new-comment-name').val(),
email: $('.js__new-comment-email').val(),
title: "{{ grav.page.header.title }}",
lang: "{{ grav.language.getActive }}",
path: "{{ grav.uri.path }}",
{% if use_captcha %}recaptchaResponse: captcha{% endif %}
},
type: 'POST'
})
.success(function() {
$('.alert-container').hide();
window.location.reload();
})
.error(function() {
$('.alert').html("Error while posting the comment");
$('.alert-container').show();
});
});
});
</script>
{% if use_captcha %}
<script src="https://www.google.com/recaptcha/api.js?onload=captchaOnloadCallback&render=explicit" async defer></script>
<script>
$(function() {
var captchaOnloadCallback = function captchaOnloadCallback() {
grecaptcha.render('g-recaptcha', {
'sitekey': "{{grav.config.plugins.comments.recatpcha_site_key}}",
'callback': captchaValidatedCallback,
'expired-callback': captchaExpiredCallback
});
}
var captchaValidatedCallback = function captchaValidatedCallback() {
};
var captchaExpiredCallback = function captchaExpiredCallback() {
grecaptcha.reset();
};
});
</script>
{% endif %}
<div class="alert-container" style="display: none">
<blockquote>
<blockquote>
<blockquote>
<blockquote>
<p class="alert"></p>
</blockquote>
</blockquote>
</blockquote>
</blockquote>
</div> </div>
{% endfor %}
<form> <div class="buttons">
<textarea class="js__new-comment-text"></textarea> {% for button in grav.config.plugins.comments.form.buttons %}
{{'PLUGIN_COMMENTS.NAME'|t}} <input type="text" class="js__new-comment-name" /> <button class="button" type="{{ button.type|default('submit') }}">{{ button.value|default('Submit') }}</button>
{{'PLUGIN_COMMENTS.EMAIL'|t}} <input type="email" class="js__new-comment-email" /> {% endfor %}
{% if use_captcha %} </div>
<div class="g-recaptcha" id="g-recaptcha"></div>
{% endif %}
<input type="submit" class="js__add-new-comment" />
</form> </form>
<div class="alert">{{ form.message }}</div>
{% if grav.twig.comments|length %} {% if grav.twig.comments|length %}
<h3>{{'PLUGIN_COMMENTS.COMMENTS'|t}}</h3> <h3>{{'PLUGIN_COMMENTS.COMMENTS'|t}}</h3>
@ -124,3 +41,4 @@ $(function() {
{% endfor %} {% endfor %}
</table> </table>
{% endif %} {% endif %}
{% endif %}