Calendar - send notifications about upcoming, tentative events to organizer
authorPaul Mehrer <p.mehrer@metaways.de>
Wed, 14 Jun 2017 14:37:29 +0000 (16:37 +0200)
committerPaul Mehrer <p.mehrer@metaways.de>
Thu, 15 Jun 2017 06:21:57 +0000 (08:21 +0200)
configurably (default: off) send notifications about future events that are
still tentative to organizer. Timespan in days configurable. Additional
filter can be configured too.

Change-Id: I675cc12c0ed14abe7c4a12f6b5254c9d8a61bde3
Reviewed-on: http://gerrit.tine20.com/customers/4877
Tested-by: Jenkins CI (http://ci.tine20.com/)
Reviewed-by: Paul Mehrer <p.mehrer@metaways.de>
Tested-by: Paul Mehrer <p.mehrer@metaways.de>
tests/tine20/Calendar/Controller/EventNotificationsTests.php
tine20/Calendar/Config.php
tine20/Calendar/Controller/Event.php
tine20/Calendar/Controller/EventNotifications.php
tine20/Calendar/Model/Event.php
tine20/Calendar/Model/EventFilter.php
tine20/Calendar/Scheduler/Task.php
tine20/Calendar/Setup/Initialize.php
tine20/Calendar/Setup/Update/Release10.php
tine20/Calendar/Setup/setup.xml

index ebc4b04..8f657f9 100644 (file)
@@ -1473,4 +1473,47 @@ class Calendar_Controller_EventNotificationsTests extends Calendar_TestCase
         $this->_assertMail('jsmith', NULL);
         $this->_assertMail('pwulf, sclever, jmcblack, rwright', 'cancel');
     }
+
+    public function testSendTentativeNotifications()
+    {
+        $tentConf = Calendar_Config::getInstance()->{Calendar_Config::TENTATIVE_NOTIFICATIONS};
+        $oldValue = $tentConf->{Calendar_Config::TENTATIVE_NOTIFICATIONS_ENABLED};
+        $tentConf->{Calendar_Config::TENTATIVE_NOTIFICATIONS_ENABLED} = true;
+        try {
+            $event = $this->_getEvent(true);
+            $event->dtstart->addHour(15);
+            $event->dtend->addHour(15);
+            $event->status = Calendar_Model_Event::STATUS_TENTATIVE;
+            $event->organizer = $this->_getPersonasContacts('sclever')->getId();
+            $this->_eventController->create($event);
+
+            self::flushMailer();
+            $this->_eventController->sendTentativeNotifications();
+            $this->_assertMail('sclever', 'Tentative');
+        } finally {
+            $tentConf->{Calendar_Config::TENTATIVE_NOTIFICATIONS_ENABLED} = $oldValue;
+        }
+    }
+
+    public function testSendTentativeNotificationsNoAttenders()
+    {
+        $tentConf = Calendar_Config::getInstance()->{Calendar_Config::TENTATIVE_NOTIFICATIONS};
+        $oldValue = $tentConf->{Calendar_Config::TENTATIVE_NOTIFICATIONS_ENABLED};
+        $tentConf->{Calendar_Config::TENTATIVE_NOTIFICATIONS_ENABLED} = true;
+        try {
+            $event = $this->_getEvent(TRUE);
+            $event->attendee = null;
+            $event->dtstart->addHour(15);
+            $event->dtend->addHour(15);
+            $event->status = Calendar_Model_Event::STATUS_TENTATIVE;
+            $event->organizer = $this->_getPersonasContacts('sclever')->getId();
+            $this->_eventController->create($event);
+
+            self::flushMailer();
+            $this->_eventController->sendTentativeNotifications();
+            $this->_assertMail('sclever', 'Tentative');
+        } finally {
+            $tentConf->{Calendar_Config::TENTATIVE_NOTIFICATIONS_ENABLED} = $oldValue;
+        }
+    }
 }
