Back to index

d-push  2.0
class.recurrence.php
Go to the documentation of this file.
00001 <?php
00002 /*
00003  * Copyright 2005 - 2012  Zarafa B.V.
00004  *
00005  * This program is free software: you can redistribute it and/or modify
00006  * it under the terms of the GNU Affero General Public License, version 3,
00007  * as published by the Free Software Foundation with the following additional
00008  * term according to sec. 7:
00009  *
00010  * According to sec. 7 of the GNU Affero General Public License, version
00011  * 3, the terms of the AGPL are supplemented with the following terms:
00012  *
00013  * "Zarafa" is a registered trademark of Zarafa B.V. The licensing of
00014  * the Program under the AGPL does not imply a trademark license.
00015  * Therefore any rights, title and interest in our trademarks remain
00016  * entirely with us.
00017  *
00018  * However, if you propagate an unmodified version of the Program you are
00019  * allowed to use the term "Zarafa" to indicate that you distribute the
00020  * Program. Furthermore you may use our trademarks where it is necessary
00021  * to indicate the intended purpose of a product or service provided you
00022  * use it in accordance with honest practices in industrial or commercial
00023  * matters.  If you want to propagate modified versions of the Program
00024  * under the name "Zarafa" or "Zarafa Server", you may only do so if you
00025  * have a written permission by Zarafa B.V. (to acquire a permission
00026  * please contact Zarafa at trademark@zarafa.com).
00027  *
00028  * The interactive user interface of the software displays an attribution
00029  * notice containing the term "Zarafa" and/or the logo of Zarafa.
00030  * Interactive user interfaces of unmodified and modified versions must
00031  * display Appropriate Legal Notices according to sec. 5 of the GNU
00032  * Affero General Public License, version 3, when you propagate
00033  * unmodified or modified versions of the Program. In accordance with
00034  * sec. 7 b) of the GNU Affero General Public License, version 3, these
00035  * Appropriate Legal Notices must retain the logo of Zarafa or display
00036  * the words "Initial Development by Zarafa" if the display of the logo
00037  * is not reasonably feasible for technical reasons. The use of the logo
00038  * of Zarafa in Legal Notices is allowed for unmodified and modified
00039  * versions of the software.
00040  *
00041  * This program is distributed in the hope that it will be useful,
00042  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00043  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00044  * GNU Affero General Public License for more details.
00045  *
00046  * You should have received a copy of the GNU Affero General Public License
00047  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00048  *
00049  */
00050 
00051     include_once('backend/zarafa/mapi/class.baserecurrence.php');
00052 
00058     class Recurrence extends BaseRecurrence
00059     {
00060         /*
00061          * ABOUT TIMEZONES
00062          *
00063          * Timezones are rather complicated here so here are some rules to think about:
00064          *
00065          * - Timestamps in mapi-like properties (so in PT_SYSTIME properties) are always in GMT (including
00066          *   the 'basedate' property in exceptions !!)
00067          * - Timestamps for recurrence (so start/end of recurrence, and basedates for exceptions (everything
00068          *   outside the 'basedate' property in the exception !!), and start/endtimes for exceptions) are
00069          *   always in LOCAL time.
00070          */
00071 
00072         // All properties for a recipient that are interesting
00073         var $recipprops = Array(
00074             PR_ENTRYID,
00075             PR_SEARCH_KEY,
00076             PR_DISPLAY_NAME,
00077             PR_EMAIL_ADDRESS,
00078             PR_RECIPIENT_ENTRYID,
00079             PR_RECIPIENT_TYPE,
00080             PR_SEND_INTERNET_ENCODING,
00081             PR_SEND_RICH_INFO,
00082             PR_RECIPIENT_DISPLAY_NAME,
00083             PR_ADDRTYPE,
00084             PR_DISPLAY_TYPE,
00085             PR_DISPLAY_TYPE_EX,
00086             PR_RECIPIENT_TRACKSTATUS,
00087             PR_RECIPIENT_TRACKSTATUS_TIME,
00088             PR_RECIPIENT_FLAGS,
00089             PR_ROWID
00090         );
00091 
00097         function Recurrence($store, $message)
00098         {
00099 
00100             $properties = array();
00101             $properties["entryid"] = PR_ENTRYID;
00102             $properties["parent_entryid"] = PR_PARENT_ENTRYID;
00103             $properties["message_class"] = PR_MESSAGE_CLASS;
00104             $properties["icon_index"] = PR_ICON_INDEX;
00105             $properties["subject"] = PR_SUBJECT;
00106             $properties["display_to"] = PR_DISPLAY_TO;
00107             $properties["importance"] = PR_IMPORTANCE;
00108             $properties["sensitivity"] = PR_SENSITIVITY;
00109             $properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
00110             $properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e";
00111             $properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223";
00112             $properties["recurring_data"] = "PT_BINARY:PSETID_Appointment:0x8216";
00113             $properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205";
00114             $properties["label"] = "PT_LONG:PSETID_Appointment:0x8214";
00115             $properties["alldayevent"] = "PT_BOOLEAN:PSETID_Appointment:0x8215";
00116             $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
00117             $properties["meeting"] = "PT_LONG:PSETID_Appointment:0x8217";
00118             $properties["startdate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8235";
00119             $properties["enddate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8236";
00120             $properties["recurring_pattern"] = "PT_STRING8:PSETID_Appointment:0x8232";
00121             $properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208";
00122             $properties["duration"] = "PT_LONG:PSETID_Appointment:0x8213";
00123             $properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218";
00124             $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
00125             $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
00126             $properties["recurrencetype"] = "PT_LONG:PSETID_Appointment:0x8231";
00127             $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
00128             $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
00129             $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
00130             $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
00131             $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
00132             $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
00133             $properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228";
00134             $properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233";
00135             $properties["timezone"] = "PT_STRING8:PSETID_Appointment:0x8234";
00136             $properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560";
00137             $properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510";
00138             $properties["hideattachments"] = "PT_BOOLEAN:PSETID_Common:0x8514";
00139 
00140             $this->proptags = getPropIdsFromStrings($store, $properties);
00141 
00142             parent::BaseRecurrence($store, $message);
00143         }
00144 
00153         function createException($exception_props, $base_date, $delete = false, $exception_recips = array(), $copy_attach_from = false)
00154         {
00155             $baseday = $this->dayStartOf($base_date);
00156             $basetime = $baseday + $this->recur["startocc"] * 60;
00157 
00158             // Remove any pre-existing exception on this base date
00159             if($this->isException($baseday)) {
00160                 $this->deleteException($baseday); // note that deleting an exception is different from creating a deleted exception (deleting an occurrence).
00161             }
00162 
00163             if(!$delete) {
00164                 if(isset($exception_props[$this->proptags["startdate"]]) && !$this->isValidExceptionDate($base_date, $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]))) {
00165                     return false;
00166                 }
00167                 // Properties in the attachment are the properties of the base object, plus $exception_props plus the base date
00168                 foreach (array("subject", "location", "label", "reminder", "reminder_minutes", "alldayevent", "busystatus") as $propname) {
00169                     if(isset($this->messageprops[$this->proptags[$propname]]))
00170                         $props[$this->proptags[$propname]] = $this->messageprops[$this->proptags[$propname]];
00171                 }
00172 
00173                 $props[PR_MESSAGE_CLASS] = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}";
00174                 unset($exception_props[PR_MESSAGE_CLASS]);
00175                 unset($exception_props[PR_ICON_INDEX]);
00176                 $props = $exception_props + $props;
00177 
00178                 // Basedate in the exception attachment is the GMT time at which the original occurrence would have been
00179                 $props[$this->proptags["basedate"]] = $this->toGMT($this->tz, $basetime);
00180 
00181                 if (!isset($exception_props[$this->proptags["startdate"]])) {
00182                     $props[$this->proptags["startdate"]] = $this->getOccurrenceStart($base_date);
00183                 }
00184 
00185                 if (!isset($exception_props[$this->proptags["duedate"]])) {
00186                     $props[$this->proptags["duedate"]] = $this->getOccurrenceEnd($base_date);
00187                 }
00188 
00189                 // synchronize commonstart/commonend with startdate/duedate
00190                 if(isset($props[$this->proptags["startdate"]])) {
00191                     $props[$this->proptags["commonstart"]] = $props[$this->proptags["startdate"]];
00192                 }
00193 
00194                 if(isset($props[$this->proptags["duedate"]])) {
00195                     $props[$this->proptags["commonend"]] = $props[$this->proptags["duedate"]];
00196                 }
00197 
00198                 // Save the data into an attachment
00199                 $this->createExceptionAttachment($props, $exception_recips, $copy_attach_from);
00200 
00201                 $changed_item = array();
00202 
00203                 $changed_item["basedate"] = $baseday;
00204                 $changed_item["start"] = $this->fromGMT($this->tz, $props[$this->proptags["startdate"]]);
00205                 $changed_item["end"] = $this->fromGMT($this->tz, $props[$this->proptags["duedate"]]);
00206 
00207                 if(array_key_exists($this->proptags["subject"], $exception_props)) {
00208                     $changed_item["subject"] = $exception_props[$this->proptags["subject"]];
00209                 }
00210 
00211                 if(array_key_exists($this->proptags["location"], $exception_props)) {
00212                     $changed_item["location"] = $exception_props[$this->proptags["location"]];
00213                 }
00214 
00215                 if(array_key_exists($this->proptags["label"], $exception_props)) {
00216                     $changed_item["label"] = $exception_props[$this->proptags["label"]];
00217                 }
00218 
00219                 if(array_key_exists($this->proptags["reminder"], $exception_props)) {
00220                     $changed_item["reminder_set"] = $exception_props[$this->proptags["reminder"]];
00221                 }
00222 
00223                 if(array_key_exists($this->proptags["reminder_minutes"], $exception_props)) {
00224                     $changed_item["remind_before"] = $exception_props[$this->proptags["reminder_minutes"]];
00225                 }
00226 
00227                 if(array_key_exists($this->proptags["alldayevent"], $exception_props)) {
00228                     $changed_item["alldayevent"] = $exception_props[$this->proptags["alldayevent"]];
00229                 }
00230 
00231                 if(array_key_exists($this->proptags["busystatus"], $exception_props)) {
00232                     $changed_item["busystatus"] = $exception_props[$this->proptags["busystatus"]];
00233                 }
00234 
00235                 // Add the changed occurrence to the list
00236                 array_push($this->recur["changed_occurences"], $changed_item);
00237             } else {
00238                 // Delete the occurrence by placing it in the deleted occurrences list
00239                 array_push($this->recur["deleted_occurences"], $baseday);
00240             }
00241 
00242             // Turn on hideattachments, because the attachments in this item are the exceptions
00243             mapi_setprops($this->message, array ( $this->proptags["hideattachments"] => true ));
00244 
00245             // Save recurrence data to message
00246             $this->saveRecurrence();
00247 
00248             return true;
00249         }
00250 
00255         function modifyException($exception_props, $base_date, $exception_recips = array(), $copy_attach_from = false)
00256         {
00257             if(isset($exception_props[$this->proptags["startdate"]]) && !$this->isValidExceptionDate($base_date, $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]))) {
00258                 return false;
00259             }
00260 
00261             $baseday = $this->dayStartOf($base_date);
00262             $basetime = $baseday + $this->recur["startocc"] * 60;
00263             $extomodify = false;
00264 
00265             for($i = 0, $len = count($this->recur["changed_occurences"]); $i < $len; $i++) {
00266                 if($this->isSameDay($this->recur["changed_occurences"][$i]["basedate"], $baseday))
00267                     $extomodify = &$this->recur["changed_occurences"][$i];
00268             }
00269 
00270             if(!$extomodify)
00271                 return false;
00272 
00273             // remove basedate property as we want to preserve the old value
00274             // client will send basedate with time part as zero, so discard that value
00275             unset($exception_props[$this->proptags["basedate"]]);
00276 
00277             if(array_key_exists($this->proptags["startdate"], $exception_props)) {
00278                 $extomodify["start"] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
00279             }
00280 
00281             if(array_key_exists($this->proptags["duedate"], $exception_props)) {
00282                 $extomodify["end"] =   $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
00283             }
00284 
00285             if(array_key_exists($this->proptags["subject"], $exception_props)) {
00286                 $extomodify["subject"] = $exception_props[$this->proptags["subject"]];
00287             }
00288 
00289             if(array_key_exists($this->proptags["location"], $exception_props)) {
00290                 $extomodify["location"] = $exception_props[$this->proptags["location"]];
00291             }
00292 
00293             if(array_key_exists($this->proptags["label"], $exception_props)) {
00294                 $extomodify["label"] = $exception_props[$this->proptags["label"]];
00295             }
00296 
00297             if(array_key_exists($this->proptags["reminder"], $exception_props)) {
00298                 $extomodify["reminder_set"] = $exception_props[$this->proptags["reminder"]];
00299             }
00300 
00301             if(array_key_exists($this->proptags["reminder_minutes"], $exception_props)) {
00302                 $extomodify["remind_before"] = $exception_props[$this->proptags["reminder_minutes"]];
00303             }
00304 
00305             if(array_key_exists($this->proptags["alldayevent"], $exception_props)) {
00306                 $extomodify["alldayevent"] = $exception_props[$this->proptags["alldayevent"]];
00307             }
00308 
00309             if(array_key_exists($this->proptags["busystatus"], $exception_props)) {
00310                 $extomodify["busystatus"] = $exception_props[$this->proptags["busystatus"]];
00311             }
00312 
00313             $exception_props[PR_MESSAGE_CLASS] = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}";
00314 
00315             // synchronize commonstart/commonend with startdate/duedate
00316             if(isset($exception_props[$this->proptags["startdate"]])) {
00317                 $exception_props[$this->proptags["commonstart"]] = $exception_props[$this->proptags["startdate"]];
00318             }
00319 
00320             if(isset($exception_props[$this->proptags["duedate"]])) {
00321                 $exception_props[$this->proptags["commonend"]] = $exception_props[$this->proptags["duedate"]];
00322             }
00323 
00324             $attach = $this->getExceptionAttachment($baseday);
00325             if(!$attach) {
00326                 if ($copy_attach_from) {
00327                     $this->deleteExceptionAttachment($base_date);
00328                     $this->createException($exception_props, $base_date, false, $exception_recips, $copy_attach_from);
00329                 } else {
00330                     $this->createExceptionAttachment($exception_props, $exception_recips, $copy_attach_from);
00331                 }
00332             } else {
00333                 $message = mapi_attach_openobj($attach, MAPI_MODIFY);
00334 
00335                 // Set exception properties on embedded message and save
00336                 mapi_setprops($message, $exception_props);
00337                 $this->setExceptionRecipients($message, $exception_recips, false);
00338                 mapi_savechanges($message);
00339 
00340                 // If a new start or duedate is provided, we update the properties 'PR_EXCEPTION_STARTTIME' and 'PR_EXCEPTION_ENDTIME'
00341                 // on the attachment which holds the embedded msg and save everything.
00342                 $props = array();
00343                 if (isset($exception_props[$this->proptags["startdate"]])) {
00344                     $props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
00345                 }
00346                 if (isset($exception_props[$this->proptags["duedate"]])) {
00347                     $props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
00348                 }
00349                 if (!empty($props)) {
00350                     mapi_setprops($attach, $props);
00351                 }
00352 
00353                 mapi_savechanges($attach);
00354             }
00355 
00356             // Save recurrence data to message
00357             $this->saveRecurrence();
00358 
00359             return true;
00360         }
00361 
00362         // Checks to see if the following is true:
00363         // 1) The exception to be created doesn't create two exceptions starting on one day (however, they can END on the same day by modifying duration)
00364         // 2) The exception to be created doesn't 'jump' over another occurrence (which may be an exception itself!)
00365         //
00366         // Both $basedate and $start are in LOCAL time
00367         function isValidExceptionDate($basedate, $start)
00368         {
00369             // The way we do this is to look at the days that we're 'moving' the item in the exception. Each
00370             // of these days may only contain the item that we're modifying. Any other item violates the rules.
00371 
00372             if($this->isException($basedate)) {
00373                 // If we're modifying an exception, we want to look at the days that we're 'moving' compared to where
00374                 // the exception used to be.
00375                 $oldexception = $this->getChangeException($basedate);
00376                 $prevday = $this->dayStartOf($oldexception["start"]);
00377             } else {
00378                 // If its a new exception, we want to look at the original placement of this item.
00379                 $prevday = $basedate;
00380             }
00381 
00382             $startday = $this->dayStartOf($start);
00383 
00384             // Get all the occurrences on the days between the basedate (may be reversed)
00385             if($prevday < $startday)
00386                 $items = $this->getItems($this->toGMT($this->tz, $prevday), $this->toGMT($this->tz, $startday + 24 * 60 * 60));
00387             else
00388                 $items = $this->getItems($this->toGMT($this->tz, $startday), $this->toGMT($this->tz, $prevday + 24 * 60 * 60));
00389 
00390             // There should now be exactly one item, namely the item that we are modifying. If there are any other items in the range,
00391             // then we abort the change, since one of the rules has been violated.
00392             return count($items) == 1;
00393         }
00394 
00407         function isValidReminderTime($basedate, $reminderminutes, $startdate)
00408         {
00409             $isreminderrangeset = false;
00410 
00411             // get all occurence items before the seleceted items occurence starttime
00412             $occitems = $this->getItems($this->messageprops[$this->proptags["startdate"]], $this->toGMT($this->tz, $basedate));
00413 
00414             if(!empty($occitems)) {
00415                 // as occitems array is sorted in ascending order of startdate, to get the previous occurence we take the last items in occitems .
00416                 $previousitem_startdate = $occitems[count($occitems) - 1][$this->proptags["startdate"]];
00417 
00418                 // if our reminder is set before or equal to the beginning of the previous occurrence, then that's not allowed
00419                 if($startdate - ($reminderminutes*60) <= $previousitem_startdate)
00420                     return false;
00421             }
00422 
00423             // Get the endtime of the current occurrence and find the next two occurrences (including the current occurrence)
00424             $currentOcc = $this->getItems($this->toGMT($this->tz, $basedate), 0x7ff00000, 2, true);
00425 
00426             // If there are another two occurrences, then the first is the current occurrence, and the one after that
00427             // is the next occurrence.
00428             if(count($currentOcc) > 1) {
00429                 $next = $currentOcc[1];
00430                 // Get reminder time of the next occurrence.
00431                 $nextOccReminderTime = $next[$this->proptags["startdate"]] - ($next[$this->proptags["reminder_minutes"]] * 60);
00432                 // If the reminder time of the next item is before the start of this item, then that's not allowed
00433                 if($nextOccReminderTime <= $startdate)
00434                     return false;
00435             }
00436 
00437             // All was ok
00438             return true;
00439         }
00440 
00441         function setRecurrence($tz, $recur)
00442         {
00443             // only reset timezone if specified
00444             if($tz)
00445                 $this->tz = $tz;
00446 
00447             $this->recur = $recur;
00448 
00449             if(!isset($this->recur["changed_occurences"]))
00450                 $this->recur["changed_occurences"] = Array();
00451 
00452             if(!isset($this->recur["deleted_occurences"]))
00453                 $this->recur["deleted_occurences"] = Array();
00454 
00455             $this->deleteAttachments();
00456             $this->saveRecurrence();
00457 
00458             // if client has not set the recurring_pattern then we should generate it and save it
00459             $messageProps = mapi_getprops($this->message, Array($this->proptags["recurring_pattern"]));
00460             if(empty($messageProps[$this->proptags["recurring_pattern"]])) {
00461                 $this->saveRecurrencePattern();
00462             }
00463         }
00464 
00465         // Returns the start or end time of the occurrence on the given base date.
00466         // This assumes that the basedate you supply is in LOCAL time
00467         function getOccurrenceStart($basedate)  {
00468             $daystart = $this->dayStartOf($basedate);
00469             return $this->toGMT($this->tz, $daystart + $this->recur["startocc"] * 60);
00470         }
00471 
00472         function getOccurrenceEnd($basedate)  {
00473             $daystart = $this->dayStartOf($basedate);
00474             return $this->toGMT($this->tz, $daystart + $this->recur["endocc"] * 60);
00475         }
00476 
00477 
00478         // Backwards compatible code
00479         function getOccurenceStart($basedate)  {
00480             return $this->getOccurrenceStart($basedate);
00481         }
00482         function getOccurenceEnd($basedate)  {
00483             return $this->getOccurrenceEnd($basedate);
00484         }
00485 
00494         function getNextReminderTime($timestamp)
00495         {
00502             $items = $this->getItems($timestamp, 0x7ff00000, 10, true);
00503 
00504             // Initially setting nextreminder to false so when no next reminder exists, false is returned.
00505             $nextreminder = false;
00512             for($i = 0, $len = count($items); $i < $len; $i++)
00513             {
00514                 $item = $items[$i];
00515                 $tempnextreminder = $item[$this->proptags["startdate"]] - ( $item[$this->proptags["reminder_minutes"]] * 60 );
00516 
00517                 // If tempnextreminder is greater than timestamp then save it in nextreminder and break from the loop.
00518                 if($tempnextreminder > $timestamp)
00519                 {
00520                     $nextreminder = $tempnextreminder;
00521                     break;
00522                 }
00523             }
00524             return $nextreminder;
00525         }
00526 
00543         static function getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested)
00544         {
00545             return getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested);
00546         }
00547 
00548 
00549         /*****************************************************************************************************************
00550          * CODE BELOW THIS LINE IS FOR INTERNAL USE ONLY
00551          *****************************************************************************************************************
00552          */
00553 
00557         function saveRecurrencePattern()
00558         {
00559             // Start formatting the properties in such a way we can apply
00560             // them directly into the recurrence pattern.
00561             $type = $this->recur['type'];
00562             $everyn = $this->recur['everyn'];
00563             $start = $this->recur['start'];
00564             $end = $this->recur['end'];
00565             $term = $this->recur['term'];
00566             $numocc = isset($this->recur['numoccur']) ? $this->recur['numoccur'] : false;
00567             $startocc = $this->recur['startocc'];
00568             $endocc = $this->recur['endocc'];
00569             $pattern = '';
00570             $occSingleDayRank = false;
00571             $occTimeRange = ($startocc != 0 && $endocc != 0);
00572 
00573             switch ($type) {
00574                 // Daily
00575                 case 0x0A:
00576                     if ($everyn == 1) {
00577                         $type = _('workday');
00578                         $occSingleDayRank = true;
00579                     } else if ($everyn == (24 * 60)) {
00580                         $type = _('day');
00581                         $occSingleDayRank = true;
00582                     } else {
00583                         $everyn /= (24 * 60);
00584                         $type = _('days');
00585                         $occSingleDayRank = false;
00586                     }
00587                     break;
00588                 // Weekly
00589                 case 0x0B:
00590                     if ($everyn == 1) {
00591                         $type = _('week');
00592                         $occSingleDayRank = true;
00593                     } else {
00594                         $type = _('weeks');
00595                         $occSingleDayRank = false;
00596                     }
00597                     break;
00598                 // Monthly
00599                 case 0x0C:
00600                     if ($everyn == 1) {
00601                         $type = _('month');
00602                         $occSingleDayRank = true;
00603                     } else {
00604                         $type = _('months');
00605                         $occSingleDayRank = false;
00606                     }
00607                     break;
00608                 // Yearly
00609                 case 0x0D:
00610                     if ($everyn <= 12) {
00611                         $everyn = 1;
00612                         $type = _('year');
00613                         $occSingleDayRank = true;
00614                     } else {
00615                         $everyn = $everyn / 12;
00616                         $type = _('years');
00617                         $occSingleDayRank = false;
00618                     }
00619                     break;
00620             }
00621 
00622             // get timings of the first occurence
00623             $firstoccstartdate = isset($startocc) ? $start + (((int) $startocc) * 60) : $start;
00624             $firstoccenddate = isset($endocc) ? $end + (((int) $endocc) * 60) : $end;
00625 
00626             $start = gmdate(_('d-m-Y'), $firstoccstartdate);
00627             $end = gmdate(_('d-m-Y'), $firstoccenddate);
00628             $startocc = gmdate(_('G:i'), $firstoccstartdate);
00629             $endocc = gmdate(_('G:i'), $firstoccenddate);
00630 
00631             // Based on the properties, we need to generate the recurrence pattern string.
00632             // This is obviously very easy since we can simply concatenate a bunch of strings,
00633             // however this messes up translations for languages which order their words
00634             // differently.
00635             // To improve translation quality we create a series of default strings, in which
00636             // we only have to fill in the correct variables. The base string is thus selected
00637             // based on the available properties.
00638             if ($term == 0x23) {
00639                 // Never ends
00640                 if ($occTimeRange) {
00641                     if ($occSingleDayRank) {
00642                         $pattern = sprintf(_('Occurs every %s effective %s from %s to %s.'), $type, $start, $startocc, $endocc);
00643                     } else {
00644                         $pattern = sprintf(_('Occurs every %s %s effective %s from %s to %s.'), $everyn, $type, $start, $startocc, $endocc);
00645                     }
00646                 } else {
00647                     if ($occSingleDayRank) {
00648                         $pattern = sprintf(_('Occurs every %s effective %s.'), $type, $start);
00649                     } else {
00650                         $pattern = sprintf(_('Occurs every %s %s effective %s.'), $everyn, $type, $start);
00651                     }
00652                 }
00653             } else if ($term == 0x22) {
00654                 // After a number of times
00655                 if ($occTimeRange) {
00656                     if ($occSingleDayRank) {
00657                         $pattern = sprintf(ngettext('Occurs every %s effective %s for %s occurence from %s to %s.',
00658                                                 'Occurs every %s effective %s for %s occurences from %s to %s.', $numocc), $type, $start, $numocc, $startocc, $endocc);
00659                     } else {
00660                         $pattern = sprintf(ngettext('Occurs every %s %s effective %s for %s occurence from %s to %s.',
00661                                                 'Occurs every %s %s effective %s for %s occurences %s to %s.', $numocc), $everyn, $type, $start, $numocc, $startocc, $endocc);
00662                     }
00663                 } else {
00664                     if ($occSingleDayRank) {
00665                         $pattern = sprintf(ngettext('Occurs every %s effective %s for %s occurence.',
00666                                                      'Occurs every %s effective %s for %s occurences.', $numocc), $type, $start, $numocc);
00667                     } else {
00668                         $pattern = sprintf(ngettext('Occurs every %s %s effective %s for %s occurence.',
00669                                                          'Occurs every %s %s effective %s for %s occurences.', $numocc), $everyn, $type, $start, $numocc);
00670                     }
00671                 }
00672             } else if ($term == 0x21) {
00673                 // After the given enddate
00674                 if ($occTimeRange) {
00675                     if ($occSingleDayRank) {
00676                         $pattern = sprintf(_('Occurs every %s effective %s until %s from %s to %s.'), $type, $start, $end, $startocc, $endocc);
00677                     } else {
00678                         $pattern = sprintf(_('Occurs every %s %s effective %s until %s from %s to %s.'), $everyn, $type, $start, $end, $startocc, $endocc);
00679                     }
00680                 } else {
00681                     if ($occSingleDayRank) {
00682                         $pattern = sprintf(_('Occurs every %s effective %s until %s.'), $type, $start, $end);
00683                     } else {
00684                         $pattern = sprintf(_('Occurs every %s %s effective %s until %s.'), $everyn, $type, $start, $end);
00685                     }
00686                 }
00687             }
00688 
00689             if(!empty($pattern)) {
00690                 mapi_setprops($this->message, Array($this->proptags["recurring_pattern"] => $pattern ));
00691             }
00692         }
00693 
00694         /*
00695          * Remove an exception by base_date. This is the base date in local daystart time
00696          */
00697         function deleteException($base_date)
00698         {
00699             // Remove all exceptions on $base_date from the deleted and changed occurrences lists
00700 
00701             // Remove all items in $todelete from deleted_occurences
00702             $new = Array();
00703 
00704             foreach($this->recur["deleted_occurences"] as $entry) {
00705                 if($entry != $base_date)
00706                     $new[] = $entry;
00707             }
00708             $this->recur["deleted_occurences"] = $new;
00709 
00710             $new = Array();
00711 
00712             foreach($this->recur["changed_occurences"] as $entry) {
00713                 if(!$this->isSameDay($entry["basedate"], $base_date))
00714                     $new[] = $entry;
00715                 else
00716                     $this->deleteExceptionAttachment($this->toGMT($this->tz, $base_date + $this->recur["startocc"] * 60));
00717             }
00718 
00719             $this->recur["changed_occurences"] = $new;
00720         }
00721 
00729         function createExceptionAttachment($exception_props, $exception_recips = array(), $copy_attach_from = false)
00730         {
00731               // Create new attachment.
00732               $attachment = mapi_message_createattach($this->message);
00733               $props = array();
00734               $props[PR_ATTACHMENT_FLAGS] = 2;
00735               $props[PR_ATTACHMENT_HIDDEN] = true;
00736               $props[PR_ATTACHMENT_LINKID] = 0;
00737               $props[PR_ATTACH_FLAGS] = 0;
00738               $props[PR_ATTACH_METHOD] = 5;
00739               $props[PR_DISPLAY_NAME] = "Exception";
00740               $props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
00741               $props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
00742               mapi_message_setprops($attachment, $props);
00743 
00744             $imessage = mapi_attach_openobj($attachment, MAPI_CREATE | MAPI_MODIFY);
00745 
00746             if ($copy_attach_from) {
00747                 $attachmentTable = mapi_message_getattachmenttable($copy_attach_from);
00748                 if($attachmentTable) {
00749                     $attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD));
00750 
00751                     foreach($attachments as $attach_props){
00752                         $attach_old = mapi_message_openattach($copy_attach_from, (int) $attach_props[PR_ATTACH_NUM]);
00753                         $attach_newResourceMsg = mapi_message_createattach($imessage);
00754                         mapi_copyto($attach_old, array(), array(), $attach_newResourceMsg, 0);
00755                         mapi_savechanges($attach_newResourceMsg);
00756                     }
00757                 }
00758             }
00759 
00760             $props = $props + $exception_props;
00761 
00762             // FIXME: the following piece of code is written to fix the creation
00763             // of an exception. This is only a quickfix as it is not yet possible
00764             // to change an existing exception.
00765             // remove mv properties when needed
00766             foreach($props as $propTag=>$propVal){
00767                 if ((mapi_prop_type($propTag) & MV_FLAG) == MV_FLAG && is_null($propVal)){
00768                     unset($props[$propTag]);
00769                 }
00770             }
00771 
00772             mapi_message_setprops($imessage, $props);
00773 
00774             $this->setExceptionRecipients($imessage, $exception_recips, true);
00775 
00776             mapi_message_savechanges($imessage);
00777             mapi_message_savechanges($attachment);
00778         }
00779 
00786         function deleteExceptionAttachment($base_date)
00787         {
00788             $attachments = mapi_message_getattachmenttable($this->message);
00789             $attachTable = mapi_table_queryallrows($attachments, Array(PR_ATTACH_NUM));
00790 
00791             foreach($attachTable as $attachRow)
00792             {
00793                 $tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]);
00794                 $exception = mapi_attach_openobj($tempattach);
00795 
00796                   $data = mapi_message_getprops($exception, array($this->proptags["basedate"]));
00797 
00798                   if($this->dayStartOf($this->fromGMT($this->tz, $data[$this->proptags["basedate"]])) == $this->dayStartOf($base_date)) {
00799                       mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]);
00800                   }
00801             }
00802         }
00803 
00807         function deleteAttachments()
00808         {
00809             $attachments = mapi_message_getattachmenttable($this->message);
00810             $attachTable = mapi_table_queryallrows($attachments, Array(PR_ATTACH_NUM, PR_ATTACHMENT_HIDDEN));
00811 
00812             foreach($attachTable as $attachRow)
00813             {
00814                 if(isset($attachRow[PR_ATTACHMENT_HIDDEN]) && $attachRow[PR_ATTACHMENT_HIDDEN]) {
00815                     mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]);
00816                 }
00817             }
00818         }
00819 
00823         function getExceptionAttachment($base_date)
00824         {
00825             // Retrieve only embedded messages
00826             $attach_res = Array(RES_AND,
00827                             Array(
00828                                 Array(RES_PROPERTY,
00829                                     Array(RELOP => RELOP_EQ,
00830                                         ULPROPTAG => PR_ATTACH_METHOD,
00831                                         VALUE => array(PR_ATTACH_METHOD => 5)
00832                                     )
00833                                 )
00834                             )
00835             );
00836             $attachments = mapi_message_getattachmenttable($this->message);
00837             $attachRows = mapi_table_queryallrows($attachments, Array(PR_ATTACH_NUM), $attach_res);
00838 
00839             if(is_array($attachRows)) {
00840                 foreach($attachRows as $attachRow)
00841                 {
00842                     $tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]);
00843                     $exception = mapi_attach_openobj($tempattach);
00844 
00845                     $data = mapi_message_getprops($exception, array($this->proptags["basedate"]));
00846 
00847                     if($this->isSameDay($this->fromGMT($this->tz,$data[$this->proptags["basedate"]]), $base_date)) {
00848                         return $tempattach;
00849                     }
00850                 }
00851             }
00852 
00853             return false;
00854         }
00855 
00869         function processOccurrenceItem(&$items, $start, $end, $basedate, $startocc, $endocc, $tz, $reminderonly)
00870         {
00871             $exception = $this->isException($basedate);
00872             if($exception){
00873                 return false;
00874             }else{
00875                 $occstart = $basedate + $startocc * 60;
00876                 $occend = $basedate + $endocc * 60;
00877 
00878                 // Convert to GMT
00879                 $occstart = $this->toGMT($tz, $occstart);
00880                 $occend = $this->toGMT($tz, $occend);
00881 
00890                 if(($occstart  >= $end || $occend <=  $start) && !($occstart == $occend && $occstart == $start))
00891                     return;
00892 
00893                 // Properties for this occurrence are the same as the main object,
00894                 // With these properties overridden
00895                 $newitem = $this->messageprops;
00896                 $newitem[$this->proptags["startdate"]] = $occstart;
00897                 $newitem[$this->proptags["duedate"]] = $occend;
00898                 $newitem[$this->proptags["commonstart"]] = $occstart;
00899                 $newitem[$this->proptags["commonend"]] = $occend;
00900                 $newitem["basedate"] = $basedate;
00901             }
00902 
00903             // If reminderonly is set, only add reminders
00904             if($reminderonly && (!isset($newitem[$this->proptags["reminder"]]) || $newitem[$this->proptags["reminder"]] == false))
00905                 return;
00906 
00907             $items[] = $newitem;
00908         }
00909 
00916         function processExceptionItems(&$items, $start, $end)
00917         {
00918             $limit = 0;
00919             foreach($this->recur["changed_occurences"] as $exception) {
00920 
00921                 // Convert to GMT
00922                 $occstart = $this->toGMT($this->tz, $exception["start"]);
00923                 $occend = $this->toGMT($this->tz, $exception["end"]);
00924 
00925                 // Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot
00926                 // see any part of the appointment. Partial overlaps DO match.
00927                 if($occstart >= $end || $occend <= $start)
00928                     continue;
00929 
00930                 array_push($items, $this->getExceptionProperties($exception));
00931                 if($limit && (count($items) == $limit))
00932                     break;
00933                 }
00934         }
00935 
00941         function isException($basedate)
00942         {
00943             if($this->isDeleteException($basedate))
00944                 return true;
00945 
00946             if($this->getChangeException($basedate) != false)
00947                 return true;
00948 
00949             return false;
00950         }
00951 
00955         function isDeleteException($basedate)
00956         {
00957             // Check if the occurrence is deleted on the specified date
00958             foreach($this->recur["deleted_occurences"] as $deleted)
00959             {
00960                 if($this->isSameDay($deleted, $basedate))
00961                     return true;
00962             }
00963 
00964             return false;
00965         }
00966 
00970         function getChangeException($basedate)
00971         {
00972             // Check if the occurrence is modified on the specified date
00973             foreach($this->recur["changed_occurences"] as $changed)
00974             {
00975                 if($this->isSameDay($changed["basedate"], $basedate))
00976                     return $changed;
00977             }
00978 
00979             return false;
00980         }
00981 
00988         function isSameDay($date1, $date2)
00989         {
00990             $time1 = $this->gmtime($date1);
00991             $time2 = $this->gmtime($date2);
00992 
00993             return $time1["tm_mon"] == $time2["tm_mon"] && $time1["tm_year"] == $time2["tm_year"] && $time1["tm_mday"] == $time2["tm_mday"];
00994         }
00995 
01001         function getExceptionProperties($exception)
01002         {
01003             // Exception has same properties as main object, with some properties overridden:
01004             $item = $this->messageprops;
01005 
01006             // Special properties
01007             $item["exception"] = true;
01008             $item["basedate"] = $exception["basedate"]; // note that the basedate is always in local time !
01009 
01010             // MAPI-compatible properties (you can handle an exception as a normal calendar item like this)
01011             $item[$this->proptags["startdate"]] = $this->toGMT($this->tz, $exception["start"]);
01012             $item[$this->proptags["duedate"]] = $this->toGMT($this->tz, $exception["end"]);
01013             $item[$this->proptags["commonstart"]] = $item[$this->proptags["startdate"]];
01014             $item[$this->proptags["commonend"]] = $item[$this->proptags["duedate"]];
01015 
01016             if(isset($exception["subject"])) {
01017                 $item[$this->proptags["subject"]] = $exception["subject"];
01018             }
01019 
01020             if(isset($exception["label"])) {
01021                 $item[$this->proptags["label"]] = $exception["label"];
01022             }
01023 
01024             if(isset($exception["alldayevent"])) {
01025                 $item[$this->proptags["alldayevent"]] = $exception["alldayevent"];
01026             }
01027 
01028             if(isset($exception["location"])) {
01029                 $item[$this->proptags["location"]] = $exception["location"];
01030             }
01031 
01032             if(isset($exception["remind_before"])) {
01033                 $item[$this->proptags["reminder_minutes"]] = $exception["remind_before"];
01034             }
01035 
01036             if(isset($exception["reminder_set"])) {
01037                 $item[$this->proptags["reminder"]] = $exception["reminder_set"];
01038             }
01039 
01040             if(isset($exception["busystatus"])) {
01041                 $item[$this->proptags["busystatus"]] = $exception["busystatus"];
01042             }
01043 
01044             return $item;
01045         }
01046 
01062         function setExceptionRecipients($message, $exception_recips, $copy_orig_recips = true)
01063         {
01064             if (isset($exception_recips['add']) || isset($exception_recips['remove']) || isset($exception_recips['modify'])) {
01065                 $this->setDeltaExceptionRecipients($message, $exception_recips, $copy_orig_recips);
01066             } else {
01067                 $this->setAllExceptionRecipients($message, $exception_recips);
01068             }
01069         }
01070 
01085         function setDeltaExceptionRecipients($exception, $exception_recips, $copy_orig_recips)
01086         {
01087             // Check if the recipients from the original message should be copied,
01088             // if so, open the recipient table of the parent message and apply all
01089             // rows on the target recipient.
01090             if ($copy_orig_recips === true) {
01091                 $origTable = mapi_message_getrecipienttable($this->message);
01092                 $recipientRows = mapi_table_queryallrows($origTable, $this->recipprops);
01093                 mapi_message_modifyrecipients($exception, MODRECIP_ADD, $recipientRows);
01094             }
01095 
01096             // Add organizer to meeting only if it is not organized.
01097             $msgprops = mapi_getprops($exception, array(PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, $this->proptags['responsestatus']));
01098             if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized){
01099                 $this->addOrganizer($msgprops, $exception_recips['add']);
01100             }
01101 
01102             // Remove all deleted recipients
01103             if (isset($exception_recips['remove'])) {
01104                 foreach ($exception_recips['remove'] as &$recip) {
01105                     if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recip[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
01106                         $recip[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
01107                     } else {
01108                         $recip[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
01109                     }
01110                     $recip[PR_RECIPIENT_TRACKSTATUS] = olResponseNone;        // No Response required
01111                 }
01112                 unset($recip);
01113                 mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['remove']);
01114             }
01115 
01116             // Add all new recipients
01117             if (isset($exception_recips['add'])) {
01118                 mapi_message_modifyrecipients($exception, MODRECIP_ADD, $exception_recips['add']);
01119             }
01120 
01121             // Modify the existing recipients
01122             if (isset($exception_recips['modify'])) {
01123                 mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['modify']);
01124             }
01125         }
01126 
01137         function setAllExceptionRecipients($message, $exception_recips)
01138         {
01139             $deletedRecipients = array();
01140             $useMessageRecipients = false;
01141 
01142             $recipientTable = mapi_message_getrecipienttable($message);
01143             $recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops);
01144 
01145             if (empty($recipientRows)) {
01146                 $useMessageRecipients = true;
01147                 $recipientTable = mapi_message_getrecipienttable($this->message);
01148                 $recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops);
01149             }
01150 
01151             // Add organizer to meeting only if it is not organized.
01152             $msgprops = mapi_getprops($message, array(PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, $this->proptags['responsestatus']));
01153             if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized){
01154                 $this->addOrganizer($msgprops, $exception_recips);
01155             }
01156 
01157             if (!empty($exception_recips)) {
01158                 foreach($recipientRows as $key => $recipient) {
01159                     $found = false;
01160                     foreach($exception_recips as $excep_recip) {
01161                         if ($recipient[PR_SEARCH_KEY] == $excep_recip[PR_SEARCH_KEY])
01162                             $found = true;
01163                     }
01164 
01165                     if (!$found) {
01166                        $foundInDeletedRecipients = false;
01167                        // Look if the $recipient is in the list of deleted recipients
01168                        if (!empty($deletedRecipients)) {
01169                                foreach($deletedRecipients as $recip) {
01170                                    if ($recip[PR_SEARCH_KEY] == $recipient[PR_SEARCH_KEY]){
01171                                        $foundInDeletedRecipients = true;
01172                                        break;
01173                                    }
01174                                }
01175                        }
01176 
01177                        // If recipient is not in list of deleted recipient, add him
01178                        if (!$foundInDeletedRecipients) {
01179                             if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recipient[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
01180                                 $recipient[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
01181                             } else {
01182                                 $recipient[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
01183                             }
01184                             $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;    // No Response required
01185                             $deletedRecipients[] = $recipient;
01186                         }
01187                     }
01188 
01189                     // When $message contains a non-empty recipienttable, we must delete the recipients
01190                     // before re-adding them. However, when $message is doesn't contain any recipients,
01191                     // we are using the recipient table of the original message ($this->message)
01192                     // rather then $message. In that case, we don't need to remove the recipients
01193                     // from the $message, as the recipient table is already empty, and
01194                     // mapi_message_modifyrecipients() will throw an error.
01195                     if ($useMessageRecipients === false) {
01196                         mapi_message_modifyrecipients($message, MODRECIP_REMOVE, array($recipient));
01197                     }
01198                 }
01199                 $exception_recips = array_merge($exception_recips, $deletedRecipients);
01200             } else {
01201                 $exception_recips = $recipientRows;
01202             }
01203 
01204             if (!empty($exception_recips)) {
01205                 // Set the new list of recipients on the exception message, this also removes the existing recipients
01206                 mapi_message_modifyrecipients($message, 0, $exception_recips);
01207             }
01208         }
01209 
01216         function getAllExceptions()
01217         {
01218             $result = false;
01219             if (!empty($this->recur["changed_occurences"])) {
01220                 $result = array();
01221                 foreach($this->recur["changed_occurences"] as $exception) {
01222                     $result[] = $exception["basedate"];
01223                 }
01224                 return $result;
01225             }
01226             return $result;
01227         }
01228 
01237         function addOrganizer($messageProps, &$recipients, $isException = false){
01238 
01239             $hasOrganizer = false;
01240             // Check if meeting already has an organizer.
01241             foreach ($recipients as $key => $recipient){
01242                 if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
01243                     $hasOrganizer = true;
01244                 } else if ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])){
01245                     // Recipients for an occurrence
01246                     $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
01247                 }
01248             }
01249 
01250             if (!$hasOrganizer){
01251                 // Create organizer.
01252                 $organizer = array();
01253                 $organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
01254                 $organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
01255                 $organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
01256                 $organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
01257                 $organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
01258                 $organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE])?'SMTP':$messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
01259                 $organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
01260                 $organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
01261 
01262                 // Add organizer to recipients list.
01263                 array_unshift($recipients, $organizer);
01264             }
01265         }
01266     }
01267 
01268     /*
01269 
01270     From http://www.ohelp-one.com/new-6765483-3268.html:
01271 
01272     Recurrence Data Structure Offset Type Value
01273 
01274     0 ULONG (?) Constant : { 0x04, 0x30, 0x04, 0x30}
01275 
01276     4 UCHAR 0x0A + recurrence type: 0x0A for daily, 0x0B for weekly, 0x0C for
01277     monthly, 0x0D for yearly
01278 
01279     5 UCHAR Constant: { 0x20}
01280 
01281     6 ULONG Seems to be a variant of the recurrence type: 1 for daily every n
01282     days, 2 for daily every weekday and weekly, 3 for monthly or yearly. The
01283     special exception is regenerating tasks that regenerate on a weekly basis: 0
01284     is used in that case (I have no idea why).
01285 
01286     Here's the recurrence-type-specific data. Because the daily every N days
01287     data are 4 bytes shorter than the data for the other types, the offsets for
01288     the rest of the data will be 4 bytes off depending on the recurrence type.
01289 
01290     Daily every N days:
01291 
01292     10 ULONG ( N - 1) * ( 24 * 60). I'm not sure what this is used for, but it's consistent.
01293 
01294     14 ULONG N * 24 * 60: minutes between recurrences
01295 
01296     18 ULONG 0 for all events and non-regenerating recurring tasks. 1 for
01297     regenerating tasks.
01298 
01299     Daily every weekday (this is essentially a subtype of weekly recurrence):
01300 
01301     10 ULONG 6 * 24 * 60: minutes between recurrences ( a week... sort of)
01302 
01303     14 ULONG 1: recur every week (corresponds to the second parameter for weekly
01304     recurrence)
01305 
01306     18 ULONG 0 for all events and non-regenerating recurring tasks. 1 for
01307     regenerating tasks.
01308 
01309     22 ULONG 0x3E: bitmask for recurring every weekday (corresponds to fourth
01310     parameter for weekly recurrence)
01311 
01312     Weekly every N weeks for all events and non-regenerating tasks:
01313 
01314     10 ULONG 6 * 24 * 60: minutes between recurrences (a week... sort of)
01315 
01316     14 ULONG N: recurrence interval
01317 
01318     18 ULONG Constant: 0
01319 
01320     22 ULONG Bitmask for determining which days of the week the event recurs on
01321     ( 1 << dayOfWeek, where Sunday is 0).
01322 
01323     Weekly every N weeks for regenerating tasks: 10 ULONG Constant: 0
01324 
01325     14 ULONG N * 7 * 24 * 60: recurrence interval in minutes between occurrences
01326 
01327     18 ULONG Constant: 1
01328 
01329     Monthly every N months on day D:
01330 
01331     10 ULONG This is the most complicated value
01332     in the entire mess. It's basically a very complicated way of stating the
01333     recurrence interval. I tweaked fbs' basic algorithm. DateTime::MonthInDays
01334     simply returns the number of days in a given month, e.g. 31 for July for 28
01335     for February (the algorithm doesn't take into account leap years, but it
01336     doesn't seem to matter). My DateTime object, like Microsoft's COleDateTime,
01337     uses 1-based months (i.e. January is 1, not 0). With that in mind, this
01338     works:
01339 
01340     long monthIndex = ( ( ( ( 12 % schedule-=GetInterval()) *
01341 
01342     ( ( schedule-=GetStartDate().GetYear() - 1601) %
01343 
01344     schedule-=GetInterval())) % schedule-=GetInterval()) +
01345 
01346     ( schedule-=GetStartDate().GetMonth() - 1)) % schedule-=GetInterval();
01347 
01348     for( int i = 0; i < monthIndex; i++)
01349 
01350     {
01351 
01352     value += DateTime::GetDaysInMonth( ( i % 12) + 1) * 24 * 60;
01353 
01354     }
01355 
01356     This should work for any recurrence interval, including those greater than
01357     12.
01358 
01359     14 ULONG N: recurrence interval
01360 
01361     18 ULONG 0 for all events and non-regenerating recurring tasks. 1 for
01362     regenerating tasks.
01363 
01364     22 ULONG D: day of month the event recurs on (if this value is greater than
01365     the number of days in a given month [e.g. 31 for and recurs in June], then
01366     the event will recur on the last day of the month)
01367 
01368     Monthly every N months on the Xth Y (e.g. "2nd Tuesday"):
01369 
01370     10 ULONG See above: same as for monthly every N months on day D
01371 
01372     14 ULONG N: recurrence interval
01373 
01374     18 ULONG 0 for all events and non-regenerating recurring tasks. 1 for
01375     regenerating tasks.
01376 
01377     22 ULONG Y: bitmask for determining which day of the week the event recurs
01378     on (see weekly every N weeks). Some useful values are 0x7F for any day, 0x3E
01379     for a weekday, or 0x41 for a weekend day.
01380 
01381     26 ULONG X: 1 for first occurrence, 2 for second, etc. 5 for last
01382     occurrence. E.g. for "2nd Tuesday", you should have values of 0x04 for the
01383     prior value and 2 for this one.
01384 
01385     Yearly on day D of month M:
01386 
01387     10 ULONG M (sort of): This is another messy
01388     value. It's the number of minute since the startning of the year to the
01389     given month. For an explanation of GetDaysInMonth, see monthly every N
01390     months. This will work:
01391 
01392     ULONG monthOfYearInMinutes = 0;
01393 
01394     for( int i = DateTime::cJanuary; i < schedule-=GetMonth(); i++)
01395 
01396     {
01397 
01398     monthOfYearInMinutes += DateTime::GetDaysInMonth( i) * 24 * 60;
01399 
01400     }
01401 
01402 
01403 
01404     14 ULONG 12: recurrence interval in months. Naturally, 12.
01405 
01406     18 ULONG 0 for all events and non-regenerating recurring tasks. 1 for
01407     regenerating tasks.
01408 
01409     22 ULONG D: day of month the event recurs on. See monthly every N months on
01410     day D.
01411 
01412     Yearly on the Xth Y of month M: 10 ULONG M (sort of): See yearly on day D of
01413     month M.
01414 
01415     14 ULONG 12: recurrence interval in months. Naturally, 12.
01416 
01417     18 ULONG Constant: 0
01418 
01419     22 ULONG Y: see monthly every N months on the Xth Y.
01420 
01421     26 ULONG X: see monthly every N months on the Xth Y.
01422 
01423     After these recurrence-type-specific values, the offsets will change
01424     depending on the type. For every type except daily every N days, the offsets
01425     will grow by at least 4. For those types using the Xth Y, the offsets will
01426     grow by an additional 4, for a total of 8. The offsets for the rest of these
01427     values will be given for the most basic case, daily every N days, i.e.
01428     without any growth. Adjust as necessary. Also, the presence of exceptions
01429     will change the offsets following the exception data by a variable number of
01430     bytes, so the offsets given in the table are accurate only for those
01431     recurrence patterns without any exceptions.
01432 
01433 
01434     22 UCHAR Type of pattern termination: 0x21 for terminating on a given date, 0x22 for terminating
01435     after a given number of recurrences, or 0x23 for never terminating
01436     (recurring infinitely)
01437 
01438     23 UCHARx3 Constant: { 0x20, 0x00, 0x00}
01439 
01440     26 ULONG Number of occurrences in pattern: 0 for infinite recurrence,
01441     otherwise supply the value, even if it terminates on a given date, not after
01442     a given number
01443 
01444     30 ULONG Constant: 0
01445 
01446     34 ULONG Number of exceptions to pattern (i.e. deleted or changed
01447     occurrences)
01448 
01449     .... ULONGxN Base date of each exception, given in hundreds of nanoseconds
01450     since 1601, so see below to turn them into a comprehensible format. The base
01451     date of an exception is the date (and only the date-- not the time) the
01452     exception would have occurred on in the pattern. They must occur in
01453     ascending order.
01454 
01455     38 ULONG Number of changed exceptions (i.e. total number of exceptions -
01456     number of deleted exceptions): if there are changed exceptions, again, more
01457     data will be needed, but that will wait
01458 
01459     .... ULONGxN Start date (and only the date-- not the time) of each changed
01460     exception, i.e. the exceptions which aren't deleted. These must also occur
01461     in ascending order. If all of the exceptions are deleted, this data will be
01462     absent. If present, they will be in the format above. Any dates that are in
01463     the first list but not in the second are exceptions that have been deleted
01464     (i.e. the difference between the two sets). Note that this is the start date
01465     (including time), not the base date. Given that the values are unordered and
01466     that they can't be matched up against the previous list in this iteration of
01467     the recurrence data (they could in previous ones), it is very difficult to
01468     tell which exceptions are deleted and which are changed. Fortunately, for
01469     this new format, the base dates are given on the attachment representing the
01470     changed exception (described below), so you can simply ignore this list of
01471     changed exceptions. Just create a list of exceptions from the previous list
01472     and assume they're all deleted unless you encounter an attachment with a
01473     matching base date later on.
01474 
01475     42 ULONG Start date of pattern given in hundreds of nanoseconds since 1601;
01476     see below for an explanation.
01477 
01478     46 ULONG End date of pattern: see start date of pattern
01479 
01480     50 ULONG Constant: { 0x06, 0x30, 0x00, 0x00}
01481 
01482     NOTE: I find the following 8-byte sequence of bytes to be very useful for
01483     orienting myself when looking at the raw data. If you can find { 0x06, 0x30,
01484     0x00, 0x00, 0x08, 0x30, 0x00, 0x00}, you can use these tables to work either
01485     forwards or backwards to find the data you need. The sequence sort of
01486     delineates certain critical exception-related data and delineates the
01487     exceptions themselves from the rest of the data and is relatively easy to
01488     find. If you're going to be meddling in here a lot, I suggest making a
01489     friend of ol' 0x00003006.
01490 
01491     54 UCHAR This number is some kind of version indicator. Use 0x08 for Outlook
01492     2003. I believe 0x06 is Outlook 2000 and possibly 98, while 0x07 is Outlook
01493     XP. This number must be consistent with the features of the data structure
01494     generated by the version of Outlook indicated thereby-- there are subtle
01495     differences between the structures, and, if the version doesn't match the
01496     data, Outlook will sometimes failto read the structure.
01497 
01498     55 UCHARx3 Constant: { 0x30, 0x00, 0x00}
01499 
01500     58 ULONG Start time of occurrence in minutes: e.g. 0 for midnight or 720 for
01501     12 PM
01502 
01503     62 ULONG End time of occurrence in minutes: i.e. start time + duration, e.g.
01504     900 for an event that starts at 12 PM and ends at 3PM
01505 
01506     Exception Data 66 USHORT Number of changed exceptions: essentially a check
01507     on the prior occurrence of this value; should be equivalent.
01508 
01509     NOTE: The following structure will occur N many times (where N = number of
01510     changed exceptions), and each structure can be of variable length.
01511 
01512     .... ULONG Start date of changed exception given in hundreds of nanoseconds
01513     since 1601
01514 
01515     .... ULONG End date of changed exception given in hundreds of nanoseconds
01516     since 1601
01517 
01518     .... ULONG This is a value I don't clearly understand. It seems to be some
01519     kind of archival value that matches the start time most of the time, but
01520     will lag behind when the start time is changed and then match up again under
01521     certain conditions later. In any case, setting to the same value as the
01522     start time seems to work just fine (more information on this value would be
01523     appreciated).
01524 
01525     .... USHORT Bitmask of changes to the exception (see below). This will be 0
01526     if the only changes to the exception were to its start or end time.
01527 
01528     .... ULONGxN Numeric values (e.g. label or minutes to remind before the
01529     event) changed in the exception. These will occur in the order of their
01530     corresponding bits (see below). If no numeric values were changed, then
01531     these values will be absent.
01532 
01533     NOTE: The following three values constitute a single sub-structure that will
01534     occur N many times, where N is the number of strings that are changed in the
01535     exception. Since there are at most 2 string values that can be excepted
01536     (i.e. subject [or description], and location), there can at most be two of
01537     these, but there may be none.
01538 
01539     .... USHORT Length of changed string value with NULL character
01540 
01541     .... USHORT Length of changed string value without NULL character (i.e.
01542     previous value - 1)
01543 
01544     .... CHARxN Changed string value (without NULL terminator)
01545 
01546     Unicode Data NOTE: If a string value was changed on an exception, those
01547     changed string values will reappear here in Unicode format after 8 bytes of
01548     NULL padding (possibly a Unicode terminator?). For each exception with a
01549     changed string value, there will be an identifier, followed by the changed
01550     strings in Unicode. The strings will occur in the order of their
01551     corresponding bits (see below). E.g., if both subject and location were
01552     changed in the exception, there would be the 3-ULONG identifier, then the
01553     length of the subject, then the subject, then the length of the location,
01554     then the location.
01555 
01556     70 ULONGx2 Constant: { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}. This
01557     padding serves as a barrier between the older data structure and the
01558     appended Unicode data. This is the same sequence as the Unicode terminator,
01559     but I'm not sure whether that's its identity or not.
01560 
01561     .... ULONGx3 These are the three times used to identify the exception above:
01562     start date, end date, and repeated start date. These should be the same as
01563     they were above.
01564 
01565     .... USHORT Length of changed string value without NULL character. This is
01566     given as count of WCHARs, so it should be identical to the value above.
01567 
01568     .... WCHARxN Changed string value in Unicode (without NULL terminator)
01569 
01570     Terminator ... ULONGxN Constant: { 0x00, 0x00, 0x00, 0x00}. 4 bytes of NULL
01571     padding per changed exception. If there were no changed exceptions, all
01572     you'll need is the final terminator below.
01573 
01574     .... ULONGx2 Constant: { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}.
01575 
01576     */
01577 ?>