2009-11-02 05:02:57 +01:00
< ? php
/**
* Set dates at which content needs to be reviewed and provide
* a report and emails to alert to content needing review
*
* @ package contentreview
*/
2012-07-10 18:36:05 +02:00
class SiteTreeContentReview extends DataExtension implements PermissionProvider {
2010-07-20 05:51:15 +02:00
2014-02-13 02:07:39 +01:00
/**
*
* @ var array
*/
2013-09-23 07:56:29 +02:00
private static $db = array (
2014-02-20 06:05:14 +01:00
" ContentReviewType " => " Enum('Inherit, Disabled, Custom', 'Inherit') " ,
2012-07-10 18:36:05 +02:00
" ReviewPeriodDays " => " Int " ,
" NextReviewDate " => " Date " ,
'LastEditedByName' => 'Varchar(255)' ,
'OwnerNames' => 'Varchar(255)'
);
2014-02-26 23:07:34 +01:00
/**
*
* @ var array
*/
private static $defaults = array (
'ContentReviewType' => 'Inherit'
);
2014-02-18 03:39:13 +01:00
/**
*
* @ var array
*/
private static $has_many = array (
'ReviewLogs' => 'ContentReviewLog'
);
2012-07-10 18:36:05 +02:00
2014-02-13 02:07:39 +01:00
/**
*
* @ var array
*/
2014-02-13 04:35:13 +01:00
private static $belongs_many_many = array (
'ContentReviewGroups' => 'Group' ,
'ContentReviewUsers' => 'Member'
2012-07-10 18:36:05 +02:00
);
2014-02-13 04:35:13 +01:00
2014-02-19 02:31:47 +01:00
/**
*
* @ var array
*/
2014-02-20 06:05:14 +01:00
private static $schedule = array (
2014-02-19 02:31:47 +01:00
0 => " No automatic review date " ,
1 => " 1 day " ,
7 => " 1 week " ,
30 => " 1 month " ,
60 => " 2 months " ,
91 => " 3 months " ,
121 => " 4 months " ,
152 => " 5 months " ,
183 => " 6 months " ,
365 => " 12 months " ,
);
2014-02-21 01:33:01 +01:00
/**
* @ return array
*/
public static function get_schedule () {
return self :: $schedule ;
}
2014-02-25 08:37:59 +01:00
/**
*
* @ param \FieldList $actions
*/
public function updateCMSActions ( \FieldList $actions ) {
if ( $this -> canBeReviewedBy ( Member :: currentUser ())) {
$reviewAction = FormAction :: create ( 'reviewed' , _t ( 'ContentReview.BUTTONREVIEWED' , 'Content reviewed' ))
-> setAttribute ( 'data-icon' , 'pencil' )
-> setAttribute ( 'data-text-alternate' , _t ( 'ContentReview.BUTTONREVIEWED' , 'Content reviewed' ));
$actions -> push ( $reviewAction );
}
}
2014-02-24 08:19:44 +01:00
/**
*
* @ param SiteTree $page
* @ return Date | false - returns false if the content review have disabled
*/
2014-02-24 09:17:09 +01:00
public function getReviewDate ( SiteTree $page = null ) {
if ( $page === null ) {
$page = $this -> owner ;
}
2014-02-24 08:19:44 +01:00
if ( $page -> obj ( 'NextReviewDate' ) -> exists ()) {
return $page -> obj ( 'NextReviewDate' );
}
2014-02-24 09:17:09 +01:00
$options = $this -> owner -> getOptions ();
2014-02-24 09:10:10 +01:00
if ( ! $options ) {
2014-02-24 08:19:44 +01:00
return false ;
}
2014-02-24 09:10:10 +01:00
if ( ! $options -> ReviewPeriodDays ) {
2014-02-24 08:19:44 +01:00
return false ;
}
// Failover to check on ReviewPeriodDays + LastEdited
2014-02-24 09:10:10 +01:00
$nextReviewUnixSec = strtotime ( ' + ' . $options -> ReviewPeriodDays . ' days' , SS_Datetime :: now () -> format ( 'U' ));
2014-02-24 08:19:44 +01:00
$date = Date :: create ( 'NextReviewDate' );
$date -> setValue ( date ( 'Y-m-d H:i:s' , $nextReviewUnixSec ));
return $date ;
}
/**
* Get the object that have the information about the content
2014-02-24 09:10:10 +01:00
* review settings . Either
* - a SiteTreeContentReview decorated object
* - the default SiteTree config
* - false if this page have it ' s content review disabled
2014-02-24 08:19:44 +01:00
*
* Will go through parents and root pages will use the siteconfig
* if their setting is Inherit .
*
* @ return DataObject or false if no settings found
*/
2014-02-24 09:10:10 +01:00
public function getOptions () {
if ( $this -> owner -> ContentReviewType == 'Custom' ) {
return $this -> owner ;
2014-02-24 08:19:44 +01:00
}
2014-02-24 09:10:10 +01:00
if ( $this -> owner -> ContentReviewType == 'Disabled' ) {
2014-02-24 08:19:44 +01:00
return false ;
}
2014-02-24 09:10:10 +01:00
$page = $this -> owner ;
2014-02-24 08:19:44 +01:00
// $page is inheriting it's settings from it's parent, find
// the first valid parent with a valid setting
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 shouldn\'t really happen, as per usual developer logic.' );
}
2014-02-13 02:07:39 +01:00
/**
*
* @ return string
*/
2014-02-13 04:35:13 +01:00
public function getOwnerNames () {
2014-02-25 08:34:42 +01:00
$options = $this -> getOptions ();
2014-02-13 04:35:13 +01:00
$names = array ();
2014-02-25 08:34:42 +01:00
if ( ! $options ) {
return '' ;
}
foreach ( $options -> OwnerGroups () as $group ) {
2014-02-19 02:31:47 +01:00
$names [] = $group -> getBreadcrumbs ( ' > ' );
2014-02-13 04:35:13 +01:00
}
2014-02-25 08:34:42 +01:00
foreach ( $options -> OwnerUsers () as $group ) {
2014-02-13 04:35:13 +01:00
$names [] = $group -> getName ();
2014-02-13 02:07:39 +01:00
}
2014-02-13 04:35:13 +01:00
return implode ( ', ' , $names );
2009-11-02 05:02:57 +01:00
}
2010-07-20 05:51:15 +02:00
2014-02-13 02:07:39 +01:00
/**
*
* @ return string
*/
public function getEditorName () {
2014-02-24 09:10:10 +01:00
$member = Member :: currentUser ();
if ( $member ) {
2010-07-20 05:51:15 +02:00
return $member -> FirstName . ' ' . $member -> Surname ;
}
2014-02-24 09:10:10 +01:00
return null ;
2010-07-20 05:51:15 +02:00
}
2014-02-13 04:35:13 +01:00
/**
* Get all Members that are Content Owners to this page
*
* This includes checking group hierarchy and adding any direct users
*
* @ return \ArrayList
*/
public function ContentReviewOwners () {
$contentReviewOwners = new ArrayList ();
2014-02-18 03:39:13 +01:00
$toplevelGroups = $this -> OwnerGroups ();
if ( $toplevelGroups -> count ()) {
2014-02-13 04:35:13 +01:00
$groupIDs = array ();
foreach ( $toplevelGroups as $group ) {
$familyIDs = $group -> collateFamilyIDs ();
if ( is_array ( $familyIDs )) {
$groupIDs = array_merge ( $groupIDs , array_values ( $familyIDs ));
}
}
2014-02-18 03:39:13 +01:00
array_unique ( $groupIDs );
2014-02-13 04:35:13 +01:00
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 );
}
}
2014-02-18 03:39:13 +01:00
$contentReviewOwners -> merge ( $this -> OwnerUsers ());
2014-02-13 04:35:13 +01:00
$contentReviewOwners -> removeDuplicates ();
return $contentReviewOwners ;
}
/**
* @ return ManyManyList
*/
2014-02-18 03:39:13 +01:00
public function OwnerGroups () {
2014-02-13 04:35:13 +01:00
return $this -> owner -> getManyManyComponents ( 'ContentReviewGroups' );
}
/**
* @ return ManyManyList
*/
2014-02-18 03:39:13 +01:00
public function OwnerUsers () {
2014-02-13 04:35:13 +01:00
return $this -> owner -> getManyManyComponents ( 'ContentReviewUsers' );
}
2010-07-20 05:51:15 +02:00
2014-02-13 02:07:39 +01:00
/**
*
* @ param FieldList $fields
* @ return void
*/
2014-02-19 02:31:47 +01:00
public function updateSettingsFields ( FieldList $fields ) {
2014-02-20 06:05:14 +01:00
Requirements :: javascript ( 'contentreview/javascript/contentreview.js' );
2014-02-19 02:31:47 +01:00
// Display read-only version only
2014-02-13 04:35:13 +01:00
if ( ! Permission :: check ( " EDIT_CONTENT_REVIEW_FIELDS " )) {
2014-02-20 06:05:14 +01:00
$schedule = self :: get_schedule ();
2014-02-19 02:31:47 +01:00
$contentOwners = ReadonlyField :: create ( 'ROContentOwners' , _t ( 'ContentReview.CONTENTOWNERS' , 'Content Owners' ), $this -> getOwnerNames ());
$nextReviewAt = DateField :: create ( 'RONextReviewDate' , _t ( " ContentReview.NEXTREVIEWDATE " , " Next review date " ), $this -> owner -> NextReviewDate );
2014-02-20 06:05:14 +01:00
if ( ! isset ( $schedule [ $this -> owner -> ReviewPeriodDays ])) {
$reviewFreq = ReadonlyField :: create ( " ROReviewPeriodDays " , _t ( " ContentReview.REVIEWFREQUENCY " , " Review frequency " ), $schedule [ 0 ]);
2014-02-19 02:31:47 +01:00
} else {
2014-02-20 06:05:14 +01:00
$reviewFreq = ReadonlyField :: create ( " ROReviewPeriodDays " , _t ( " ContentReview.REVIEWFREQUENCY " , " Review frequency " ), $schedule [ $this -> owner -> ReviewPeriodDays ]);
2014-02-19 02:31:47 +01:00
}
$logConfig = GridFieldConfig :: create ()
-> addComponent ( new GridFieldSortableHeader ())
-> addComponent ( $logColumns = new GridFieldDataColumns ());
// Cast the value to the users prefered date format
$logColumns -> setFieldCasting ( array (
'Created' => 'DateTimeField->value'
));
$logs = GridField :: create ( 'ROReviewNotes' , 'Review Notes' , $this -> owner -> ReviewLogs (), $logConfig );
2014-02-25 08:35:27 +01:00
$optionsFrom = ReadonlyField :: create ( 'ROType' , _t ( 'ContentReview.SETTINGSFROM' , " Options are " ), $this -> owner -> ContentReviewType );
2014-02-25 08:37:59 +01:00
2014-02-19 02:31:47 +01:00
$fields -> addFieldsToTab ( " Root.ContentReview " , array (
$contentOwners ,
$nextReviewAt -> performReadonlyTransformation (),
$reviewFreq ,
2014-02-25 08:35:27 +01:00
$optionsFrom ,
2014-02-19 02:31:47 +01:00
$logs
));
2014-02-13 02:07:39 +01:00
return ;
2009-11-02 05:02:57 +01:00
}
2014-02-19 02:31:47 +01:00
2014-02-20 06:05:14 +01:00
$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 );
2014-02-13 04:35:13 +01:00
$users = Permission :: get_members_by_permission ( array ( " CMS_ACCESS_CMSMain " , " ADMIN " ));
2014-02-19 02:31:47 +01:00
$usersMap = $users -> map ( 'ID' , 'Title' ) -> toArray ();
2014-02-13 04:35:13 +01:00
asort ( $usersMap );
2014-02-19 02:31:47 +01:00
$userField = ListboxField :: create ( 'OwnerUsers' , _t ( " ContentReview.PAGEOWNERUSERS " , " Users " ), $usersMap )
2014-02-13 04:35:13 +01:00
-> setMultiple ( true )
-> setAttribute ( 'data-placeholder' , _t ( 'ContentReview.ADDUSERS' , 'Add users' ))
-> setDescription ( _t ( 'ContentReview.OWNERUSERSDESCRIPTION' , 'Page owners that are responsible for reviews' ));
2014-02-13 02:07:39 +01:00
2014-02-13 04:35:13 +01:00
$groupsMap = array ();
foreach ( Group :: get () as $group ) {
// Listboxfield values are escaped, use ASCII char instead of »
$groupsMap [ $group -> ID ] = $group -> getBreadcrumbs ( ' > ' );
}
asort ( $groupsMap );
2014-02-19 02:31:47 +01:00
$groupField = ListboxField :: create ( 'OwnerGroups' , _t ( " ContentReview.PAGEOWNERGROUPS " , " Groups " ), $groupsMap )
2014-02-13 04:35:13 +01:00
-> setMultiple ( true )
-> setAttribute ( 'data-placeholder' , _t ( 'ContentReview.ADDGROUP' , 'Add groups' ))
-> setDescription ( _t ( 'ContentReview.OWNERGROUPSDESCRIPTION' , 'Page owners that are responsible for reviews' ));
2014-02-19 02:31:47 +01:00
$reviewDate = DateField :: create ( " NextReviewDate " , _t ( " ContentReview.NEXTREVIEWDATE " , " Next review date " ))
-> setConfig ( 'showcalendar' , true )
2014-02-13 04:35:13 +01:00
-> setConfig ( 'dateformat' , 'yyyy-MM-dd' )
-> setConfig ( 'datavalueformat' , 'yyyy-MM-dd' )
-> setDescription ( _t ( 'ContentReview.NEXTREVIEWDATADESCRIPTION' , 'Leave blank for no review' ));
2014-02-20 06:05:14 +01:00
$reviewFrequency = DropdownField :: create ( " ReviewPeriodDays " , _t ( " ContentReview.REVIEWFREQUENCY " , " Review frequency " ), self :: get_schedule ())
2014-02-19 02:31:47 +01:00
-> 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 ());
2014-02-13 04:35:13 +01:00
2014-02-20 06:05:14 +01:00
$fields -> addFieldsToTab ( " Root.ContentReview " , array (
new HeaderField ( _t ( 'ContentReview.REVIEWHEADER' , " Content review " ), 2 ),
$viewersOptionsField ,
CompositeField :: create (
$userField ,
$groupField ,
$reviewDate ,
$reviewFrequency
) -> addExtraClass ( 'contentReviewSettings' ),
$notesField
));
2009-11-02 05:02:57 +01:00
}
2014-02-14 02:49:43 +01:00
2014-02-18 03:39:13 +01:00
/**
* Creates a ContentReviewLog and connects it to this Page
*
* @ param Member $reviewer
* @ param string $message
*/
public function addReviewNote ( Member $reviewer , $message ) {
$reviewLog = ContentReviewLog :: create ();
$reviewLog -> Note = $message ;
2014-02-19 02:31:47 +01:00
$reviewLog -> ReviewerID = $reviewer -> ID ;
2014-02-18 03:39:13 +01:00
$this -> owner -> ReviewLogs () -> add ( $reviewLog );
}
/**
* Advance review date to the next date based on review period or set it to null
* if there is no schedule
*
* @ return bool - returns true if date was set and false is content review is 'off'
*/
public function advanceReviewDate () {
2014-02-24 12:46:50 +01:00
$options = $this -> getOptions ();
$nextDate = false ;
if ( $options && $options -> ReviewPeriodDays ) {
$nextDate = date ( 'Y-m-d' , strtotime ( '+ ' . $options -> ReviewPeriodDays . ' days' , SS_Datetime :: now () -> format ( 'U' )));
2014-02-18 03:39:13 +01:00
}
2014-02-24 12:46:50 +01:00
$this -> owner -> NextReviewDate = $nextDate ;
2014-02-18 03:39:13 +01:00
$this -> owner -> write ();
2014-02-24 12:46:50 +01:00
return ( bool ) $nextDate ;
2014-02-18 03:39:13 +01:00
}
2014-02-14 02:49:43 +01:00
/**
* Check if a review is due by a member for this owner
*
* @ param Member $member
* @ return boolean
*/
2014-02-24 08:19:44 +01:00
public function canBeReviewedBy ( Member $member = null ) {
2014-02-14 02:49:43 +01:00
if ( ! $this -> owner -> obj ( 'NextReviewDate' ) -> exists ()) {
return false ;
}
if ( $this -> owner -> obj ( 'NextReviewDate' ) -> InFuture ()) {
return false ;
}
2014-02-25 00:10:39 +01:00
$options = $this -> getOptions ();
if ( $options -> OwnerGroups () -> count () == 0 && $options -> OwnerUsers () -> count () == 0 ) {
2014-02-14 02:49:43 +01:00
return false ;
}
2014-02-24 08:19:44 +01:00
// This content should be reviewed by someone
if ( ! $member ) {
return true ;
}
2014-02-25 00:10:39 +01:00
if ( $member -> inGroups ( $options -> OwnerGroups ())) {
2014-02-14 02:49:43 +01:00
return true ;
}
2014-02-25 00:10:39 +01:00
if ( $options -> OwnerUsers () -> find ( 'ID' , $member -> ID )) {
2014-02-14 02:49:43 +01:00
return true ;
}
return false ;
}
2010-07-20 05:51:15 +02:00
2014-02-13 02:07:39 +01:00
/**
* Set the review data from the review period , if set .
*/
public function onBeforeWrite () {
2010-07-20 05:51:15 +02:00
$this -> owner -> LastEditedByName = $this -> owner -> getEditorName ();
2014-02-13 04:35:13 +01:00
$this -> owner -> OwnerNames = $this -> owner -> getOwnerNames ();
2014-02-24 08:19:44 +01:00
// If the user changed the Type, we need to recalculate the
// Next review date
if ( $this -> owner -> isChanged ( 'ContentReviewType' , 2 )) {
2014-02-26 23:07:34 +01:00
2014-02-24 08:19:44 +01:00
// Changed to Disabled
if ( $this -> owner -> ContentReviewType == 'Disabled' ) {
2014-02-26 23:07:34 +01:00
$this -> setDefaultReviewDateForDisabled ();
2014-02-24 08:19:44 +01:00
// Changed to Custom
2014-02-26 23:07:34 +01:00
} elseif ( $this -> owner -> ContentReviewType == 'Custom' ) {
$this -> setDefaultReviewDateForCustom ();
// Changed to Inherit
2014-02-24 08:19:44 +01:00
} else {
2014-02-26 23:07:34 +01:00
$this -> setDefaultReviewDateForInherited ();
2014-02-24 08:19:44 +01:00
}
}
2014-02-26 23:07:34 +01:00
// Ensure that a inherited page always have a next review date
if ( $this -> owner -> ContentReviewType == 'Inherit' && ! $this -> owner -> NextReviewDate ) {
$this -> setDefaultReviewDateForInherited ();
}
2014-02-24 08:19:44 +01:00
// Oh yey.. now we need to update all the child pages that inherit this setting
2014-02-24 10:14:26 +01:00
// We can only change children after this record has been created, otherwise the stageChildren
2014-02-24 08:19:44 +01:00
// method will grab all pages in the DB (this messes up unittesting)
if ( ! $this -> owner -> exists ()) {
return ;
}
2014-02-26 23:07:34 +01:00
// parent page change it's review period
// && !$this->owner->isChanged('ContentReviewType', 2)
if ( $this -> owner -> isChanged ( 'ReviewPeriodDays' , 2 )) {
2014-02-24 08:19:44 +01:00
$nextReviewUnixSec = strtotime ( ' + ' . $this -> owner -> ReviewPeriodDays . ' days' , SS_Datetime :: now () -> format ( 'U' ));
2014-02-26 23:07:34 +01:00
$this -> owner -> NextReviewDate = date ( 'Y-m-d' , $nextReviewUnixSec );
2014-02-24 08:19:44 +01:00
}
2014-02-26 23:07:34 +01:00
//
2014-02-24 08:19:44 +01:00
if ( $this -> owner -> isChanged ( 'NextReviewDate' , 2 )) {
$children = $this -> owner -> stageChildren ( true ) -> filter ( 'ContentReviewType' , 'Inherit' );
foreach ( $children as $child ) {
$child -> NextReviewDate = $this -> owner -> NextReviewDate ;
$child -> write ();
}
}
2009-11-02 05:02:57 +01:00
}
2014-02-26 23:07:34 +01:00
/**
*
*/
private function setDefaultReviewDateForDisabled () {
$this -> owner -> NextReviewDate = null ;
}
/**
*
*/
protected function setDefaultReviewDateForCustom () {
if ( $this -> owner -> NextReviewDate ) {
return ;
}
$this -> owner -> NextReviewDate = null ;
$nextDate = $this -> getReviewDate ();
if ( is_object ( $nextDate )) {
$this -> owner -> NextReviewDate = $nextDate -> getValue ();
} else {
$this -> owner -> NextReviewDate = $nextDate ;
}
}
/**
*
*/
protected function setDefaultReviewDateForInherited () {
$options = $this -> getOptions ();
$nextDate = null ;
// Set the review date to be the same as the parents reviewdate
if ( $options && $this -> owner -> parent () -> exists ()) {
$nextDate = $this -> getReviewDate ( $this -> owner -> parent ());
// Use the defualt settings
} elseif ( $options instanceof SiteConfig ) {
$nextDate = $this -> getReviewDate ();
}
if ( is_object ( $nextDate )) {
$this -> owner -> NextReviewDate = $nextDate -> getValue ();
} else {
$this -> owner -> NextReviewDate = $nextDate ;
}
}
2014-02-13 02:07:39 +01:00
/**
* Provide permissions to the CMS
*
* @ return array
*/
public function providePermissions () {
2009-11-02 05:02:57 +01:00
return array (
" EDIT_CONTENT_REVIEW_FIELDS " => array (
'name' => " Set content owners and review dates " ,
'category' => _t ( 'Permissions.CONTENT_CATEGORY' , 'Content permissions' ),
'sort' => 50
)
);
}
}