index 5365ad4..bf68a72 100644 (file)
@@ -161,6 +161,26 @@ class Calendar_Config extends Tinebase_Config_Abstract
     const FEATURE_RECUR_EXCEPT = 'featureRecurExcept';
 
     /**
+     * @var string
+     */
+    const TENTATIVE_NOTIFICATIONS = 'tentativeNotifications';
+
+    /**
+     * @var string
+     */
+    const TENTATIVE_NOTIFICATIONS_ENABLED = 'enabled';
+
+    /**
+     * @var string
+     */
+    const TENTATIVE_NOTIFICATIONS_DAYS = 'days';
+
+    /**
+     * @var string
+     */
+    const TENTATIVE_NOTIFICATIONS_FILTER = 'filter';
+
+    /**
      * (non-PHPdoc)
      * @see tine20/Tinebase/Config/Definition::$_properties
      */
@@ -385,22 +405,27 @@ class Calendar_Config extends Tinebase_Config_Abstract
                 self::FEATURE_SPLIT_VIEW => array(
                     'label'         => 'Calendar Split View', //_('Calendar Split View')
                     'description'   => 'Split day and week views by attendee', //_('Split day and week views by attendee')
+                    'type'          => Tinebase_Config_Abstract::TYPE_BOOL,
                 ),
                 self::FEATURE_YEAR_VIEW => array(
                     'label'         => 'Calendar Year View', //_('Calendar Year View')
                     'description'   => 'Adds year view to Calendar', //_('Adds year view to Calendar')
+                    'type'          => Tinebase_Config_Abstract::TYPE_BOOL,
                 ),
                 self::FEATURE_EXTENDED_EVENT_CONTEXT_ACTIONS => array(
                     'label'         => 'Calendar Extended Context Menu Actions', //_('Calendar Extended Context Menu Actions')
                     'description'   => 'Adds extended actions to event context menus', //_('Adds extended actions to event context menus')
+                    'type'          => Tinebase_Config_Abstract::TYPE_BOOL,
                 ),
                 self::FEATURE_COLOR_BY => array(
                     'label'         => 'Color Events By', //_('Color Events By')
                     'description'   => 'Choose event color by different criteria', //_('Choose event color by different criteria')
+                    'type'          => Tinebase_Config_Abstract::TYPE_BOOL,
                 ),
                 self::FEATURE_RECUR_EXCEPT => array(
-                        'label'         => 'Recur Events Except', //_('Recur Events Except')
-                        'description'   => 'Recur Events except on certain dates', //_('Recur Events except on certain dates')
+                    'label'         => 'Recur Events Except', //_('Recur Events Except')
+                    'description'   => 'Recur Events except on certain dates', //_('Recur Events except on certain dates')
+                    'type'          => Tinebase_Config_Abstract::TYPE_BOOL,
                 ),
             ),
             'default'               => array(
@@ -411,6 +436,36 @@ class Calendar_Config extends Tinebase_Config_Abstract
                 self::FEATURE_RECUR_EXCEPT                      => false,
             ),
         ),
