API CMS Editable notification templates

PSR-2 Coding fixup
This commit is contained in:
Damian Mooyman 2015-11-17 14:17:54 +13:00
parent 6138d0b927
commit d9729bf7f1
14 changed files with 320 additions and 141 deletions

View File

@ -6,7 +6,6 @@
*/
class ContentReviewCMSExtension extends LeftAndMainExtension
{
/**
* @var array
*/

View File

@ -9,23 +9,49 @@
class ContentReviewDefaultSettings extends DataExtension
{
/**
* @config
*
* @var array
*/
private static $db = array(
"ReviewPeriodDays" => "Int",
'ReviewPeriodDays' => 'Int',
'ReviewFrom' => 'Varchar(255)',
'ReviewSubject' => 'Varchar(255)',
'ReviewBody' => 'HTMLText',
);
/**
* @config
*
* @var array
*/
private static $defaults = array(
'ReviewSubject' => 'Page(s) are due for content review',
'ReviewBody' => '<h2>Page(s) due for review</h2><p>There are $PagesCount pages that are due for review today by you.</p>',
);
/**
* @config
*
* @var array
*/
private static $many_many = array(
"ContentReviewGroups" => "Group",
"ContentReviewUsers" => "Member",
'ContentReviewGroups' => 'Group',
'ContentReviewUsers' => 'Member',
);
/**
* Template to use for content review emails.
*
* This should contain an $EmailBody variable as a placeholder for the user-defined copy
*
* @config
*
* @var string
*/
private static $content_review_template = 'ContentReviewEmail';
/**
* @return string
*/
public function getOwnerNames()
@ -33,14 +59,14 @@ class ContentReviewDefaultSettings extends DataExtension
$names = array();
foreach ($this->OwnerGroups() as $group) {
$names[] = $group->getBreadcrumbs(" > ");
$names[] = $group->getBreadcrumbs(' > ');
}
foreach ($this->OwnerUsers() as $group) {
$names[] = $group->getName();
}
return implode(", ", $names);
return implode(', ', $names);
}
/**
@ -48,7 +74,7 @@ class ContentReviewDefaultSettings extends DataExtension
*/
public function OwnerGroups()
{
return $this->owner->getManyManyComponents("ContentReviewGroups");
return $this->owner->getManyManyComponents('ContentReviewGroups');
}
/**
@ -56,54 +82,78 @@ class ContentReviewDefaultSettings extends DataExtension
*/
public function OwnerUsers()
{
return $this->owner->getManyManyComponents("ContentReviewUsers");
return $this->owner->getManyManyComponents('ContentReviewUsers');
}
/**
*
* @param FieldList $fields
*/
public function updateCMSFields(FieldList $fields)
{
$helpText = LiteralField::create("ContentReviewHelp", _t("ContentReview.DEFAULTSETTINGSHELP", "These content review settings will apply to all pages that does not have specific Content Review schedule."));
$helpText = LiteralField::create(
'ContentReviewHelp',
_t(
'ContentReview.DEFAULTSETTINGSHELP',
'These content review settings will apply to all pages that does not have specific Content Review schedule.'
)
);
$fields->addFieldToTab("Root.ContentReview", $helpText);
$fields->addFieldToTab('Root.ContentReview', $helpText);
$reviewFrequency = DropdownField::create("ReviewPeriodDays", _t("ContentReview.REVIEWFREQUENCY", "Review frequency"), SiteTreeContentReview::get_schedule())
->setDescription(_t("ContentReview.REVIEWFREQUENCYDESCRIPTION", "The review date will be set to this far in the future whenever the page is published"));
$reviewFrequency = DropdownField::create(
'ReviewPeriodDays',
_t('ContentReview.REVIEWFREQUENCY', 'Review frequency'),
SiteTreeContentReview::get_schedule()
)
->setDescription(_t(
'ContentReview.REVIEWFREQUENCYDESCRIPTION',
'The review date will be set to this far in the future whenever the page is published'
));
$fields->addFieldToTab("Root.ContentReview", $reviewFrequency);
$fields->addFieldToTab('Root.ContentReview', $reviewFrequency);
$users = Permission::get_members_by_permission(array(
"CMS_ACCESS_CMSMain",
"ADMIN",
'CMS_ACCESS_CMSMain',
'ADMIN',
));
$usersMap = $users->map("ID", "Title")->toArray();
$usersMap = $users->map('ID', 'Title')->toArray();
asort($usersMap);
$userField = ListboxField::create("OwnerUsers", _t("ContentReview.PAGEOWNERUSERS", "Users"), $usersMap)
$userField = ListboxField::create('OwnerUsers', _t('ContentReview.PAGEOWNERUSERS', 'Users'), $usersMap)
->setMultiple(true)
->setAttribute("data-placeholder", _t("ContentReview.ADDUSERS", "Add users"))
->setDescription(_t("ContentReview.OWNERUSERSDESCRIPTION", "Page owners that are responsible for reviews"));
->setAttribute('data-placeholder', _t('ContentReview.ADDUSERS', 'Add users'))
->setDescription(_t('ContentReview.OWNERUSERSDESCRIPTION', 'Page owners that are responsible for reviews'));
$fields->addFieldToTab("Root.ContentReview", $userField);
$fields->addFieldToTab('Root.ContentReview', $userField);
$groupsMap = array();
foreach (Group::get() as $group) {
// Listboxfield values are escaped, use ASCII char instead of &raquo;
$groupsMap[$group->ID] = $group->getBreadcrumbs(" > ");
$groupsMap[$group->ID] = $group->getBreadcrumbs(' > ');
}
asort($groupsMap);
$groupField = ListboxField::create("OwnerGroups", _t("ContentReview.PAGEOWNERGROUPS", "Groups"), $groupsMap)
$groupField = ListboxField::create('OwnerGroups', _t('ContentReview.PAGEOWNERGROUPS', 'Groups'), $groupsMap)
->setMultiple(true)
->setAttribute("data-placeholder", _t("ContentReview.ADDGROUP", "Add groups"))
->setDescription(_t("ContentReview.OWNERGROUPSDESCRIPTION", "Page owners that are responsible for reviews"));
->setAttribute('data-placeholder', _t('ContentReview.ADDGROUP', 'Add groups'))
->setDescription(_t('ContentReview.OWNERGROUPSDESCRIPTION', 'Page owners that are responsible for reviews'));
$fields->addFieldToTab("Root.ContentReview", $groupField);
$fields->addFieldToTab('Root.ContentReview', $groupField);
// 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')),
LiteralField::create('TemplateHelp', $this->owner->renderWith('ContentReviewAdminHelp')),
)
);
}
/**
@ -112,7 +162,64 @@ class ContentReviewDefaultSettings extends DataExtension
*
* @return ArrayList
*/
public function ContentReviewOwners() {
public function ContentReviewOwners()
{
return SiteTreeContentReview::merge_owners($this->OwnerGroups(), $this->OwnerUsers());
}
/**
* Get the review body, falling back to the default if left blank.
*
* @return string HTML text
*/
public function getReviewBody()
{
return $this->getWithDefault('ReviewBody');
}
/**
* Get the review subject line, falling back to the default if left blank.
*
* @return string plain text value
*/
public function getReviewSubject()
{
return $this->getWithDefault('ReviewSubject');
}
/**
* Get the "from" field for review emails.
*
* @return string
*/
public function getReviewFrom()
{
$from = $this->owner->getField('ReviewFrom');
if ($from) {
return $from;
}
// Fall back to admin email
return Config::inst()->get('Email', 'admin_email');
}
/**
* Get the value of a user-configured field, falling back to the default if left blank.
*
* @param string $field
*
* @return string
*/
protected function getWithDefault($field)
{
$value = $this->owner->getField($field);
if ($value) {
return $value;
}
// fallback to default value
$defaults = $this->owner->config()->defaults;
if (isset($defaults[$field])) {
return $defaults[$field];
}
}
}

