Adding default setting to the SiteConfig, broken email and tests though.

This commit is contained in:
Stig Lindqvist 2014-02-20 18:05:14 +13:00
parent f673815eff
commit 78d84a57c6
5 changed files with 344 additions and 52 deletions

View File

@ -9,4 +9,7 @@ Member:
- ContentReviewOwner - ContentReviewOwner
CMSPageEditController: CMSPageEditController:
extensions: extensions:
- ContentReviewCMSExtension - ContentReviewCMSExtension
SiteConfig:
extensions:
- ContentReviewDefaultSettings

View File

@ -4,10 +4,19 @@
* Daily task to send emails to the owners of content items * Daily task to send emails to the owners of content items
* when the review date rolls around * when the review date rolls around
* *
*
* @todo create a page cache for the inherited so that we dont unneccesary need to look up parent pages
* @package contentreview * @package contentreview
*/ */
class ContentReviewEmails extends BuildTask { 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 * @param SS_HTTPRequest $request
@ -21,52 +30,160 @@ class ContentReviewEmails extends BuildTask {
$now = class_exists('SS_Datetime') ? SS_Datetime::now()->URLDate() : SSDatetime::now()->URLDate(); $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') $pages = Page::get('Page')
->leftJoin('Group_SiteTreeContentReview', '"SiteTree"."ID" = "OwnerGroups"."SiteTreeID"', 'OwnerGroups') ->leftJoin('Group_SiteTreeContentReview', '"SiteTree"."ID" = "OwnerGroups"."SiteTreeID"', 'OwnerGroups')
->leftJoin('Member_SiteTreeContentReview', '"SiteTree"."ID" = "OwnerUsers"."SiteTreeID"', "OwnerUsers") ->leftJoin('Member_SiteTreeContentReview', '"SiteTree"."ID" = "OwnerUsers"."SiteTreeID"', "OwnerUsers")
->where('"SiteTree"."NextReviewDate" <= \''.$now.'\' AND' .' ("OwnerGroups"."ID" IS NOT NULL OR "OwnerUsers"."ID" IS NOT NULL)') ->where('"SiteTree"."ContentReviewType" != \'Custom\' AND "SiteTree"."NextReviewDate" <= \''.$now.'\' AND' .
' ("OwnerGroups"."ID" IS NOT NULL OR "OwnerUsers"."ID" IS NOT NULL)')
; ;
if ($pages && $pages->Count()) { $this->notify($pages);
foreach($pages as $page) {
$owners = $page->ContentReviewOwners(); // Then grab all the pages with that inherits their settings
if(!$owners->count()) {
continue; $pages = Page::get('Page')
} ->leftJoin('Group_SiteTreeContentReview', '"SiteTree"."ID" = "OwnerGroups"."SiteTreeID"', 'OwnerGroups')
$sender = Security::findAnAdministrator(); ->leftJoin('Member_SiteTreeContentReview', '"SiteTree"."ID" = "OwnerUsers"."SiteTreeID"', "OwnerUsers")
->where('"SiteTree"."ContentReviewType" = \'Inherit\'')
foreach($owners as $recipient) { ;
$subject = sprintf(_t('ContentReviewEmails.SUBJECT', 'Page %s due for content review'), $page->Title);
$overduePages = $this->findInheritedSettings($pages);
$email = new Email();
$email->setTo($recipient->Email); // Lets send one email to one owner with all the pages in there instead of no of pages of emails
$email->setFrom(($sender->Email) ? $sender->Email : Email::getAdminEmail()); foreach($overduePages as $memberID => $pages) {
$email->setTemplate('ContentReviewEmails'); $this->notify_user($memberID, $pages);
$email->setSubject($subject);
$email->populateTemplate(array(
"PageCMSLink" => "admin/pages/edit/show/".$page->ID,
"Recipient" => $recipient,
"Sender" => $sender,
"Page" => $page,
"StageSiteLink" => Controller::join_links($page->Link(), "?stage=Stage"),
"LiveSiteLink" => Controller::join_links($page->Link(), "?stage=Live"),
));
$email->send();
$message = '<strong>'._t('ContentReviewEmails.EMAIL_HEADING','Page due for review').'</strong><br/>'.
'The page "'.$page->Title.'" is due for review today by you.<br/>
<a href="admin/pages/edit/show/'.$page->ID.'">'. _t('ContentReviewEmails.REVIEWPAGELINK','Review the page in the CMS') .'</a> &mdash;
<a href="#">'. _t('ContentReviewEmails.VIEWPUBLISHEDLINK','View this page on the website') .'</a>';
if(class_exists('Notification')) {
Notification::notify($recipient, $message);
}
}
}
} }
// Revert subsite filter (if installed) // Revert subsite filter (if installed)
if (ClassInfo::exists('Subsite')) { if(ClassInfo::exists('Subsite')) {
Subsite::$disable_subsite_filter = $oldSubsiteState; Subsite::$disable_subsite_filter = $oldSubsiteState;
} }
} }
/**
*
* @param SS_list $pages
* @return type
*/
protected function findInheritedSettings(SS_list $pages) {
$overduePages = array();
foreach($pages as $page) {
$settings = $this->findContentSettingFor($page);
// This page has a parent with the 'Disabled' option
if(!$settings) {
continue;
}
$owners = $settings->ContentReviewOwners();
if(!$owners->count()) {
continue;
}
if(!$settings->ReviewPeriodDays) {
continue;
}
// Calculate next time this page should be reviewed from the LastEdited datea
$nextReviewUnixSec = strtotime($page->LastEdited . ' + '.$settings->ReviewPeriodDays . ' days');
if($nextReviewUnixSec > time()) {
continue;
}
foreach($owners as $owner) {
if(!isset(self::$member_cache[$owner->ID])) {
self::$member_cache[$owner->ID] = $owner;
}
if(!isset($overduePages[$owner->ID])) {
$overduePages[$owner->ID] = array();
}
$overduePages[$owner->ID][] = $page;
}
}
return $overduePages;
}
/**
*
* @param SiteTree $page
* @return DataObject or false if no settings found
*/
protected function findContentSettingFor($page) {
while($parent = $page->Parent()) {
// Root page, use siteconfig
if(!$parent->exists()) {
return SiteConfig::current_site_config();
}
if($parent->ContentReviewType == 'Custom') {
return $parent;
}
if($parent->ContentReviewType == 'Disabled') {
return false;
}
$page = $parent;
}
throw new Exception('This shouldnt really happen, as usual.');
}
/**
*
* @param int $owner
* @param array $pages
*/
protected function notify_user($ownerID, array $pages) {
$owner = self::$member_cache[$ownerID];
echo $owner->Email.PHP_EOL;
foreach($pages as $page) {
echo $page->Title.PHP_EOL;
}
}
/**
*
* @param SS_List $pages
* @return void
*/
protected function notify(SS_List $pages) {
if(!$pages) {
return;
}
if(!$pages->Count()) {
return;
}
foreach($pages as $page) {
// Resolve the content owner groups and members to a single list of members
$owners = $page->ContentReviewOwners();
if(!$owners->count()) {
continue;
}
$sender = Security::findAnAdministrator();
foreach($owners as $recipient) {
$subject = sprintf(_t('ContentReviewEmails.SUBJECT', 'Page %s due for content review'), $page->Title);
$email = new Email();
$email->setTo($recipient->Email);
$email->setFrom(($sender->Email) ? $sender->Email : Email::getAdminEmail());
$email->setTemplate('ContentReviewEmails');
$email->setSubject($subject);
$email->populateTemplate(array(
"PageCMSLink" => "admin/pages/edit/show/".$page->ID,
"Recipient" => $recipient,
"Sender" => $sender,
"Page" => $page,
"StageSiteLink" => Controller::join_links($page->Link(), "?stage=Stage"),
"LiveSiteLink" => Controller::join_links($page->Link(), "?stage=Live"),
));
//$email->send();
$message = '<strong>'._t('ContentReviewEmails.EMAIL_HEADING','Page due for review').'</strong><br/>'.
'The page "'.$page->Title.'" is due for review today by you.<br/>
<a href="admin/pages/edit/show/'.$page->ID.'">'. _t('ContentReviewEmails.REVIEWPAGELINK','Review the page in the CMS') .'</a> &mdash;
<a href="#">'. _t('ContentReviewEmails.VIEWPUBLISHEDLINK','View this page on the website') .'</a>';
if(class_exists('Notification')) {
// Notification::notify($recipient, $message);
}
// echo $page->Title.' - '.$recipient->Email.PHP_EOL;
}
}
}
} }

