From 6b337496cffe2236b89baacb66106bd0a8aa8ec4 Mon Sep 17 00:00:00 2001 From: Stig Lindqvist Date: Tue, 25 Feb 2014 15:54:27 +1300 Subject: [PATCH] NEW Send one notification email per author #7 --- code/ContentReviewEmails.php | 154 ----------- code/tasks/ContentReviewEmails.php | 101 ++++++++ .../ContentReviewOwnerMigrationTask.php | 0 templates/ContentReviewEmail.ss | 24 ++ templates/ContentReviewEmails.ss | 24 -- tests/ContentReviewNotificationTest.php | 20 +- tests/ContentReviewTest.yml | 4 + tests/SiteTreeContentReviewTest.php | 241 +++++++++--------- 8 files changed, 271 insertions(+), 297 deletions(-) delete mode 100644 code/ContentReviewEmails.php create mode 100644 code/tasks/ContentReviewEmails.php rename code/{ => tasks}/ContentReviewOwnerMigrationTask.php (100%) create mode 100644 templates/ContentReviewEmail.ss delete mode 100644 templates/ContentReviewEmails.ss diff --git a/code/ContentReviewEmails.php b/code/ContentReviewEmails.php deleted file mode 100644 index 81e8a92..0000000 --- a/code/ContentReviewEmails.php +++ /dev/null @@ -1,154 +0,0 @@ -URLDate() : SSDatetime::now()->URLDate(); - - // First grab all the pages with a custom setting - //$customSettingsPages = Page::get('Page') - // ->leftJoin('Group_SiteTreeContentReview', '"SiteTree"."ID" = "OwnerGroups"."SiteTreeID"', 'OwnerGroups') - // ->leftJoin('Member_SiteTreeContentReview', '"SiteTree"."ID" = "OwnerUsers"."SiteTreeID"', "OwnerUsers") - // ->where('"SiteTree"."ContentReviewType" = \'Custom\' AND "SiteTree"."NextReviewDate" <= \''.$now.'\' AND' . - // ' ("OwnerGroups"."ID" IS NOT NULL OR "OwnerUsers"."ID" IS NOT NULL)') - //; - - //$this->getOverduePagesForOwners($customSettingsPages, $overduePages); - - // Then grab all the pages with that inherits their settings - //$inheritedSettingsPages = Page::get('Page') - // ->leftJoin('Group_SiteTreeContentReview', '"SiteTree"."ID" = "OwnerGroups"."SiteTreeID"', 'OwnerGroups') - // ->leftJoin('Member_SiteTreeContentReview', '"SiteTree"."ID" = "OwnerUsers"."SiteTreeID"', "OwnerUsers") - // ->where('"SiteTree"."ContentReviewType" = \'Inherit\'') - //; - - - $pages = Page::get(); - $this->getOverduePagesForOwners($pages, $overduePages); - - // 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); - } - - // Revert subsite filter (if installed) - if(ClassInfo::exists('Subsite')) { - Subsite::$disable_subsite_filter = $oldSubsiteState; - } - } - - /** - * - * @param SS_list $pages - * @param array &$pages - * @return array - */ - protected function getOverduePagesForOwners(SS_list $pages, array &$overduePages) { - foreach($pages as $page) { - if(!$page->isContentReviewOverdue()) { - continue; - } - - $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] = array(); - } - $overduePages[$owner->ID][] = $page; - } - } - return $overduePages; - } - - /** - * - * @param int $owner - * @param array $pages - */ - protected function notifyOwner($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/tasks/ContentReviewEmails.php b/code/tasks/ContentReviewEmails.php new file mode 100644 index 0000000..904f383 --- /dev/null +++ b/code/tasks/ContentReviewEmails.php @@ -0,0 +1,101 @@ +URLDate() : SSDatetime::now()->URLDate(); + + // First grab all the pages with a custom setting + $pages = Page::get('Page')->where('"SiteTree"."NextReviewDate" <= \''.$now.'\''); + + $this->getOverduePagesForOwners($pages, $overduePages); + + + // 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); + } + + // Revert subsite filter (if installed) + if(ClassInfo::exists('Subsite')) { + Subsite::$disable_subsite_filter = $oldSubsiteState; + } + } + + /** + * + * @param SS_list $pages + * @param array &$pages + * @return array + */ + protected function getOverduePagesForOwners(SS_list $pages, array &$overduePages) { + + foreach($pages as $page) { + if(!$page->canBeReviewedBy()) { + continue; + } + $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(); + } + $overduePages[$owner->ID]->push($page); + } + } + return $overduePages; + } + + /** + * + * @param int $owner + * @param array $pages + */ + 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'); + + $subject = _t('ContentReviewEmails.SUBJECT', 'Page(s) are due for content review'); + $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->send(); + } +} diff --git a/code/ContentReviewOwnerMigrationTask.php b/code/tasks/ContentReviewOwnerMigrationTask.php similarity index 100% rename from code/ContentReviewOwnerMigrationTask.php rename to code/tasks/ContentReviewOwnerMigrationTask.php diff --git a/templates/ContentReviewEmail.ss b/templates/ContentReviewEmail.ss new file mode 100644 index 0000000..41cf052 --- /dev/null +++ b/templates/ContentReviewEmail.ss @@ -0,0 +1,24 @@ + + + + + + + + + + <% loop Pages %> + + + + + <% end_loop %> + +
+