+        self::TENTATIVE_NOTIFICATIONS => array(
+            'label'                 => 'Send Tentative Notifications', //_('Send Tentative Notifications')
+            'description'           => 'Send notifications to event organiziers of events that are tentative certain days before event is due', //_('Send notifications to event organiziers of events that are tentative certain days before event is due')
+            'type'                  => 'object',
+            'class'                 => 'Tinebase_Config_Struct',
+            'clientRegistryInclude' => TRUE,
+            'setBySetupModule'      => false,
+            'setByAdminModule'      => true,
+            'content'               => array(
+                self::TENTATIVE_NOTIFICATIONS_ENABLED   => array(
+                    'label'         => 'Enabled', //_('Enabled')
+                    'description'   => 'Enabled', //_('Enabled')
+                    'type'          => Tinebase_Config_Abstract::TYPE_BOOL,
+                    'default'       => false,
+                ),
+                self::TENTATIVE_NOTIFICATIONS_DAYS      => array(
+                    'label'         => 'Days Before Due Date', //_('Days Before Due Date')
+                    'description'   => 'How many days before the events due date to start send notifications.', //_('How many days before the events due date to start send notifications.')
+                    'type'          => Tinebase_Config_Abstract::TYPE_INT,
+                    'default'       => 5,
+                ),
+                self::TENTATIVE_NOTIFICATIONS_FILTER    => array(
+                    'label'         => 'Additional Filter', //_('Additional Filter')
+                    'description'   => 'Additional filter to limit events notifications should be send for.', //_('Additional filter to limit events notifications should be send for.')
+                    'type'          => Tinebase_Config_Abstract::TYPE_STRING,
+                    'default'       => NULL,
+                ),
+            ),
+            'default'               => array(),
+        ),
     );
     
     /**
index 8920c5e..b49a64e 100644 (file)
@@ -2937,4 +2937,35 @@ class Calendar_Controller_Event extends Tinebase_Controller_Record_Abstract impl
         
         return $updateCount;
     }
+
+    public function sendTentativeNotifications()
+    {
+        $eventNotificationController = Calendar_Controller_EventNotifications::getInstance();
+        $calConfig = Calendar_Config::getInstance();
+        if (true !== $calConfig->{Calendar_Config::TENTATIVE_NOTIFICATIONS}
+                ->{Calendar_Config::TENTATIVE_NOTIFICATIONS_ENABLED}) {
+            return;
+        }
+
+        $days = $calConfig->{Calendar_Config::TENTATIVE_NOTIFICATIONS}->{Calendar_Config::TENTATIVE_NOTIFICATIONS_DAYS};
+        $additionalFilters = $calConfig->{Calendar_Config::TENTATIVE_NOTIFICATIONS}
+            ->{Calendar_Config::TENTATIVE_NOTIFICATIONS_FILTER};
+
+        $filter = array(
+            array('field' => 'period', 'operator' => 'within', 'value' => array(
+                'from'  => Tinebase_DateTime::now(),
+                'until' => Tinebase_DateTime::now()->addDay($days)
+            )),
+            array('field' => 'status', 'operator' => 'equals', 'value' => Calendar_Model_Event::STATUS_TENTATIVE)
+        );
+
+        if (null !== $additionalFilters) {
+            $filter = array_merge($filter, $additionalFilters);
+        }
+
+        $filter = new Calendar_Model_EventFilter($filter);
+        foreach ($this->search($filter) as $event) {
+            $eventNotificationController->doSendNotifications($event, null, 'tentative');
+        }
+    }
 }
index 1816c92..f3e452b 100644 (file)
     /**
      * send notifications 
      * 
-     * @param Tinebase_Record_Interface  $_event
+     * @param Calendar_Model_Event       $_event
      * @param Tinebase_Model_FullUser    $_updater
      * @param String                     $_action
      * @param Tinebase_Record_Interface  $_oldEvent
      * @param Array                      $_additionalData
      */
-    public function doSendNotifications(Tinebase_Record_Interface $_event, Tinebase_Model_FullUser $_updater, $_action, Tinebase_Record_Interface $_oldEvent = NULL, array $_additionalData = array())
+    public function doSendNotifications(Calendar_Model_Event $_event, $_updater, $_action, Tinebase_Record_Interface $_oldEvent = NULL, array $_additionalData = array())
     {
         if (isset($_additionalData['alarm']))
         {
         }
 
         // we only send notifications to attendee
-        if (! $_event->attendee instanceof Tinebase_Record_RecordSet) {
+        if (! $_event->attendee instanceof Tinebase_Record_RecordSet && 'tentative' !== $_action) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
                 . " Event has no attendee");
             return;
         }
         
         // lets resolve attendee once as batch to fill cache
