diff --git a/_config/config.yml b/_config/config.yml
index 8bb4e99..7f17b79 100644
--- a/_config/config.yml
+++ b/_config/config.yml
@@ -9,4 +9,7 @@ Member:
- ContentReviewOwner
CMSPageEditController:
extensions:
- - ContentReviewCMSExtension
\ No newline at end of file
+ - ContentReviewCMSExtension
+SiteConfig:
+ extensions:
+ - ContentReviewDefaultSettings
\ No newline at end of file
diff --git a/code/ContentReviewEmails.php b/code/ContentReviewEmails.php
index 0833314..d331219 100644
--- a/code/ContentReviewEmails.php
+++ b/code/ContentReviewEmails.php
@@ -4,10 +4,19 @@
* Daily task to send emails to the owners of content items
* 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
*/
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
@@ -21,52 +30,160 @@ class ContentReviewEmails extends BuildTask {
$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')
->leftJoin('Group_SiteTreeContentReview', '"SiteTree"."ID" = "OwnerGroups"."SiteTreeID"', 'OwnerGroups')
->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()) {
- foreach($pages as $page) {
- $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 = ''._t('ContentReviewEmails.EMAIL_HEADING','Page due for review').'
'.
- 'The page "'.$page->Title.'" is due for review today by you.
- '. _t('ContentReviewEmails.REVIEWPAGELINK','Review the page in the CMS') .' —
- '. _t('ContentReviewEmails.VIEWPUBLISHEDLINK','View this page on the website') .'';
- if(class_exists('Notification')) {
- Notification::notify($recipient, $message);
- }
- }
- }
+ $this->notify($pages);
+
+ // Then grab all the pages with that inherits their settings
+
+ $pages = Page::get('Page')
+ ->leftJoin('Group_SiteTreeContentReview', '"SiteTree"."ID" = "OwnerGroups"."SiteTreeID"', 'OwnerGroups')
+ ->leftJoin('Member_SiteTreeContentReview', '"SiteTree"."ID" = "OwnerUsers"."SiteTreeID"', "OwnerUsers")
+ ->where('"SiteTree"."ContentReviewType" = \'Inherit\'')
+ ;
+
+ $overduePages = $this->findInheritedSettings($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->notify_user($memberID, $pages);
}
// Revert subsite filter (if installed)
- if (ClassInfo::exists('Subsite')) {
+ if(ClassInfo::exists('Subsite')) {
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 = ''._t('ContentReviewEmails.EMAIL_HEADING','Page due for review').'
'.
+ 'The page "'.$page->Title.'" is due for review today by you.
+ '. _t('ContentReviewEmails.REVIEWPAGELINK','Review the page in the CMS') .' —
+ '. _t('ContentReviewEmails.VIEWPUBLISHEDLINK','View this page on the website') .'';
+ if(class_exists('Notification')) {
+ // Notification::notify($recipient, $message);
+ }
+ // echo $page->Title.' - '.$recipient->Email.PHP_EOL;
+ }
+ }
+ }
}
diff --git a/code/extensions/ContentReviewDefaultSettings.php b/code/extensions/ContentReviewDefaultSettings.php
new file mode 100644
index 0000000..40f2acd
--- /dev/null
+++ b/code/extensions/ContentReviewDefaultSettings.php
@@ -0,0 +1,110 @@
+ "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 »
+ $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;
+ }
+}
diff --git a/code/extensions/SiteTreeContentReview.php b/code/extensions/SiteTreeContentReview.php
index ef03fa3..ed0ba76 100644
--- a/code/extensions/SiteTreeContentReview.php
+++ b/code/extensions/SiteTreeContentReview.php
@@ -12,6 +12,7 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
* @var array
*/
private static $db = array(
+ "ContentReviewType" => "Enum('Inherit, Disabled, Custom', 'Inherit')",
"ReviewPeriodDays" => "Int",
"NextReviewDate" => "Date",
'LastEditedByName' => 'Varchar(255)',
@@ -39,7 +40,7 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
*
* @var array
*/
- private $daysToString = array(
+ private static $schedule = array(
0 => "No automatic review date",
1 => "1 day",
7 => "1 week",
@@ -110,6 +111,13 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
return $contentReviewOwners;
}
+ /**
+ * @return array
+ */
+ public static function get_schedule() {
+ return self::$schedule;
+ }
+
/**
* @return ManyManyList
*/
@@ -130,17 +138,19 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
* @return void
*/
public function updateSettingsFields(FieldList $fields) {
+
+ Requirements::javascript('contentreview/javascript/contentreview.js');
$crFields = new FieldList();
// Display read-only version only
if(!Permission::check("EDIT_CONTENT_REVIEW_FIELDS")) {
-
+ $schedule = self::get_schedule();
$contentOwners = ReadonlyField::create('ROContentOwners', _t('ContentReview.CONTENTOWNERS', 'Content Owners'), $this->getOwnerNames());
$nextReviewAt = DateField::create('RONextReviewDate', _t("ContentReview.NEXTREVIEWDATE", "Next review date"), $this->owner->NextReviewDate);
- if(!isset($this->daysToString[$this->owner->ReviewPeriodDays])) {
- $reviewFreq = ReadonlyField::create("ROReviewPeriodDays", _t("ContentReview.REVIEWFREQUENCY", "Review frequency"), $this->daysToString[0]);
+ if(!isset($schedule[$this->owner->ReviewPeriodDays])) {
+ $reviewFreq = ReadonlyField::create("ROReviewPeriodDays", _t("ContentReview.REVIEWFREQUENCY", "Review frequency"), $schedule[0]);
} 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()
@@ -162,6 +172,17 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
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"));
$usersMap = $users->map('ID', 'Title')->toArray();
@@ -190,19 +211,22 @@ class SiteTreeContentReview extends DataExtension implements PermissionProvider
->setConfig('datavalueformat', 'yyyy-MM-dd')
->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'));
$notesField = GridField::create('ReviewNotes', 'Review Notes', $this->owner->ReviewLogs(), GridFieldConfig_RecordEditor::create());
- $crFields->add(new HeaderField(_t('ContentReview.REVIEWHEADER', "Content review"), 2));
- $crFields->add($userField);
- $crFields->add($groupField);
- $crFields->add($reviewDate);
- $crFields->add($reviewFrequency);
- $crFields->add($notesField);
-
- $fields->addFieldsToTab("Root.ContentReview", $crFields);
+ $fields->addFieldsToTab("Root.ContentReview", array(
+ new HeaderField(_t('ContentReview.REVIEWHEADER', "Content review"), 2),
+ $viewersOptionsField,
+ CompositeField::create(
+ $userField,
+ $groupField,
+ $reviewDate,
+ $reviewFrequency
+ )->addExtraClass('contentReviewSettings'),
+ $notesField
+ ));
}
/**
diff --git a/javascript/contentreview.js b/javascript/contentreview.js
index afc35a5..38852fb 100644
--- a/javascript/contentreview.js
+++ b/javascript/contentreview.js
@@ -4,5 +4,43 @@ jQuery(function($) {
"use strict";
$.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();
+ }
+ });
+
});
});
\ No newline at end of file