<% _t('ContentReviewEmails.EMAIL_HEADING','Page(s) due for review') %>

+

There are $Pages.Count pages that are due for review today by you.

+
$Title<% _t('ContentReviewEmails.REVIEWPAGELINK','Review the page in the CMS') %>
+ <% _t('ContentReviewEmails.VIEWPUBLISHEDLINK','View this page on the website') %> +
+ + \ No newline at end of file diff --git a/templates/ContentReviewEmails.ss b/templates/ContentReviewEmails.ss deleted file mode 100644 index ccbbd17..0000000 --- a/templates/ContentReviewEmails.ss +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - -
-

<% _t('ContentReviewEmails.EMAIL_HEADING','Page due for review') %>

- -

The page $Page.Title is due for review today by you

- -

Actions

- -
- - \ No newline at end of file diff --git a/tests/ContentReviewNotificationTest.php b/tests/ContentReviewNotificationTest.php index 0646b6c..7e8cd70 100644 --- a/tests/ContentReviewNotificationTest.php +++ b/tests/ContentReviewNotificationTest.php @@ -2,14 +2,28 @@ class ContentReviewNotificationTest extends SapphireTest { + public static $fixture_file = 'contentreview/tests/ContentReviewTest.yml'; + public function testContentReviewEmails() { - $this->markTestIncomplete(); - SS_Datetime::set_mock_now('2010-02-14 12:00:00'); + SS_Datetime::set_mock_now('2010-02-24 12:00:00'); + + // This propagates the next review date to 'contact-child' page from the parent page + $childParentPage = $this->objFromFixture('Page', 'contact'); + $childParentPage->NextReviewDate = '2010-02-23'; + $childParentPage->write(); $task = new ContentReviewEmails(); $task->run(new SS_HTTPRequest('GET', '/dev/tasks/ContentReviewEmails')); - $this->assertEmailSent('author@example.com', null, sprintf(_t('ContentReviewEmails.SUBJECT', 'Page %s due for content review'), 'Staff')); + $expectedSubject = _t('ContentReviewEmails.SUBJECT', 'Page(s) are due for content review'); + $email = $this->findEmail('author@example.com', null, $expectedSubject); + + $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']); SS_Datetime::clear_mock_now(); } diff --git a/tests/ContentReviewTest.yml b/tests/ContentReviewTest.yml index 3896143..380d94c 100644 --- a/tests/ContentReviewTest.yml +++ b/tests/ContentReviewTest.yml @@ -36,11 +36,13 @@ Member: Email: visitor@example.com Page: +# Cant be reviewed, no owners home: Title: Home ContentReviewType: Custom NextReviewDate: 2010-02-01 ReviewPeriodDays: 10 +# Cant be reviewed, no owners about: Title: About Us ContentReviewType: Custom @@ -62,10 +64,12 @@ Page: 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 +# Cant be reviewed, no NextReviewDate group-owned: Title: Page owned by group ContentReviewType: Custom diff --git a/tests/SiteTreeContentReviewTest.php b/tests/SiteTreeContentReviewTest.php index c7cf858..be01187 100644 --- a/tests/SiteTreeContentReviewTest.php +++ b/tests/SiteTreeContentReviewTest.php @@ -4,103 +4,109 @@ class SiteTreeContentReviewTest extends FunctionalTest { public static $fixture_file = 'contentreview/tests/ContentReviewTest.yml'; -// public function testPermissionsExists() { -// $perms = singleton('SiteTreeContentReview')->providePermissions(); -// $this->assertTrue(isset($perms['EDIT_CONTENT_REVIEW_FIELDS'])); -// } -// -// public function testUserWithPermissionCanEdit() { -// $editor = $this->objFromFixture('Member', 'editor'); -// $this->logInAs($editor); -// $page = new Page(); -// $fields = $page->getSettingsFields(); -// $this->assertNotNull($fields->dataFieldByName('NextReviewDate')); -// } -// -// public function testUserWithoutPermissionCannotEdit() { -// $author = $this->objFromFixture('Member', 'author'); -// $this->logInAs($author); -// $page = new Page(); -// $fields = $page->getSettingsFields(); -// $this->assertNull($fields->dataFieldByName('NextReviewDate')); -// } -// -// public function testAutomaticallyToNotSetReviewDate() { -// $editor = $this->objFromFixture('Member', 'editor'); -// $this->logInAs($editor); -// -// $page = new Page(); -// $page->ReviewPeriodDays = 10; -// $page->write(); -// $this->assertTrue($page->doPublish()); -// $this->assertEquals(null, $page->NextReviewDate); -// } -// -// public function testAddReviewNote() { -// $author = $this->objFromFixture('Member', 'author'); -// $page = $this->objFromFixture('Page', 'home'); -// $page->addReviewNote($author, 'This is a message'); -// -// // Get the page again to make sure it's not only cached in memory -// $homepage = $this->objFromFixture('Page', 'home'); -// $this->assertEquals(1, $homepage->ReviewLogs()->count()); -// $this->assertEquals('This is a message', $homepage->ReviewLogs()->first()->Note); -// } -// -// public function testGetContentReviewOwners() { -// $page = $this->objFromFixture('Page', 'group-owned'); -// $owners = $page->ContentReviewOwners(); -// $this->assertEquals(1, $owners->count()); -// $this->assertEquals('author@example.com', $owners->first()->Email); -// } -// -// public function testCanNotBeReviewBecauseNoReviewDate() { -// SS_Datetime::set_mock_now('2010-01-01 12:00:00'); -// $author = $this->objFromFixture('Member', 'author'); -// $page = $this->objFromFixture('Page', 'no-review'); -// // page 'no-review' is owned by author, but there is no review date -// $this->assertFalse($page->canBeReviewedBy($author)); -// } -// -// public function testCanNotBeReviewedBecauseInFuture() { -// SS_Datetime::set_mock_now('2010-01-01 12:00:00'); -// $author = $this->objFromFixture('Member', 'author'); -// $page = $this->objFromFixture('Page', 'staff'); -// // page 'staff' is owned by author, but the review date is in the future -// $this->assertFalse($page->canBeReviewedBy($author)); -// } -// -// public function testCanNotBeReviewedByUser() { -// SS_Datetime::set_mock_now('2010-03-01 12:00:00'); -// $author = $this->objFromFixture('Member', 'author'); -// $page = $this->objFromFixture('Page', 'home'); -// // page 'home' doesnt have any owners -// $this->assertFalse($page->canBeReviewedBy($author)); -// } -// -// public function testCanBeReviewedByUser() { -// SS_Datetime::set_mock_now('2010-03-01 12:00:00'); -// $author = $this->objFromFixture('Member', 'author'); -// $page = $this->objFromFixture('Page', 'staff'); -// // page 'staff' is owned by author -// $this->assertTrue($page->canBeReviewedBy($author)); -// } -// -// public function testCanNotBeReviewedByGroup() { -// SS_Datetime::set_mock_now('2010-03-01 12:00:00'); -// $author = $this->objFromFixture('Member', 'editor'); -// $page = $this->objFromFixture('Page', 'contact'); -// // page 'contact' is owned by the authorgroup -// $this->assertFalse($page->canBeReviewedBy($author)); -// } -// -// public function testCanBeReviewedByGroup() { -// SS_Datetime::set_mock_now('2010-03-01 12:00:00'); -// $author = $this->objFromFixture('Member', 'author'); -// $page = $this->objFromFixture('Page', 'contact'); -// // page 'contact' is owned by the authorgroup -// $this->assertTrue($page->canBeReviewedBy($author)); -// } + public function testPermissionsExists() { + $perms = singleton('SiteTreeContentReview')->providePermissions(); + $this->assertTrue(isset($perms['EDIT_CONTENT_REVIEW_FIELDS'])); + } + + public function testUserWithPermissionCanEdit() { + $editor = $this->objFromFixture('Member', 'editor'); + $this->logInAs($editor); + $page = new Page(); + $fields = $page->getSettingsFields(); + $this->assertNotNull($fields->dataFieldByName('NextReviewDate')); + } + + public function testUserWithoutPermissionCannotEdit() { + $author = $this->objFromFixture('Member', 'author'); + $this->logInAs($author); + $page = new Page(); + $fields = $page->getSettingsFields(); + $this->assertNull($fields->dataFieldByName('NextReviewDate')); + } + + public function testAutomaticallyToNotSetReviewDate() { + $editor = $this->objFromFixture('Member', 'editor'); + $this->logInAs($editor); + + $page = new Page(); + $page->ReviewPeriodDays = 10; + $page->write(); + $this->assertTrue($page->doPublish()); + $this->assertEquals(null, $page->NextReviewDate); + } + + public function testAddReviewNote() { + $author = $this->objFromFixture('Member', 'author'); + $page = $this->objFromFixture('Page', 'home'); + $page->addReviewNote($author, 'This is a message'); + + // Get the page again to make sure it's not only cached in memory + $homepage = $this->objFromFixture('Page', 'home'); + $this->assertEquals(1, $homepage->ReviewLogs()->count()); + $this->assertEquals('This is a message', $homepage->ReviewLogs()->first()->Note); + } + + public function testGetContentReviewOwners() { + $page = $this->objFromFixture('Page', 'group-owned'); + $owners = $page->ContentReviewOwners(); + $this->assertEquals(1, $owners->count()); + $this->assertEquals('author@example.com', $owners->first()->Email); + } + + public function testCanNotBeReviewBecauseNoReviewDate() { + SS_Datetime::set_mock_now('2010-01-01 12:00:00'); + $author = $this->objFromFixture('Member', 'author'); + $page = $this->objFromFixture('Page', 'no-review'); + // page 'no-review' is owned by author, but there is no review date + $this->assertFalse($page->canBeReviewedBy($author)); + SS_Datetime::clear_mock_now(); + } + + public function testCanNotBeReviewedBecauseInFuture() { + SS_Datetime::set_mock_now('2010-01-01 12:00:00'); + $author = $this->objFromFixture('Member', 'author'); + $page = $this->objFromFixture('Page', 'staff'); + // page 'staff' is owned by author, but the review date is in the future + $this->assertFalse($page->canBeReviewedBy($author)); + SS_Datetime::clear_mock_now(); + } + + public function testCanNotBeReviewedByUser() { + SS_Datetime::set_mock_now('2010-03-01 12:00:00'); + $author = $this->objFromFixture('Member', 'author'); + $page = $this->objFromFixture('Page', 'home'); + // page 'home' doesnt have any owners + $this->assertFalse($page->canBeReviewedBy($author)); + SS_Datetime::clear_mock_now(); + } + + public function testCanBeReviewedByUser() { + SS_Datetime::set_mock_now('2010-03-01 12:00:00'); + $author = $this->objFromFixture('Member', 'author'); + $page = $this->objFromFixture('Page', 'staff'); + // page 'staff' is owned by author + $this->assertTrue($page->canBeReviewedBy($author)); + SS_Datetime::clear_mock_now(); + } + + public function testCanNotBeReviewedByGroup() { + SS_Datetime::set_mock_now('2010-03-01 12:00:00'); + $author = $this->objFromFixture('Member', 'editor'); + $page = $this->objFromFixture('Page', 'contact'); + // page 'contact' is owned by the authorgroup + $this->assertFalse($page->canBeReviewedBy($author)); + SS_Datetime::clear_mock_now(); + } + + public function testCanBeReviewedByGroup() { + SS_Datetime::set_mock_now('2010-03-01 12:00:00'); + $author = $this->objFromFixture('Member', 'author'); + $page = $this->objFromFixture('Page', 'contact'); + // page 'contact' is owned by the authorgroup + $this->assertTrue($page->canBeReviewedBy($author)); + SS_Datetime::clear_mock_now(); + } public function testCanBeReviewedFromInheritedSetting() { SS_Datetime::set_mock_now('2013-03-01 12:00:00'); @@ -112,25 +118,28 @@ class SiteTreeContentReviewTest extends FunctionalTest { $page = $this->objFromFixture('Page', 'contact-child'); $this->assertTrue($page->canBeReviewedBy($author)); + SS_Datetime::clear_mock_now(); } -// public function testReviewActionVisibleForAuthor() { -// SS_Datetime::set_mock_now('2020-03-01 12:00:00'); -// $page = $this->objFromFixture('Page', 'contact'); -// $author = $this->objFromFixture('Member', 'author'); -// $this->logInAs($author); -// -// $fields = $page->getCMSActions(); -// $this->assertNotNull($fields->fieldByName('action_reviewed')); -// } -// -// public function testReviewActionNotVisibleForEditor() { -// SS_Datetime::set_mock_now('2020-03-01 12:00:00'); -// $page = $this->objFromFixture('Page', 'contact'); -// $author = $this->objFromFixture('Member', 'editor'); -// $this->logInAs($author); -// -// $fields = $page->getCMSActions(); -// $this->assertNull($fields->fieldByName('action_reviewed')); -// } + public function testReviewActionVisibleForAuthor() { + SS_Datetime::set_mock_now('2020-03-01 12:00:00'); + $page = $this->objFromFixture('Page', 'contact'); + $author = $this->objFromFixture('Member', 'author'); + $this->logInAs($author); + + $fields = $page->getCMSActions(); + $this->assertNotNull($fields->fieldByName('action_reviewed')); + SS_Datetime::clear_mock_now(); + } + + public function testReviewActionNotVisibleForEditor() { + SS_Datetime::set_mock_now('2020-03-01 12:00:00'); + $page = $this->objFromFixture('Page', 'contact'); + $author = $this->objFromFixture('Member', 'editor'); + $this->logInAs($author); + + $fields = $page->getCMSActions(); + $this->assertNull($fields->fieldByName('action_reviewed')); + SS_Datetime::clear_mock_now(); + } }