-        $attendee = clone $_event->attendee;
-        Calendar_Model_Attender::resolveAttendee($attendee);
+        if (null !== $_event->attendee) {
+            $attendee = clone $_event->attendee;
+            Calendar_Model_Attender::resolveAttendee($attendee);
+        }
         
         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
             . " " . print_r($_event->toArray(), true));
                 }
                 
                 break;
-                
+
+            case 'tentative':
+                $attendee = new Calendar_Model_Attender(array(
+                    'cal_event_id'      => $_event->getId(),
+                    'user_type'         => Calendar_Model_Attender::USERTYPE_USER,
+                    'user_id'           => $_event->organizer,
+                ), true);
+                $this->sendNotificationToAttender($attendee, $_event, $_updater, 'tentative', self::NOTIFICATION_LEVEL_NONE);
+                break;
+
             default:
                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " unknown action '$_action'");
                 break;
      * 
      * @param Calendar_Model_Attender    $_attender
      * @param Calendar_Model_Event       $_event
-     * @param Tinebase_Model_FullAccount $_updater
+     * @param Tinebase_Model_FullUser    $_updater
      * @param string                     $_action
      * @param string                     $_notificationLevel
      * @param array                      $_updates
 
             // check if user wants this notification NOTE: organizer gets mails unless she set notificationlevel to NONE
             // NOTE prefUser is organizer for external notifications
