plugins/calendar plugins/libcalendaring plugins/tasklist

Thomas Brüderli bruederli at kolabsys.com
Thu Jul 31 18:22:21 CEST 2014


 plugins/calendar/calendar.php             |  138 +++++----------------------
 plugins/libcalendaring/libcalendaring.php |  138 +++++++++++++++++++++++++++
 plugins/tasklist/tasklist.php             |  153 +++++-------------------------
 3 files changed, 193 insertions(+), 236 deletions(-)

New commits:
commit 978c9023e5a2a94be4a1e1569d134a64ba9e474b
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Jul 31 18:21:53 2014 +0200

    Move iTip message parsing functionality to libcalendaring. Only parse iCal attachments once although used by calendar and tasks

diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index ae1c4ce..9bc6be5 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -173,7 +173,6 @@ class calendar extends rcube_plugin
     else if ($args['task'] == 'mail') {
       // hooks to catch event invitations on incoming mails
       if ($args['action'] == 'show' || $args['action'] == 'preview') {
-        $this->add_hook('message_load', array($this, 'mail_message_load'));
         $this->add_hook('template_object_messagebody', array($this, 'mail_messagebody_html'));
       }
 
@@ -2287,7 +2286,7 @@ class calendar extends rcube_plugin
       foreach ($p['messages'] as $i => $header) {
         $part = new StdClass;
         $part->mimetype = $header->ctype;
-        if ($this->is_vcalendar($part)) {
+        if (libcalendaring::part_is_vcalendar($part)) {
           $header->list_flags['attachmentClass'] = 'ical';
         }
         else if (in_array($header->ctype, array('multipart/alternative', 'multipart/mixed'))) {
@@ -2295,7 +2294,7 @@ class calendar extends rcube_plugin
 
           if (!empty($header->structure) && is_array($header->structure->parts)) {
             foreach ($header->structure->parts as $part) {
-              if ($this->is_vcalendar($part) && !empty($part->ctype_parameters['method'])) {
+              if (libcalendaring::part_is_vcalendar($part) && !empty($part->ctype_parameters['method'])) {
                 $header->list_flags['attachmentClass'] = 'ical';
                 break;
               }
@@ -2305,29 +2304,6 @@ class calendar extends rcube_plugin
       }
     }
   }
-  
-  /**
-   * Check mail message structure of there are .ics files attached
-   */
-  public function mail_message_load($p)
-  {
-    $this->message = $p['object'];
-    $itip_part = null;
-
-    // check all message parts for .ics files
-    foreach ((array)$this->message->mime_parts as $part) {
-      if ($this->is_vcalendar($part)) {
-        if ($part->ctype_parameters['method'])
-          $itip_part = $part->mime_id;
-        else
-          $this->ics_parts[] = $part->mime_id;
-      }
-    }
-    
-    // priorize part with method parameter
-    if ($itip_part)
-      $this->ics_parts = array($itip_part);
-  }
 
   /**
    * Add UI element to copy event invitations or updates to the calendar
@@ -2335,47 +2311,38 @@ class calendar extends rcube_plugin
   public function mail_messagebody_html($p)
   {
     // load iCalendar functions (if necessary)
-    if (!empty($this->ics_parts)) {
+    if (!empty($this->lib->ical_parts)) {
       $this->get_ical();
       $this->load_itip();
     }
 
     $html = '';
     $has_events = false;
-    foreach ($this->ics_parts as $mime_id) {
-      $part    = $this->message->mime_parts[$mime_id];
-      $charset = $part->ctype_parameters['charset'] ? $part->ctype_parameters['charset'] : RCMAIL_CHARSET;
-      $events  = $this->ical->import($this->message->get_part_content($mime_id), $charset);
-      $title   = $this->gettext('title');
-
-      // successfully parsed events?
-      if (empty($events))
-          continue;
-
-      // show a box for every event in the file
-      foreach ($events as $idx => $event) {
-        if ($event['_type'] != 'event')  // skip non-event objects (#2928)
-          continue;
-
-        $has_events = true;
-
-        // get prepared inline UI for this event object
-        if ($this->ical->method) {
-          $html .= html::div('calendar-invitebox',
-            $this->itip->mail_itip_inline_ui(
-              $event,
-              $this->ical->method,
-              $mime_id.':'.$idx,
-              'calendar',
-              rcube_utils::anytodatetime($this->message->headers->date)
-            )
-          );
-        }
+    $ical_objects = $this->lib->get_mail_ical_objects();
 
-        // limit listing
-        if ($idx >= 3)
-          break;
+    // show a box for every event in the file
+    foreach ($ical_objects as $idx => $event) {
+      if ($event['_type'] != 'event')  // skip non-event objects (#2928)
+        continue;
+
+      $has_events = true;
+
+      // get prepared inline UI for this event object
+      if ($ical_objects->method) {
+        $html .= html::div('calendar-invitebox',
+          $this->itip->mail_itip_inline_ui(
+            $event,
+            $ical_objects->method,
+            $ical_objects->mime_id . ':' . $idx,
+            'calendar',
+            rcube_utils::anytodatetime($ical_objects->message_date)
+          )
+        );
       }
+
+      // limit listing
+      if ($idx >= 3)
+        break;
     }
 
     // prepend event boxes to message body
@@ -2403,40 +2370,6 @@ class calendar extends rcube_plugin
     return $p;
   }
 
-  /**
-   * Read the given mime message from IMAP and parse ical data
-   */
-  private function mail_get_itip_event($mbox, $uid, $mime_id)
-  {
-    $charset = RCMAIL_CHARSET;
-
-    // establish imap connection
-    $imap = $this->rc->get_storage();
-    $imap->set_mailbox($mbox);
-
-    if ($uid && $mime_id) {
-      list($mime_id, $index) = explode(':', $mime_id);
-      $part = $imap->get_message_part($uid, $mime_id);
-      if ($part->ctype_parameters['charset'])
-        $charset = $part->ctype_parameters['charset'];
-      $headers = $imap->get_message_headers($uid);
-
-      if ($part) {
-        $events = $this->get_ical()->import($part, $charset);
-      }
-    }
-
-    // successfully parsed events?
-    if (!empty($events) && ($event = $events[$index])) {
-      // store the message's sender address for comparisons
-      $event['_sender'] = preg_match('/([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,}))/', $headers->from, $m) ? $m[1] : '';
-      $event['_sender_utf'] = rcube_idn_to_utf8($event['_sender']);
-
-      return $event;
-    }
-
-    return null;
-  }
 
   /**
    * Handler for POST request to import an event attached to a mail message
@@ -2454,7 +2387,7 @@ class calendar extends rcube_plugin
     $success = false;
 
     // successfully parsed events?
-    if ($event = $this->mail_get_itip_event($mbox, $uid, $mime_id)) {
+    if ($event = $this->lib->mail_get_itip_object($mbox, $uid, $mime_id, 'event')) {
       // find writeable calendar to store event
       $cal_id = !empty($_REQUEST['_folder']) ? get_input_value('_folder', RCUBE_INPUT_POST) : null;
       $calendars = $this->driver->list_calendars(false, true);
@@ -2635,7 +2568,7 @@ class calendar extends rcube_plugin
     $mbox = get_input_value('_mbox', RCUBE_INPUT_POST);
     $mime_id = get_input_value('_part', RCUBE_INPUT_POST);
 
-    if (($event = $this->mail_get_itip_event($mbox, $uid, $mime_id)) && $this->ical->method == 'REPLY') {
+    if (($event = $this->lib->mail_get_itip_object($mbox, $uid, $mime_id, 'event')) && $event['_method'] == 'REPLY') {
       $event['comment'] = get_input_value('_comment', RCUBE_INPUT_POST);
 
       foreach ($event['attendees'] as $_attendee) {
@@ -2807,21 +2740,6 @@ class calendar extends rcube_plugin
     return $args;
   }
 
-  /**
-   * Checks if specified message part is a vcalendar data
-   *
-   * @param rcube_message_part Part object
-   * @return boolean True if part is of type vcard
-   */
-  private function is_vcalendar($part)
-  {
-    return (
-      in_array($part->mimetype, array('text/calendar', 'text/x-vcalendar', 'application/ics')) ||
-      // Apple sends files as application/x-any (!?)
-      ($part->mimetype == 'application/x-any' && $part->filename && preg_match('/\.ics$/i', $part->filename))
-    );
-  }
-
 
   /**
    * Get a list of email addresses of the current user (from login and identities)
diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php
index a8b70cb..6eae2df 100644
--- a/plugins/libcalendaring/libcalendaring.php
+++ b/plugins/libcalendaring/libcalendaring.php
@@ -35,6 +35,8 @@ class libcalendaring extends rcube_plugin
     public $gmt_offset;
     public $dst_active;
     public $timezone_offset;
+    public $ical_parts = array();
+    public $ical_message;
 
     public $defaults = array(
       'calendar_date_format'  => "yyyy-MM-dd",
@@ -57,6 +59,8 @@ class libcalendaring extends rcube_plugin
 
     private static $instance;
 
+    private $mail_ical_parser;
+
     /**
      * Singleton getter to allow direct access from other plugins
      */
@@ -102,6 +106,21 @@ class libcalendaring extends rcube_plugin
             $this->add_hook('refresh', array($this, 'refresh'));
             $this->register_action('plugin.alarms', array($this, 'alarms_action'));
         }
+
+        // proceed initialization in startup hook
+        $this->add_hook('startup', array($this, 'startup'));
+    }
+
+    /**
+     * Startup hook
+     */
+    public function startup($args)
+    {
+        if ($args['task'] == 'mail') {
+            if ($args['action'] == 'show' || $args['action'] == 'preview') {
+                $this->add_hook('message_load', array($this, 'mail_message_load'));
+            }
+        }
     }
 
     /**
@@ -1220,6 +1239,125 @@ class libcalendaring extends rcube_plugin
     }
 
 
+    /*********  iTip message detection  *********/
+
+    /**
+     * Check mail message structure of there are .ics files attached
+     */
+    public function mail_message_load($p)
+    {
+        $this->ical_message = $p['object'];
+        $itip_part     = null;
+
+        // check all message parts for .ics files
+        foreach ((array)$this->ical_message->mime_parts as $part) {
+            if (self::part_is_vcalendar($part)) {
+                if ($part->ctype_parameters['method'])
+                    $itip_part = $part->mime_id;
+                else
+                    $this->ical_parts[] = $part->mime_id;
+            }
+        }
+
+        // priorize part with method parameter
+        if ($itip_part) {
+            $this->ical_parts = array($itip_part);
+        }
+    }
+
+    /**
+     * Getter for the parsed iCal objects attached to the current email message
+     *
+     * @return object libvcalendar parser instance with the parsed objects
+     */
+    public function get_mail_ical_objects()
+    {
+        // create parser and load ical objects
+        if (!$this->mail_ical_parser) {
+            $this->mail_ical_parser = $this->get_ical();
+
+            foreach ($this->ical_parts as $mime_id) {
+                $part    = $this->ical_message->mime_parts[$mime_id];
+                $charset = $part->ctype_parameters['charset'] ?: RCMAIL_CHARSET;
+                $this->mail_ical_parser->import($this->ical_message->get_part_content($mime_id), $charset);
+
+                // stop on the part that has an iTip method specified
+                if (count($this->mail_ical_parser->objects) && $this->mail_ical_parser->method) {
+                    $this->mail_ical_parser->message_date = $this->ical_message->headers->date;
+                    $this->mail_ical_parser->mime_id = $mime_id;
+                    break;
+                }
+            }
+        }
+
+        return $this->mail_ical_parser;
+    }
+
+    /**
+     * Read the given mime message from IMAP and parse ical data
+     *
+     * @param string Mailbox name
+     * @param string Message UID
+     * @param string Message part ID and object index (e.g. '1.2:0')
+     * @param string Object type filter (optional)
+     *
+     * @return array Hash array with the parsed iCal 
+     */
+    public function mail_get_itip_object($mbox, $uid, $mime_id, $type = null)
+    {
+        $charset = RCMAIL_CHARSET;
+
+        // establish imap connection
+        $imap = $this->rc->get_storage();
+        $imap->set_mailbox($mbox);
+
+        if ($uid && $mime_id) {
+            list($mime_id, $index) = explode(':', $mime_id);
+
+            $part    = $imap->get_message_part($uid, $mime_id);
+            $headers = $imap->get_message_headers($uid);
+            $parser  = $this->get_ical();
+
+            if ($part->ctype_parameters['charset']) {
+                $charset = $part->ctype_parameters['charset'];
+            }
+
+            if ($part) {
+                $objects = $parser->import($part, $charset);
+            }
+        }
+
+        // successfully parsed events/tasks?
+        if (!empty($objects) && ($object = $objects[$index]) && (!$type || $object['_type'] == $type)) {
+            if ($parser->method)
+                $object['_method'] = $parser->method;
+
+            // store the message's sender address for comparisons
+            $object['_sender'] = preg_match('/([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,}))/', $headers->from, $m) ? $m[1] : '';
+            $object['_sender_utf'] = rcube_idn_to_utf8($object['_sender']);
+
+            return $object;
+        }
+
+        return null;
+    }
+
+    /**
+     * Checks if specified message part is a vcalendar data
+     *
+     * @param rcube_message_part Part object
+     * @return boolean True if part is of type vcard
+     */
+    public static function part_is_vcalendar($part)
+    {
+        return (
+            in_array($part->mimetype, array('text/calendar', 'text/x-vcalendar', 'application/ics')) ||
+            // Apple sends files as application/x-any (!?)
+            ($part->mimetype == 'application/x-any' && $part->filename && preg_match('/\.ics$/i', $part->filename))
+        );
+    }
+
+
     /*********  Static utility functions  *********/
 
     /**
diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php
index 9aca0df..29d30c8 100644
--- a/plugins/tasklist/tasklist.php
+++ b/plugins/tasklist/tasklist.php
@@ -123,7 +123,6 @@ class tasklist extends rcube_plugin
         }
         else if ($args['task'] == 'mail') {
             if ($args['action'] == 'show' || $args['action'] == 'preview') {
-                $this->add_hook('message_load', array($this, 'mail_message_load'));
                 $this->add_hook('template_object_messagebody', array($this, 'mail_messagebody_html'));
             }
 
@@ -1337,84 +1336,44 @@ class tasklist extends rcube_plugin
     }
 
     /**
-     * Check mail message structure of there are .ics files attached
-     *
-     * @todo move to libcalendaring
-     */
-    public function mail_message_load($p)
-    {
-        $this->message = $p['object'];
-        $itip_part     = null;
-
-        // check all message parts for .ics files
-        foreach ((array)$this->message->mime_parts as $part) {
-            if ($this->is_vcalendar($part)) {
-                if ($part->ctype_parameters['method'])
-                    $itip_part = $part->mime_id;
-                else
-                    $this->ics_parts[] = $part->mime_id;
-            }
-        }
-
-        // priorize part with method parameter
-        if ($itip_part) {
-            $this->ics_parts = array($itip_part);
-        }
-    }
-
-    /**
-     * Add UI element to copy event invitations or updates to the calendar
-     *
-     * @todo move to libcalendaring
+     * Add UI element to copy task invitations or updates to the tasklist
      */
     public function mail_messagebody_html($p)
     {
         // load iCalendar functions (if necessary)
-        if (!empty($this->ics_parts)) {
+        if (!empty($this->lib->ical_parts)) {
             $this->get_ical();
             $this->load_itip();
         }
 
-        // @todo: Calendar plugin does the same, which means the
-        // attachment body is fetched twice, this is not optimal
         $html = '';
         $has_tasks = false;
-        foreach ($this->ics_parts as $mime_id) {
-            $part    = $this->message->mime_parts[$mime_id];
-            $charset = $part->ctype_parameters['charset'] ? $part->ctype_parameters['charset'] : RCMAIL_CHARSET;
-            $objects = $this->ical->import($this->message->get_part_content($mime_id), $charset);
-            $title   = $this->gettext('title');
-
-            // successfully parsed events?
-            if (empty($objects)) {
+        $ical_objects = $this->lib->get_mail_ical_objects();
+
+        // show a box for every task in the file
+        foreach ($ical_objects as $idx => $task) {
+            if ($task['_type'] != 'task') {
                 continue;
             }
 
-            // show a box for every task in the file
-            foreach ($objects as $idx => $task) {
-                if ($task['_type'] != 'task') {
-                    continue;
-                }
-
-                $has_tasks = true;
-
-                // get prepared inline UI for this event object
-                if ($this->ical->method) {
-                    $html .= html::div('tasklist-invitebox',
-                        $this->itip->mail_itip_inline_ui(
-                            $task,
-                            $this->ical->method,
-                            $mime_id . ':' . $idx,
-                            'tasks',
-                            rcube_utils::anytodatetime($this->message->headers->date)
-                        )
-                    );
-                }
+            $has_tasks = true;
+
+            // get prepared inline UI for this event object
+            if ($ical_objects->method) {
+                $html .= html::div('tasklist-invitebox',
+                    $this->itip->mail_itip_inline_ui(
+                        $task,
+                        $ical_objects->method,
+                        $ical_objects->mime_id . ':' . $idx,
+                        'tasks',
+                        rcube_utils::anytodatetime($ical_objects->message_date)
+                    )
+                );
+            }
 
-                // limit listing
-                if ($idx >= 3) {
-                    break;
-                }
+            // limit listing
+            if ($idx >= 3) {
+                break;
             }
         }
 
@@ -1447,66 +1406,6 @@ class tasklist extends rcube_plugin
     }
 
     /**
-     * Read the given mime message from IMAP and parse ical data
-     *
-     * @todo move to libcalendaring
-     */
-    private function mail_get_itip_task($mbox, $uid, $mime_id)
-    {
-        $charset = RCMAIL_CHARSET;
-
-        // establish imap connection
-        $imap = $this->rc->get_storage();
-        $imap->set_mailbox($mbox);
-
-        if ($uid && $mime_id) {
-            list($mime_id, $index) = explode(':', $mime_id);
-
-            $part    = $imap->get_message_part($uid, $mime_id);
-            $headers = $imap->get_message_headers($uid);
-
-            if ($part->ctype_parameters['charset']) {
-                $charset = $part->ctype_parameters['charset'];
-            }
-
-            if ($part) {
-                $tasks = $this->get_ical()->import($part, $charset);
-            }
-        }
-
-        // successfully parsed events?
-        if (!empty($tasks) && ($task = $tasks[$index])) {
-            $task = $this->from_ical($task);
-
-            // store the message's sender address for comparisons
-            $task['_sender'] = preg_match('/([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,}))/', $headers->from, $m) ? $m[1] : '';
-            $askt['_sender_utf'] = rcube_idn_to_utf8($task['_sender']);
-
-            return $task;
-        }
-
-        return null;
-    }
-
-    /**
-     * Checks if specified message part is a vcalendar data
-     *
-     * @param rcube_message_part Part object
-     *
-     * @return boolean True if part is of type vcard
-     *
-     * @todo move to libcalendaring
-     */
-    private function is_vcalendar($part)
-    {
-        return (
-            in_array($part->mimetype, array('text/calendar', 'text/x-vcalendar', 'application/ics')) ||
-            // Apple sends files as application/x-any (!?)
-            ($part->mimetype == 'application/x-any' && $part->filename && preg_match('/\.ics$/i', $part->filename))
-        );
-    }
-
-    /**
      * Load iCalendar functions
      */
     public function get_ical()
@@ -1624,7 +1523,9 @@ class tasklist extends rcube_plugin
         $success   = false;
 
         // successfully parsed tasks?
-        if ($task = $this->mail_get_itip_task($mbox, $uid, $mime_id)) {
+        if ($task = $this->lib->mail_get_itip_object($mbox, $uid, $mime_id, 'task')) {
+            $task = $this->from_ical($task);
+
             // find writeable list to store the task
             $list_id = !empty($_REQUEST['_list']) ? rcube_utils::get_input_value('_list', rcube_utils::INPUT_POST) : null;
             $lists   = $this->driver->get_lists();




More information about the commits mailing list