This commit is contained in:
Jules 2016-11-24 05:23:36 +00:00 committed by GitHub
commit 7ea7d218de
11 changed files with 316 additions and 46 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
.DS_Store
.idea/*

View File

@ -28,12 +28,10 @@ class ContentReviewCMSExtension extends LeftAndMainExtension
$page = $this->findRecord($data);
if (!$page->canEdit()) {
return Security::permissionFailure($this->owner);
}
}
$notes = (!empty($data["ReviewNotes"]) ? $data["ReviewNotes"] : _t("ContentReview.NOCOMMENTS", "(no comments)"));
$page->addReviewNote(Member::currentUser(), $notes);
$page->addReviewNote(Member::currentUser(), $notes, $page->ReviewInfo);
$page->advanceReviewDate();
$this->owner->getResponse()->addHeader("X-Status", _t("ContentReview.REVIEWSUCCESSFUL", "Content reviewed successfully"));
return $this->owner->redirectBack();
}

View File

@ -17,7 +17,13 @@ class ContentReviewDefaultSettings extends DataExtension
'ReviewPeriodDays' => 'Int',
'ReviewFrom' => 'Varchar(255)',
'ReviewSubject' => 'Varchar(255)',
'ReviewSubjectReminder' => 'Varchar(255)',
'ReviewBody' => 'HTMLText',
'ReviewBodyFirstReminder' => 'HTMLText',
'ReviewBodySecondReminder' => 'HTMLText',
'ReviewReminderEmail' => 'Text',
'FirstReviewDaysBefore' => 'Int',
'SecondReviewDaysBefore' => 'Int'
);
/**
@ -26,8 +32,14 @@ class ContentReviewDefaultSettings extends DataExtension
* @var array
*/
private static $defaults = array(
'ReviewSubjectReminder' => 'Page(s) are approaching content review date',
'ReviewSubject' => 'Page(s) are due for content review',
'ReviewBodyFirstReminder' => '<h2>Page(s) 1 month from review</h2><p>There are $FirstReminderPagesCount pages that are due for review by you 1 month from today.</p>',
'ReviewBodySecondReminder' => '<h2>Page(s) 1 week from from review</h2><p>There are $SecondReminderPagesCount pages that are due for review by you 1 week from today.</p>',
'ReviewBody' => '<h2>Page(s) due for review</h2><p>There are $PagesCount pages that are due for review today by you.</p>',
'ReviewReminderEmail' => 'govt.nz@dia.govt.nz',
'FirstReviewDaysBefore' => '30',
'SecondReviewDaysBefore' => '7'
);
/**
@ -50,6 +62,7 @@ class ContentReviewDefaultSettings extends DataExtension
* @var string
*/
private static $content_review_template = 'ContentReviewEmail';
private static $content_review_reminder_template = 'ContentReviewReminderEmail';
/**
* @return string
@ -112,6 +125,7 @@ class ContentReviewDefaultSettings extends DataExtension
$fields->addFieldToTab('Root.ContentReview', $reviewFrequency);
$users = Permission::get_members_by_permission(array(
'CMS_ACCESS_CMSMain',
'ADMIN',
@ -143,14 +157,31 @@ class ContentReviewDefaultSettings extends DataExtension
$fields->addFieldToTab('Root.ContentReview', $groupField);
$FirstReviewDaysBefore = NumericField::create(
'FirstReviewDaysBefore',
_t('ContentReview.FIRSTREVIEWDAYSBEFORE', 'First review reminder # days before final review')
);
$SecondReviewDaysBefore = NumericField::create(
'SecondReviewDaysBefore',
_t('ContentReview.SECONDREVIEWDAYSBEFORE', 'Second review reminder # days before final review')
);
// Email content
$fields->addFieldsToTab(
'Root.ContentReview',
array(
TextField::create('ReviewFrom', _t('ContentReview.EMAILFROM', 'From email address'))
->setRightTitle(_t('Review.EMAILFROM_RIGHTTITLE', 'e.g: do-not-reply@site.com')),
TextField::create('ReviewSubject', _t('ContentReview.EMAILSUBJECT', 'Subject line')),
TextAreaField::create('ReviewBody', _t('ContentReview.EMAILTEMPLATE', 'Email template')),
$FirstReviewDaysBefore,
$SecondReviewDaysBefore,
TextField::create('ReviewReminderEmail','Review reminder email address')
->setRightTitle('e.g: review.reminders@site.com'),
TextField::create('ReviewSubjectReminder', _t('ContentReview.EMAILSUBJECTREMINDER', 'Subject line - reminder')),
TextField::create('ReviewSubject', _t('ContentReview.EMAILSUBJECT', 'Subject line - Review due')),
TextAreaField::create('ReviewBodyFirstReminder', _t('ContentReview.EMAILTEMPLATEFIRSTREMINDER', 'Email body - First reminder')),
TextAreaField::create('ReviewBodySecondReminder', _t('ContentReview.EMAILTEMPLATESECONDREMINDER', 'Email body - Second reminder')),
TextAreaField::create('ReviewBody', _t('ContentReview.EMAILTEMPLATE', 'Email body - Review due')),
LiteralField::create('TemplateHelp', $this->owner->renderWith('ContentReviewAdminHelp')),
)
);

View File

@ -25,6 +25,7 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
"NextReviewDate" => "Date",
"LastEditedByName" => "Varchar(255)",
"OwnerNames" => "Varchar(255)",
"ReviewInfo" => "Text"
);
/**
@ -126,13 +127,22 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
"<label class=\"left\" for=\"Form_EditForm_ReviewNotes\">" . _t("ContentReview.CONTENTREVIEW", "Content due for review") . "</label>"
);
if (strlen($this->owner->ReviewInfo) > 0) {
$reviewInfo = LiteralField::create(
"ReviewContentInfo",
"<p class=\"quick-info\">" . $this->owner->ReviewInfo . "</p>"
);
} else {
$reviewInfo = '';
}
$ReviewNotes = LiteralField::create("ReviewNotes", "<textarea class=\"no-change-track\" id=\"Form_EditForm_ReviewNotes\" name=\"ReviewNotes\" placeholder=\"" . _t("ContentReview.COMMENTS", "(optional) Add comments...") . "\" class=\"text\"></textarea>");
$quickReviewAction = FormAction::create("savereview", _t("ContentReview.MARKREVIEWED", "Mark as reviewed"))
->setAttribute("data-icon", "pencil")
->setAttribute("data-text-alternate", _t("ContentReview.MARKREVIEWED", "Mark as reviewed"));
$allFields = CompositeField::create($reviewTitle, $ReviewNotes, $quickReviewAction)
$allFields = CompositeField::create($reviewTitle, $reviewInfo, $ReviewNotes, $quickReviewAction)
->addExtraClass('review-notes field');
$reviewTab = Tab::create('ReviewContent', $allFields);
@ -383,6 +393,8 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
->addExtraClass('custom-setting')
->setDescription(_t("ContentReview.REVIEWFREQUENCYDESCRIPTION", "The review date will be set to this far in the future whenever the page is published"));
$reviewInfoField = TextareaField::create("ReviewInfo", _t("ContentReview.REVIEWINFO", "Review information"));
$notesField = GridField::create("ReviewNotes", "Review Notes", $this->owner->ReviewLogs(), GridFieldConfig_RecordEditor::create());
$fields->addFieldsToTab("Root.ContentReview", array(
@ -395,6 +407,7 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
$reviewFrequency
)->addExtraClass("review-settings"),
ReadonlyField::create("ROContentOwners", _t("ContentReview.CONTENTOWNERS", "Content Owners"), $this->getOwnerNames()),
$reviewInfoField,
$notesField,
));
}
@ -405,11 +418,14 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
* @param Member $reviewer
* @param string $message
*/
public function addReviewNote(Member $reviewer, $message)
public function addReviewNote(Member $reviewer, $message, $reviewInfo = null)
{
$reviewLog = ContentReviewLog::create();
$reviewLog->Note = $message;
$reviewLog->ReviewerID = $reviewer->ID;
if ($reviewInfo) {
$reviewLog->ReviewInfo = $reviewInfo;
}
$this->owner->ReviewLogs()->add($reviewLog);
}
@ -425,16 +441,58 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
$nextDate = false;
$options = $this->getOptions();
if ($options && $options->ReviewPeriodDays) {
if ($options && $options->ReviewPeriodDays > 0) {
$nextDate = date('Y-m-d', strtotime('+ ' . $options->ReviewPeriodDays . ' days', SS_Datetime::now()->format('U')));
$this->owner->NextReviewDate = $nextDate;
$this->owner->write();
} else {
$this->owner->NextReviewDate = null;
$this->owner->write();
}
return (bool) $nextDate;
}
public function canRemind(Member $member = null) {
if (!$this->owner->obj("NextReviewDate")->exists()) {
return false;
}
// If today is not the date of the first reminder, return false
$config = SiteConfig::current_site_config();
$firstReview = $config->FirstReviewDaysBefore;
$now = SS_Datetime::now();
$notifyDate1 = date('Y-m-d', strtotime($this->owner->NextReviewDate . ' -' . $firstReview . ' days'));
// If today is not the first reminder date
if (!$notifyDate1 == $now->URLDate()) {
return false;
}
$options = $this->getOptions();
if (!$options) {
return false;
} elseif ($options->OwnerGroups()->count() == 0 && $options->OwnerUsers()->count() == 0) {
return false;
}
if (!$member) {
return true;
}
if ($member->inGroups($options->OwnerGroups())) {
return true;
}
if ($options->OwnerUsers()->find("ID", $member->ID)) {
return true;
}
return false;
}
/**
* Check if a review is due by a member for this owner.
*
@ -454,8 +512,10 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
$options = $this->getOptions();
if ($options->OwnerGroups()->count() == 0 && $options->OwnerUsers()->count() == 0) {
if (!$options) {
return false;
} elseif ($options->OwnerGroups()->count() == 0 && $options->OwnerUsers()->count() == 0) {
return false;
}
if (!$member) {

View File

@ -7,6 +7,7 @@ class ContentReviewLog extends DataObject
*/
private static $db = array(
"Note" => "Text",
"ReviewInfo" => "Text"
);
/**
@ -21,9 +22,10 @@ class ContentReviewLog extends DataObject
* @var array
*/
private static $summary_fields = array(
"Note" => array("title" => "Note"),
"Created" => array("title" => "Reviewed at"),
"Reviewer.Title" => array("title" => "Reviewed by"),
"ReviewInfo" => array("title" => "Review Information"),
"Note" => array("title" => "Note"),
"Created" => array("title" => "Reviewed at"),
"Reviewer.Title" => array("title" => "Reviewed by"),
);
/**

View File

@ -12,18 +12,50 @@ class ContentReviewEmails extends BuildTask
{
$compatibility = ContentReviewCompatability::start();
$now = SS_Datetime::now();
// First grab all the pages with a custom setting
$pages = Page::get()
->filter('NextReviewDate:LessThanOrEqual', SS_Datetime::now()->URLDate());
->filter('NextReviewDate:LessThanOrEqual', $now->URLDate());
$overduePages = $this->getOverduePagesForOwners($pages);
// Lets send one email to one owner with all the pages in there instead of no of pages
// of emails.
foreach ($overduePages as $memberID => $pages) {
$this->notifyOwner($memberID, $pages);
// Calculate whether today is the date a First or Second review should occur
$config = SiteConfig::current_site_config();
$firstReview = $config->FirstReviewDaysBefore;
$secondReview = $config->SecondReviewDaysBefore;
// Subtract the number of days prior to the review, from the current date
// Get all pages where the NextReviewDate is still in the future
$pendingPages = Page::get()->filter('NextReviewDate:GreaterThan', $now->URLDate());
// for each of these pages, check if today is the date the First or Second
// reminder should be sent, and if so, add it to the appropriate ArrayList
$firstReminderPages = new ArrayList();
$secondReminderPages = new ArrayList();
foreach ($pendingPages as $page) {
$notifyDate1 = date('Y-m-d', strtotime($page->NextReviewDate . ' -' . $firstReview . ' days'));
$notifyDate2 = date('Y-m-d', strtotime($page->NextReviewDate . ' -' . $secondReview . ' days'));
if ($notifyDate1 == $now->URLDate()) {
$firstReminderPages->push($page);
}
if ($notifyDate2 == $now->URLDate()) {
$secondReminderPages->push($page);
}
}
$overduePages = $this->getNotifiablePagesForOwners($pages);
// Send one email to one owner with all the pages in there instead of no of pages of emails.
foreach ($overduePages as $memberID => $pages) {
$this->notifyOwner($memberID, $pages, "due");
}
// Send an email to the generic address with any first or second reminders
$this->notifyTeam($firstReminderPages, $secondReminderPages);
ContentReviewCompatability::done($compatibility);
}
@ -32,15 +64,16 @@ class ContentReviewEmails extends BuildTask
*
* @return array
*/
protected function getOverduePagesForOwners(SS_list $pages)
protected function getNotifiablePagesForOwners(SS_list $pages)
{
$overduePages = array();
foreach ($pages as $page) {
if (!$page->canBeReviewedBy()) {
if (!$page->canRemind()) {
continue;
}
$option = $page->getOptions();
foreach ($option->ContentReviewOwners() as $owner) {
@ -54,26 +87,66 @@ class ContentReviewEmails extends BuildTask
return $overduePages;
}
/**
* Send an email to the configured team indicating which notices are at 'first reminder' or 'second reminder' status
* The 'days before due' value for each of these is configurable in settings.
* ie. a value of 30 for the 'first reminder' setting means a page with a review date exactly 30 days from due, will
* be present in the email sent to the configured address.
*/
protected function notifyTeam($firstReminderPages, $secondReminderPages) {
// Prepare variables
$siteConfig = SiteConfig::current_site_config();
$templateVariables1 = $this->getTemplateVariables('', $siteConfig, $firstReminderPages, 'reminder1');
$templateVariables2 = $this->getTemplateVariables('', $siteConfig, $secondReminderPages, 'reminder2');
// Build email
$email = new Email();
$email->setTo($siteConfig->ReviewReminderEmail);
$email->setFrom($siteConfig->ReviewFrom);
$subject = $siteConfig->ReviewSubjectReminder;
$email->setSubject($subject);
// Get user-editable body
$bodyFirstReminder = $this->getEmailBody($siteConfig, $templateVariables1, 'reminder1');
$bodySecondReminder = $this->getEmailBody($siteConfig, $templateVariables2, 'reminder2');
// Populate mail body with fixed template
$email->setTemplate($siteConfig->config()->content_review_reminder_template);
$email->populateTemplate($templateVariables1, $templateVariables2);
$email->populateTemplate(array(
'EmailBodyFirstReminder' => $bodyFirstReminder,
'EmailBodySecondReminder' => $bodySecondReminder,
'FirstReminderPages' => $firstReminderPages,
'SecondReminderPages' => $secondReminderPages,
));
$email->send();
}
/**
* @param int $ownerID
* @param array|SS_List $pages
* @param string $type
*/
protected function notifyOwner($ownerID, SS_List $pages)
protected function notifyOwner($ownerID, SS_List $pages, $type)
{
// Prepare variables
$siteConfig = SiteConfig::current_site_config();
$owner = Member::get()->byID($ownerID);
$templateVariables = $this->getTemplateVariables($owner, $siteConfig, $pages);
$templateVariables = $this->getTemplateVariables($owner, $siteConfig, $pages, $type);
// Build email
$email = new Email();
$email->setTo($owner->Email);
$email->setFrom($siteConfig->ReviewFrom);
$email->setSubject($siteConfig->ReviewSubject);
$subject = $siteConfig->ReviewSubject;
$email->setSubject($subject);
// Get user-editable body
$body = $this->getEmailBody($siteConfig, $templateVariables);
$body = $this->getEmailBody($siteConfig, $templateVariables, $type);
// Populate mail body with fixed template
$email->setTemplate($siteConfig->config()->content_review_template);
@ -83,6 +156,7 @@ class ContentReviewEmails extends BuildTask
'Recipient' => $owner,
'Pages' => $pages,
));
$email->send();
}
@ -94,9 +168,18 @@ class ContentReviewEmails extends BuildTask
*
* @return HTMLText
*/
protected function getEmailBody($config, $variables)
protected function getEmailBody($config, $variables, $type)
{
$template = SSViewer::fromString($config->ReviewBody);
if ($type == "reminder1") {
$template = SSViewer::fromString($config->ReviewBodyFirstReminder);
}
if ($type == "reminder2") {
$template = SSViewer::fromString($config->ReviewBodySecondReminder);
}
if ($type == "due") {
$template = SSViewer::fromString($config->ReviewBody);
}
$value = $template->process(new ArrayData($variables));
// Cast to HTML
@ -112,18 +195,33 @@ class ContentReviewEmails extends BuildTask
* @param Member $recipient
* @param SiteConfig $config
* @param SS_List $pages
* @param string $type
*
* @return array
*/
protected function getTemplateVariables($recipient, $config, $pages)
protected function getTemplateVariables($recipient = null, $config, $pages)
{
return array(
'Subject' => $config->ReviewSubject,
'PagesCount' => $pages->count(),
'FromEmail' => $config->ReviewFrom,
'ToFirstName' => $recipient->FirstName,
'ToSurname' => $recipient->Surname,
'ToEmail' => $recipient->Email,
);
if ($recipient != null) {
return array(
'Subject' => $config->ReviewSubject,
'PagesCount' => $pages->count(),
'FromEmail' => $config->ReviewFrom,
'ToFirstName' => $recipient->FirstName,
'ToSurname' => $recipient->Surname,
'ToEmail' => $recipient->Email
);
} else {
return array(
'Subject' => $config->ReviewSubjectReminder,
'FirstReminderPagesCount' => $pages->count(),
'SecondReminderPagesCount' => $pages->count(),
'FromEmail' => $config->ReviewFrom,
'ToEmail' => $config->ReviewReminderEmail
);
}
}
}

View File

@ -11,6 +11,13 @@
margin: 0 0 4px 10px;
}
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .review-notes .quick-info {
padding: 6px;
margin: 4px 8px;
border: 1px solid #b3b3b3;
border-radius: 4px;
}
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel .review-notes .cms-sitetree-information p.meta-info {
color: #f46b00;
font-weight: bold;

View File

@ -10,8 +10,12 @@ en:
DEFAULTSETTINGSHELP: 'These content review settings will apply to all pages that does not have specific Content Review schedule.'
DISABLE: 'Disable content review'
EMAILFROM: 'From email address'
EMAILSUBJECT: 'Subject line'
EMAILTEMPLATE: 'Email template'
EMAILSUBJECTREMINDER: 'Subject line - Reminder'
EMAILSUBJECT: 'Subject line - Review due'
EMAILTEMPLATEFIRSTREMINDER: 'Email body - First reminder'
EMAILTEMPLATESECONDREMINDER: 'Email body - Second reminder'
EMAILTEMPLATE: 'Email body - Review due'
FIRSTREVIEWDAYSBEFORE: 'First review reminder # days before final review'
INHERIT: 'Inherit from parent page'
MARKREVIEWED: 'Mark as reviewed'
NEXTREVIEWDATADESCRIPTION: 'Leave blank for no review'
@ -25,9 +29,11 @@ en:
REVIEWFREQUENCY: 'Review frequency'
REVIEWFREQUENCYDESCRIPTION: 'The review date will be set to this far in the future whenever the page is published'
REVIEWHEADER: 'Content review'
REVIEWINFO: 'Review information'
REVIEWNOTES: 'Review notes'
REVIEWSUCCESSFUL: 'Content reviewed successfully'
SAVE: Save
SECONDREVIEWDAYSBEFORE: 'Second review reminder # days before final review'
SETTINGSFROM: 'Options are'
ContentReviewEmails:
REVIEWPAGELINK: 'Review the page in the CMS'

View File

@ -6,6 +6,20 @@
[![License](http://img.shields.io/packagist/l/silverstripe/contentreview.svg?style=flat-square)](license.md)
![helpfulrobot](https://helpfulrobot.io/silverstripe/contentreview/badge)
**Note:** _Govt.nz customisations to this module are as follows_
Prior to the due date, the content review task will send an email to the address configured in the settings.
This email contains two lists:
- a list of pages whose review date is exactly 1 month in the future.
- another list of pages whose review date is exactly 1 week in the future.
Both these times (1 month and 1 week) are defaults only, and can be configured in the settings area.
The email body for these reminder emails is also configurable in the settings.
---
This module helps keep your website content accurate and up-to-date, which keeps your users happy.
It does so by sending reviewers reminder emails to go in and check the content. For a reviewer this

View File

@ -1,10 +1,22 @@
<p>This is a list of dynamic variables that you can use in the email templates.</p>
<p>This is a list of dynamic variables that you can use in the <strong>First</strong> and <strong>Second</strong> reminder email templates.</p>
<ul>
<li>&#36;Subject - Email subject line</li>
<li>&#36;PagesCount - Number of pages pending review</li>
<li>&#36;FromEmail - Sender email address</li>
<li>&#36;ToFirstName - The email receivers first name</li>
<li>&#36;ToSurname - The email receivers surname</li>
<li>&#36;ToEmail - The email receivers email</li>
<li>&#36;Subject - Email subject line</li>
<li>&#36;FirstReminderPagesCount - Number of pages approaching review date (first reminder)</li>
<li>&#36;SecondReminderPagesCount - Number of pages approaching review date (second reminder)</li>
<li>&#36;FromEmail - Sender email address</li>
<li>&#36;ToEmail - The email receivers email</li>
</ul>
<br>
<p>This is a list of dynamic variables that you can use in the <strong>Due</strong> email template.</p>
<ul>
<li>&#36;Subject - Email subject line</li>
<li>&#36;PagesCount - Number of pages pending review</li>
<li>&#36;FromEmail - Sender email address</li>
<li>&#36;ToFirstName - The email receivers first name</li>
<li>&#36;ToSurname - The email receivers surname</li>
<li>&#36;ToEmail - The email receivers email</li>
</ul>

View File

@ -0,0 +1,41 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head></head>
<body id="Content">
<table cellspacing="1" cellpadding="10">
<tbody>
<tr>
<td scope="row" colspan="2" class="typography">
$EmailBodyFirstReminder
</td>
</tr>
<% loop $FirstReminderPages %>
<tr>
<td valign="top">$Title</td>
<td><a href="{$BaseURL}admin/pages/edit/show/$ID"><% _t('ContentReviewEmails.REVIEWPAGELINK','Review the page in the CMS') %></a><br />
<a href="$AbsoluteLink"><% _t('ContentReviewEmails.VIEWPUBLISHEDLINK','View this page on the website') %></a>
</td>
</tr>
<% end_loop %>
</tbody>
</table>
<table id="Content" cellspacing="1" cellpadding="10">
<tbody>
<tr>
<td scope="row" colspan="2" class="typography">
$EmailBodySecondReminder
</td>
</tr>
<% loop $SecondReminderPages %>
<tr>
<td valign="top">$Title</td>
<td><a href="{$BaseURL}admin/pages/edit/show/$ID"><% _t('ContentReviewEmails.REVIEWPAGELINK','Review the page in the CMS') %></a><br />
<a href="$AbsoluteLink"><% _t('ContentReviewEmails.VIEWPUBLISHEDLINK','View this page on the website') %></a>
</td>
</tr>
<% end_loop %>
</tbody>
</table>
</body>
</html>