-            if (($attendeeAccountId == $_updater->getId() && ! $sendOnOwnActions) 
+            if ((null !== $_updater && $attendeeAccountId == $_updater->getId() && ! $sendOnOwnActions)
                 || ($sendLevel < $_notificationLevel && (
                         ((is_object($organizer) && method_exists($attendee, 'getPreferredEmailAddress') && $attendee->getPreferredEmailAddress() != $organizer->getPreferredEmailAddress())
                         || (is_object($organizer) && !method_exists($attendee, 'getPreferredEmailAddress') && $attendee->email != $organizer->getPreferredEmailAddress()))
                         break;
                 }
                 break;
+
+            case 'tentative':
+                $messageSubject = sprintf($translate->_('Tentative event notification for event "%1$s" at %2$s' ), $_event->summary, $startDateString);
+                break;
+
             default:
                 $messageSubject = 'unknown action';
                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " unknown action '$_action'");
      * @param string $method
      * @param Calendar_Model_Event $event
      * @param string $_action
-     * @param Tinebase_Model_FullAccount $updater
+     * @param Tinebase_Model_FullUser $updater
      * @param Zend_Mime_Part $calendarPart
      * @return array
      */
index 0e8037a..f1fe50d 100644 (file)
  * @property string                         uid
  * @property string                         etag
  * @property int                            container_id
+ * @property string                         organizer
  * @property Tinebase_Record_RecordSet      attendee
  * @property Tinebase_DateTime              dtstart
  * @property Tinebase_DateTime              dtend
  * @property Calendar_Model_Rrule           rrule
  * @property string                         transp
+ * @property string                         status
  */
 class Calendar_Model_Event extends Tinebase_Record_Abstract
 {
index e4cbaee..81f112e 100644 (file)
@@ -48,7 +48,7 @@ class Calendar_Model_EventFilter extends Tinebase_Model_Filter_FilterGroup
         'attender_role'         => array('filter' => 'Calendar_Model_AttenderRoleFilter'),
         'organizer'             => array('filter' => 'Addressbook_Model_ContactIdFilter', 'options' => array('modelName' => 'Addressbook_Model_Contact')),
         'class'                 => array('filter' => 'Tinebase_Model_Filter_Text'),
-        //'status'              => array('filter' => 'Tinebase_Model_Filter_Text'),
+        'status'                => array('filter' => 'Tinebase_Model_Filter_Text'),
         'tag'                   => array('filter' => 'Tinebase_Model_Filter_Tag', 'options' => array(
             'idProperty' => 'cal_events.id',
             'applicationName' => 'Calendar',
index d49bef2..8bb47bb 100644 (file)
@@ -5,7 +5,7 @@
  * @package     Calendar
  * @subpackage  Scheduler
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2016-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Cornelius WeiƟ <c.weiss@metaways.de>
  */
 
@@ -24,6 +24,10 @@ class Calendar_Scheduler_Task extends Tinebase_Scheduler_Task
      */
     public static function addUpdateConstraintsExdatesTask(Zend_Scheduler $_scheduler)
     {
+        if ($_scheduler->hasTask('Calendar_Controller_Event::updateConstraintsExdates')) {
+            return;
+        }
+
         $task = self::getPreparedTask(self::TASK_TYPE_DAILY, array(
             'controller'    => 'Calendar_Controller_Event',
             'action'        => 'updateConstraintsExdates',
@@ -34,4 +38,21 @@ class Calendar_Scheduler_Task extends Tinebase_Scheduler_Task
         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
             . ' Saved task Calendar_Controller_Event::updateConstraintsExdates in scheduler.');
     }
+
+    public static function addTentativeNotificationTask(Zend_Scheduler $_scheduler)
+    {
+        if ($_scheduler->hasTask('Calendar_Controller_Event::sendTentativeNotifications')) {
+            return;
+        }
+
+        $task = self::getPreparedTask(self::TASK_TYPE_DAILY, array(
+            'controller'    => 'Calendar_Controller_Event',
+            'action'        => 'sendTentativeNotifications',
+        ));
+        $_scheduler->addTask('Calendar_Controller_Event::sendTentativeNotifications', $task);
+        $_scheduler->saveTask();
+
+        if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
+            . ' Saved task Calendar_Controller_Event::sendTentativeNotifications in scheduler.');
+    }
 }
index 066ceb4..e7ba666 100644 (file)
@@ -5,7 +5,7 @@
  * @package     Calendar
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Jonas Fischer <j.fischer@metaways.de>
- * @copyright   Copyright (c) 2008-2011 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2008-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  *
  */
 
@@ -29,7 +29,7 @@ class Calendar_Setup_Initialize extends Setup_Initialize
             'model'             => 'Calendar_Model_EventFilter',
         );
         
-        $myEventsPFilter = $pfe->createDuringSetup(new Tinebase_Model_PersistentFilter(array_merge($commonValues, array(
+        $pfe->createDuringSetup(new Tinebase_Model_PersistentFilter(array_merge($commonValues, array(
             'name'              => Calendar_Preference::DEFAULTPERSISTENTFILTER_NAME,
             'description'       => "All events I attend", // _("All events I attend")
             'filters'           => array(
@@ -89,5 +89,6 @@ class Calendar_Setup_Initialize extends Setup_Initialize
     {
         $scheduler = Tinebase_Core::getScheduler();
         Calendar_Scheduler_Task::addUpdateConstraintsExdatesTask($scheduler);
+        Calendar_Scheduler_Task::addTentativeNotificationTask($scheduler);
     }
 }
index 3bbbfce..40cf5e0 100644 (file)
@@ -163,4 +163,17 @@ class Calendar_Setup_Update_Release10 extends Setup_Update_Abstract
 
         $this->setApplicationVersion('Calendar', '10.6');
     }
+
+    /**
+     * update to 10.7
+     *
+     * import export definitions
+     */
+    public function update_6()
+    {
+        $scheduler = Tinebase_Core::getScheduler();
+        Calendar_Scheduler_Task::addTentativeNotificationTask($scheduler);
+
+        $this->setApplicationVersion('Calendar', '10.7');
+    }
 }
index 9cc1a45..d24ac89 100644 (file)
@@ -2,7 +2,7 @@
 <application>
     <name>Calendar</name>
     <!-- gettext('Calendar') -->   
-    <version>10.6</version>
+    <version>10.7</version>
     <order>15</order>
     <status>enabled</status>
     <tables>