View File

@ -122,7 +122,7 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
Requirements::css("contentreview/css/contentreview.css");
$reviewTitle = LiteralField::create(
"ReviewContentNotesLabel",
"ReviewContentNotesLabel",
"<label class=\"left\" for=\"Form_EditForm_ReviewNotes\">" . _t("ContentReview.CONTENTREVIEW", "Content due for review") . "</label>"
);

View File

@ -89,7 +89,7 @@ class PagesDueForReviewReport extends SS_Report
),
"ContentReviewType" => array(
"title" => "Settings are",
"formatting" => function ($value, $item) use ($linkPath,$linkQuery) {
"formatting" => function ($value, $item) use ($linkPath, $linkQuery) {
if ($item->ContentReviewType == "Inherit") {
$options = $item->getOptions();
if ($options && $options instanceof SiteConfig) {
@ -117,7 +117,8 @@ class PagesDueForReviewReport extends SS_Report
* @param array $params
*
* @return SS_List
*/public function sourceRecords($params = array())
*/
public function sourceRecords($params = array())
{
Versioned::reading_stage("Stage");

View File

@ -5,13 +5,6 @@
*/
class ContentReviewEmails extends BuildTask
{
/**
* Holds a cached array for looking up members via their ID.
*
* @var array
*/
protected static $member_cache = array();
/**
* @param SS_HTTPRequest $request
*/
@ -19,10 +12,9 @@ class ContentReviewEmails extends BuildTask
{
$compatibility = ContentReviewCompatability::start();
$now = class_exists("SS_Datetime") ? SS_Datetime::now()->URLDate() : SSDatetime::now()->URLDate();
// First grab all the pages with a custom setting
$pages = Page::get("Page")->where("\"SiteTree\".\"NextReviewDate\" <= '{$now}'");
$pages = Page::get()
->filter('NextReviewDate:LessThanOrEqual', SS_Datetime::now()->URLDate());
$overduePages = $this->getOverduePagesForOwners($pages);
@ -52,10 +44,6 @@ class ContentReviewEmails extends BuildTask
$option = $page->getOptions();
foreach ($option->ContentReviewOwners() as $owner) {
if (!isset(self::$member_cache[$owner->ID])) {
self::$member_cache[$owner->ID] = $owner;
}
if (!isset($overduePages[$owner->ID])) {
$overduePages[$owner->ID] = new ArrayList();
}
@ -73,22 +61,69 @@ class ContentReviewEmails extends BuildTask
*/
protected function notifyOwner($ownerID, SS_List $pages)
{
$owner = self::$member_cache[$ownerID];
$sender = Security::findAnAdministrator();
$senderEmail = ($sender->Email) ? $sender->Email : Config::inst()->get("Email", "admin_email");
// Prepare variables
$siteConfig = SiteConfig::current_site_config();
$owner = Member::get()->byID($ownerID);
$templateVariables = $this->getTemplateVariables($owner, $siteConfig, $pages);
$subject = _t("ContentReviewEmails.SUBJECT", "Page(s) are due for content review");
// Build email
$email = new Email();
$email->setTo($owner->Email);
$email->setFrom($senderEmail);
$email->setTemplate("ContentReviewEmail");
$email->setSubject($subject);
$email->populateTemplate(array(
"Recipient" => $owner,
"Sender" => $sender,
"Pages" => $pages,
));
$email->setFrom($siteConfig->ReviewFrom);
$email->setSubject($siteConfig->ReviewSubject);
// Get user-editable body
$body = $this->getEmailBody($siteConfig, $templateVariables);
// Populate mail body with fixed template
$email->setTemplate($siteConfig->config()->content_review_template);
$email->populateTemplate($templateVariables);
$email->populateTemplate(array(
'EmailBody' => $body,
'Recipient' => $owner,
'Pages' => $pages,
));
$email->send();
}
/**
* Get string value of HTML body with all variable evaluated.
*
* @param SiteConfig $config
* @param array List of safe template variables to expose to this template
*
* @return HTMLText
*/
protected function getEmailBody($config, $variables)
{
$template = SSViewer::fromString($config->ReviewBody);
$value = $template->process(new ArrayData($variables));
// Cast to HTML
return DBField::create_field('HTMLText', (string) $value);
}
/**
* Gets list of safe template variables and their values which can be used
* in both the static and editable templates.
*
* {@see ContentReviewAdminHelp.ss}
*
* @param Member $recipient
* @param SiteConfig $config
* @param SS_List $pages
*
* @return array
*/
protected function getTemplateVariables($recipient, $config, $pages)
{
return array(
'Subject' => $config->ReviewSubject,
'PagesCount' => $pages->count(),
'FromEmail' => $config->ReviewFrom,
'ToFirstName' => $recipient->FirstName,
'ToSurname' => $recipient->Surname,
'ToEmail' => $recipient->Email,
);
}
}

BIN
docs/.DS_Store vendored

Binary file not shown.

BIN
docs/en/.DS_Store vendored

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

View File

@ -49,6 +49,11 @@ In order for the contentreview module to send emails, you need to *either*:
* Setup the DailyTask script to run daily via cron. See framework/tasks/ScheduledTask.php for more information on setup.
* Install the queuedjobs module, and follow the configuration steps to create a cron job for that module. Once installed, you can just run dev/build to have a job created, which will run at 9am every day by default.
Global settings can be configured via the global settings admin in the CMS under the "Content Review" tab.
This includes global groups, users, as well as a template editor that supports a limited number of variables.
![settings](docs/en/images/content-review-siteconfig-settings.png)
## Usage
To set up content review schedules you need to log in as a user with the 'Set content owners and review dates' permission. This can either

View File

@ -0,0 +1,10 @@
<p>This is a list of dynamic variables that you can use in the 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>
</ul>

View File

@ -6,8 +6,7 @@
<tbody>
<tr>
<td scope="row" colspan="2" class="typography">
<h2><% _t('ContentReviewEmails.EMAIL_HEADING','Page(s) due for review') %></h2>
<p>There are $Pages.Count pages that are due for review today by you.</p>
$EmailBody
</td>
</tr>
<% loop Pages %>

View File

@ -6,7 +6,6 @@
*/
abstract class ContentReviewBaseTest extends FunctionalTest
{
/**
* @var bool
*/

View File

@ -8,39 +8,57 @@ class ContentReviewNotificationTest extends SapphireTest
/**
* @var string
*/
public static $fixture_file = "contentreview/tests/ContentReviewTest.yml";
public static $fixture_file = 'contentreview/tests/ContentReviewTest.yml';
public function setUp()
{
parent::setUp();
// Hack to ensure only desired siteconfig is scaffolded
$desiredID = $this->idFromFixture('SiteConfig', 'mysiteconfig');
foreach (SiteConfig::get()->exclude('ID', $desiredID) as $config) {
$config->delete();
}
}
/**
* @var array
*/
protected $requiredExtensions = array(
"SiteTree" => array("SiteTreeContentReview"),
"Group" => array("ContentReviewOwner"),
"Member" => array("ContentReviewOwner"),
"CMSPageEditController" => array("ContentReviewCMSExtension"),
"SiteConfig" => array("ContentReviewDefaultSettings"),
'SiteTree' => array('SiteTreeContentReview'),
'Group' => array('ContentReviewOwner'),
'Member' => array('ContentReviewOwner'),
'CMSPageEditController' => array('ContentReviewCMSExtension'),
'SiteConfig' => array('ContentReviewDefaultSettings'),
);
public function testContentReviewEmails()
{
SS_Datetime::set_mock_now("2010-02-24 12:00:00");
SS_Datetime::set_mock_now('2010-02-24 12:00:00');
/** @var Page|SiteTreeContentReview $childParentPage */
$childParentPage = $this->objFromFixture("Page", "contact");
$childParentPage->NextReviewDate = "2010-02-23";
$childParentPage = $this->objFromFixture('Page', 'contact');
$childParentPage->NextReviewDate = '2010-02-23';
$childParentPage->write();
$task = new ContentReviewEmails();
$task->run(new SS_HTTPRequest("GET", "/dev/tasks/ContentReviewEmails"));
$task->run(new SS_HTTPRequest('GET', '/dev/tasks/ContentReviewEmails'));
$expectedSubject = _t("ContentReviewEmails.SUBJECT", "Page(s) are due for content review");
$email = $this->findEmail("author@example.com", null, $expectedSubject);
// Set template variables (as per variable case)
$ToEmail = 'author@example.com';
$Subject = 'Please log in to review some content!';
$PagesCount = 3;
$ToFirstName = 'Test';
$email = $this->findEmail($ToEmail, null, $Subject);
$this->assertNotNull($email, "Email haven't been sent.");
$this->assertContains("There are 3 pages that are due for review today by you.", $email["htmlContent"]);
$this->assertContains("Staff", $email["htmlContent"]);
$this->assertContains("Contact Us", $email["htmlContent"]);
$this->assertContains("Contact Us Child", $email["htmlContent"]);
$this->assertContains(
"<h1>$Subject</h1><p>There are $PagesCount pages that are due for review today by you, $ToFirstName.</p><p>This email was sent to $ToEmail</p>",
$email['htmlContent']
);
$this->assertContains('Staff', $email['htmlContent']);
$this->assertContains('Contact Us', $email['htmlContent']);
$this->assertContains('Contact Us Child', $email['htmlContent']);
SS_Datetime::clear_mock_now();
}

View File

@ -1,76 +1,82 @@
SiteConfig:
mysiteconfig:
ReviewFrom: sender@silverstripe.com
ReviewSubject: 'Please log in to review some content!'
ReviewBody: '<h1>$Subject</h1><p>There are $PagesCount pages that are due for review today by you, $ToFirstName.</p><p>This email was sent to $ToEmail</p>'
Permission:
cmsmain1:
Code: CMS_ACCESS_CMSMain
cmsmain2:
Code: CMS_ACCESS_CMSMain
setreviewdates:
Code: EDIT_CONTENT_REVIEW_FIELDS
workflowadmin1:
Code: IS_WORKFLOW_ADMIN
workflowadmin2:
Code: IS_WORKFLOW_ADMIN
cmsmain1:
Code: CMS_ACCESS_CMSMain
cmsmain2:
Code: CMS_ACCESS_CMSMain
setreviewdates:
Code: EDIT_CONTENT_REVIEW_FIELDS
workflowadmin1:
Code: IS_WORKFLOW_ADMIN
workflowadmin2:
Code: IS_WORKFLOW_ADMIN
Group:
editorgroup:
Title: Edit existing pages
Code: editorgroup
Permissions: =>Permission.cmsmain1,=>Permission.workflowadmin1,=>Permission.setreviewdates
authorgroup:
Title: Author existing pages
Code: authorgroup
Permissions: =>Permission.cmsmain2,=>Permission.workflowadmin2
editorgroup:
Title: Edit existing pages
Code: editorgroup
Permissions: =>Permission.cmsmain1,=>Permission.workflowadmin1,=>Permission.setreviewdates
authorgroup:
Title: Author existing pages
Code: authorgroup
Permissions: =>Permission.cmsmain2,=>Permission.workflowadmin2
Member:
author:
FirstName: Test
Surname: Author
Email: author@example.com
Groups: =>Group.authorgroup
editor:
FirstName: Test
Surname: Editor
Groups: =>Group.editorgroup
visitor:
FirstName: Kari
Surname: Visitor
Email: visitor@example.com
author:
FirstName: Test
Surname: Author
Email: author@example.com
Groups: =>Group.authorgroup
editor:
FirstName: Test
Surname: Editor
Groups: =>Group.editorgroup
visitor:
FirstName: Kari
Surname: Visitor
Email: visitor@example.com
Page:
# Cant be reviewed, no owners
home:
Title: Home
ContentReviewType: Custom
NextReviewDate: 2010-02-01
ReviewPeriodDays: 10
home:
Title: Home
ContentReviewType: Custom
NextReviewDate: 2010-02-01
ReviewPeriodDays: 10
# Cant be reviewed, no owners
about:
Title: About Us
ContentReviewType: Custom
NextReviewDate: 2010-02-07
ReviewPeriodDays: 10
staff:
Title: Staff
ContentReviewType: Custom
NextReviewDate: 2010-02-14
ReviewPeriodDays: 10
ContentReviewUsers: =>Member.author
contact:
Title: Contact Us
ContentReviewType: Custom
ReviewPeriodDays: 10
NextReviewDate: 2010-02-21
ContentReviewGroups: =>Group.authorgroup
contact-child:
Title: Contact Us Child
ContentReviewType: Inherit
ParentID: =>Page.contact
about:
Title: About Us
ContentReviewType: Custom
NextReviewDate: 2010-02-07
ReviewPeriodDays: 10
staff:
Title: Staff
ContentReviewType: Custom
NextReviewDate: 2010-02-14
ReviewPeriodDays: 10
ContentReviewUsers: =>Member.author
contact:
Title: Contact Us
ContentReviewType: Custom
ReviewPeriodDays: 10
NextReviewDate: 2010-02-21
ContentReviewGroups: =>Group.authorgroup
contact-child:
Title: Contact Us Child
ContentReviewType: Inherit
ParentID: =>Page.contact
# Cant be reviewed, no NextReviewDate
no-review:
Title: Page without review date
ContentReviewType: Custom
ContentReviewUsers: =>Member.author
no-review:
Title: Page without review date
ContentReviewType: Custom
ContentReviewUsers: =>Member.author
# Cant be reviewed, no NextReviewDate
group-owned:
Title: Page owned by group
ContentReviewType: Custom
ContentReviewGroups: =>Group.authorgroup
group-owned:
Title: Page owned by group
ContentReviewType: Custom
ContentReviewGroups: =>Group.authorgroup