View File

@ -0,0 +1,110 @@
<?php
/**
* This extensions add a default schema for new pages and pages without a content review setting
*
*/
class ContentReviewDefaultSettings extends DataExtension {
/**
*
* @var array
*/
private static $db = array(
"ReviewPeriodDays" => "Int",
);
/**
*
* @var array
*/
private static $many_many = array(
'ContentReviewGroups' => 'Group',
'ContentReviewUsers' => 'Member'
);
/**
* @return ManyManyList
*/
public function OwnerGroups() {
return $this->owner->getManyManyComponents('ContentReviewGroups');
}
/**
* @return ManyManyList
*/
public function OwnerUsers() {
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.'));
$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'));
$fields->addFieldToTab('Root.ContentReview', $reviewFrequency);
$users = Permission::get_members_by_permission(array("CMS_ACCESS_CMSMain", "ADMIN"));
$usersMap = $users->map('ID', 'Title')->toArray();
asort($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'));
$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(' > ');
}
asort($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'));
$fields->addFieldToTab('Root.ContentReview', $groupField);
}
/**
* Get all Members that are default Content Owners
*
* This includes checking group hierarchy and adding any direct users
*
* @return \ArrayList
*/
public function ContentReviewOwners() {
$contentReviewOwners = new ArrayList();
$toplevelGroups = $this->OwnerGroups();
if($toplevelGroups->count()) {
$groupIDs = array();
foreach($toplevelGroups as $group) {
$familyIDs = $group->collateFamilyIDs();
if(is_array($familyIDs)) {
$groupIDs = array_merge($groupIDs, array_values($familyIDs));
}
}
array_unique($groupIDs);
if(count($groupIDs)) {
$groupMembers = DataObject::get('Member')->where("\"Group\".\"ID\" IN (" . implode(",",$groupIDs) . ")")
->leftJoin("Group_Members", "\"Member\".\"ID\" = \"Group_Members\".\"MemberID\"")
->leftJoin("Group", "\"Group_Members\".\"GroupID\" = \"Group\".\"ID\"");
$contentReviewOwners->merge($groupMembers);
}
}
$contentReviewOwners->merge($this->OwnerUsers());
$contentReviewOwners->removeDuplicates();
return $contentReviewOwners;
}
}

View File

@ -12,6 +12,7 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
* @var array * @var array
*/ */
private static $db = array( private static $db = array(
"ContentReviewType" => "Enum('Inherit, Disabled, Custom', 'Inherit')",
"ReviewPeriodDays" => "Int", "ReviewPeriodDays" => "Int",
"NextReviewDate" => "Date", "NextReviewDate" => "Date",
'LastEditedByName' => 'Varchar(255)', 'LastEditedByName' => 'Varchar(255)',
@ -39,7 +40,7 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
* *
* @var array * @var array
*/ */
private $daysToString = array( private static $schedule = array(
0 => "No automatic review date", 0 => "No automatic review date",
1 => "1 day", 1 => "1 day",
7 => "1 week", 7 => "1 week",
@ -110,6 +111,13 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
return $contentReviewOwners; return $contentReviewOwners;
} }
/**
* @return array
*/
public static function get_schedule() {
return self::$schedule;
}
/** /**
* @return ManyManyList * @return ManyManyList
*/ */
@ -130,17 +138,19 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
* @return void * @return void
*/ */
public function updateSettingsFields(FieldList $fields) { public function updateSettingsFields(FieldList $fields) {
Requirements::javascript('contentreview/javascript/contentreview.js');
$crFields = new FieldList(); $crFields = new FieldList();
// Display read-only version only // Display read-only version only
if(!Permission::check("EDIT_CONTENT_REVIEW_FIELDS")) { if(!Permission::check("EDIT_CONTENT_REVIEW_FIELDS")) {
$schedule = self::get_schedule();
$contentOwners = ReadonlyField::create('ROContentOwners', _t('ContentReview.CONTENTOWNERS', 'Content Owners'), $this->getOwnerNames()); $contentOwners = ReadonlyField::create('ROContentOwners', _t('ContentReview.CONTENTOWNERS', 'Content Owners'), $this->getOwnerNames());
$nextReviewAt = DateField::create('RONextReviewDate', _t("ContentReview.NEXTREVIEWDATE", "Next review date"), $this->owner->NextReviewDate); $nextReviewAt = DateField::create('RONextReviewDate', _t("ContentReview.NEXTREVIEWDATE", "Next review date"), $this->owner->NextReviewDate);
if(!isset($this->daysToString[$this->owner->ReviewPeriodDays])) { if(!isset($schedule[$this->owner->ReviewPeriodDays])) {
$reviewFreq = ReadonlyField::create("ROReviewPeriodDays", _t("ContentReview.REVIEWFREQUENCY", "Review frequency"), $this->daysToString[0]); $reviewFreq = ReadonlyField::create("ROReviewPeriodDays", _t("ContentReview.REVIEWFREQUENCY", "Review frequency"), $schedule[0]);
} else { } else {
$reviewFreq = ReadonlyField::create("ROReviewPeriodDays", _t("ContentReview.REVIEWFREQUENCY", "Review frequency"), $this->daysToString[$this->owner->ReviewPeriodDays]); $reviewFreq = ReadonlyField::create("ROReviewPeriodDays", _t("ContentReview.REVIEWFREQUENCY", "Review frequency"), $schedule[$this->owner->ReviewPeriodDays]);
} }
$logConfig = GridFieldConfig::create() $logConfig = GridFieldConfig::create()
@ -162,6 +172,17 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
return; return;
} }
$options = array();
$options["Disabled"] = _t('ContentReview.DISABLE', "Disable content review");
$options["Inherit"] = _t('ContentReview.INHERIT', "Inherit from parent page");
$options["Custom"] = _t('ContentReview.CUSTOM', "Custom settings");
$viewersOptionsField = OptionsetField::create("ContentReviewType", _t('ContentReview.OPTIONS', "Options"), $options);
//$viewersOptionsField->setValue($this->owner->ContentReviewType);
#var_dump($this->owner->ContentReviewType);
#die();
$users = Permission::get_members_by_permission(array("CMS_ACCESS_CMSMain", "ADMIN")); $users = Permission::get_members_by_permission(array("CMS_ACCESS_CMSMain", "ADMIN"));
$usersMap = $users->map('ID', 'Title')->toArray(); $usersMap = $users->map('ID', 'Title')->toArray();
@ -190,19 +211,22 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
->setConfig('datavalueformat', 'yyyy-MM-dd') ->setConfig('datavalueformat', 'yyyy-MM-dd')
->setDescription(_t('ContentReview.NEXTREVIEWDATADESCRIPTION', 'Leave blank for no review')); ->setDescription(_t('ContentReview.NEXTREVIEWDATADESCRIPTION', 'Leave blank for no review'));
$reviewFrequency = DropdownField::create("ReviewPeriodDays", _t("ContentReview.REVIEWFREQUENCY", "Review frequency"), $this->daysToString) $reviewFrequency = DropdownField::create("ReviewPeriodDays", _t("ContentReview.REVIEWFREQUENCY", "Review frequency"), self::get_schedule())
->setDescription(_t('ContentReview.REVIEWFREQUENCYDESCRIPTION', 'The review date will be set to this far in the future whenever the page is published')); ->setDescription(_t('ContentReview.REVIEWFREQUENCYDESCRIPTION', 'The review date will be set to this far in the future whenever the page is published'));
$notesField = GridField::create('ReviewNotes', 'Review Notes', $this->owner->ReviewLogs(), GridFieldConfig_RecordEditor::create()); $notesField = GridField::create('ReviewNotes', 'Review Notes', $this->owner->ReviewLogs(), GridFieldConfig_RecordEditor::create());
$crFields->add(new HeaderField(_t('ContentReview.REVIEWHEADER', "Content review"), 2)); $fields->addFieldsToTab("Root.ContentReview", array(
$crFields->add($userField); new HeaderField(_t('ContentReview.REVIEWHEADER', "Content review"), 2),
$crFields->add($groupField); $viewersOptionsField,
$crFields->add($reviewDate); CompositeField::create(
$crFields->add($reviewFrequency); $userField,
$crFields->add($notesField); $groupField,
$reviewDate,
$fields->addFieldsToTab("Root.ContentReview", $crFields); $reviewFrequency
)->addExtraClass('contentReviewSettings'),
$notesField
));
} }
/** /**

View File

@ -4,5 +4,43 @@ jQuery(function($) {
"use strict"; "use strict";
$.entwine('ss', function($) { $.entwine('ss', function($) {
/**
* Class: .cms-edit-form #ContentReviewType
*
* Toggle display of group dropdown in "access" tab,
* based on selection of radiobuttons.
*/
$('.cms-edit-form #ContentReviewType').entwine({
// Constructor: onmatch
onmatch: function() {
// TODO Decouple
var dropdown;
if(this.attr('id') == 'ContentReviewType') dropdown = $('.contentReviewSettings');
this.find('.optionset :input').bind('change', function(e) {
var wrapper = $(this).closest('.middleColumn').parent('div');
if(e.target.value == 'Custom') {
wrapper.addClass('remove-splitter');
dropdown['show']();
}
else {
wrapper.removeClass('remove-splitter');
dropdown['hide']();
}
});
// initial state
var currentVal = this.find('input[name=' + this.attr('id') + ']:checked').val();
dropdown[currentVal == 'Custom' ? 'show' : 'hide']();
this._super();
},
onunmatch: function() {
this._super();
}
});
}); });
}); });