Back to index

d-push  2.0
class.baserecurrence.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 
00056     class BaseRecurrence
00057     {
00061         var $store;
00062 
00066         var $message;
00067 
00071         var $messageprops;
00072 
00076         var $proptags;
00077 
00081         var $recur;
00082 
00086         var $tz;
00087 
00094         function BaseRecurrence($store, $message)
00095         {
00096             $this->store = $store;
00097 
00098             if(is_array($message)) {
00099                 $this->messageprops = $message;
00100             } else {
00101                 $this->message = $message;
00102                 $this->messageprops = mapi_getprops($this->message, $this->proptags);
00103             }
00104 
00105             if(isset($this->messageprops[$this->proptags["recurring_data"]])) {
00106                 // There is a possibility that recurr blob can be more than 255 bytes so get full blob through stream interface
00107                 if (strlen($this->messageprops[$this->proptags["recurring_data"]]) >= 255) {
00108                     $this->getFullRecurrenceBlob();
00109                 }
00110 
00111                 $this->recur = $this->parseRecurrence($this->messageprops[$this->proptags["recurring_data"]]);
00112             }
00113             if(isset($this->proptags["timezone_data"]) && isset($this->messageprops[$this->proptags["timezone_data"]])) {
00114                 $this->tz = $this->parseTimezone($this->messageprops[$this->proptags["timezone_data"]]);
00115             }
00116         }
00117 
00118         function getRecurrence()
00119         {
00120             return $this->recur;
00121         }
00122 
00123         function getFullRecurrenceBlob()
00124         {
00125             $message = mapi_msgstore_openentry($this->store, $this->messageprops[PR_ENTRYID]);
00126 
00127             $recurrBlob = '';
00128             $stream = mapi_openproperty($message, $this->proptags["recurring_data"], IID_IStream, 0, 0);
00129             $stat = mapi_stream_stat($stream);
00130 
00131             for ($i = 0; $i < $stat['cb']; $i += 1024) {
00132                 $recurrBlob .= mapi_stream_read($stream, 1024);
00133             }
00134 
00135             if (!empty($recurrBlob)) {
00136                 $this->messageprops[$this->proptags["recurring_data"]] = $recurrBlob;
00137             }
00138         }
00139 
00190         function parseRecurrence($rdata)
00191         {
00192             if (strlen($rdata) < 10) {
00193                 return;
00194             }
00195 
00196             $ret["changed_occurences"] = array();
00197             $ret["deleted_occurences"] = array();
00198 
00199             $data = unpack("Vconst1/Crtype/Cconst2/Vrtype2", $rdata);
00200 
00201             $ret["type"] = $data["rtype"];
00202             $ret["subtype"] = $data["rtype2"];
00203             $rdata = substr($rdata, 10);
00204 
00205             switch ($data["rtype"])
00206             {
00207                 case 0x0a:
00208                     // Daily
00209                     if (strlen($rdata) < 12) {
00210                         return $ret;
00211                     }
00212 
00213                     $data = unpack("Vunknown/Veveryn/Vregen", $rdata);
00214                     $ret["everyn"] = $data["everyn"];
00215                     $ret["regen"] = $data["regen"];
00216 
00217                     switch($ret["subtype"])
00218                     {
00219                         case 0:
00220                             $rdata = substr($rdata, 12);
00221                             break;
00222                         case 1:
00223                             $rdata = substr($rdata, 16);
00224                             break;
00225                     }
00226 
00227                     break;
00228 
00229                 case 0x0b:
00230                     // Weekly
00231                     if (strlen($rdata) < 16) {
00232                         return $ret;
00233                     }
00234 
00235                     $data = unpack("Vconst1/Veveryn/Vregen", $rdata);
00236                     $rdata = substr($rdata, 12);
00237 
00238                     $ret["everyn"] = $data["everyn"];
00239                     $ret["regen"] = $data["regen"];
00240                     $ret["weekdays"] = 0;
00241 
00242                     if ($data["regen"] == 0) {
00243                         $data = unpack("Vweekdays", $rdata);
00244                         $rdata = substr($rdata, 4);
00245 
00246                         $ret["weekdays"] = $data["weekdays"];
00247                     }
00248                     break;
00249 
00250                 case 0x0c:
00251                     // Monthly
00252                     if (strlen($rdata) < 16) {
00253                         return $ret;
00254                     }
00255 
00256                     $data = unpack("Vconst1/Veveryn/Vregen/Vmonthday", $rdata);
00257 
00258                     $ret["everyn"] = $data["everyn"];
00259                     $ret["regen"] = $data["regen"];
00260 
00261                     if ($ret["subtype"] == 3) {
00262                         $ret["weekdays"] = $data["monthday"];
00263                     } else {
00264                         $ret["monthday"] = $data["monthday"];
00265                     }
00266 
00267                     $rdata = substr($rdata, 16);
00268 
00269                     if ($ret["subtype"] == 3) {
00270                         $data = unpack("Vnday", $rdata);
00271                         $ret["nday"] = $data["nday"];
00272                         $rdata = substr($rdata, 4);
00273                     }
00274                     break;
00275 
00276                 case 0x0d:
00277                     // Yearly
00278                     if (strlen($rdata) < 16)
00279                         return $ret;
00280 
00281                     $data = unpack("Vmonth/Veveryn/Vregen/Vmonthday", $rdata);
00282 
00283                     $ret["month"] = $data["month"];
00284                     $ret["everyn"] = $data["everyn"];
00285                     $ret["regen"] = $data["regen"];
00286 
00287                     if ($ret["subtype"] == 3) {
00288                         $ret["weekdays"] = $data["monthday"];
00289                     } else {
00290                         $ret["monthday"] = $data["monthday"];
00291                     }
00292 
00293                     $rdata = substr($rdata, 16);
00294 
00295                     if ($ret["subtype"] == 3) {
00296                         $data = unpack("Vnday", $rdata);
00297                         $ret["nday"] = $data["nday"];
00298                         $rdata = substr($rdata, 4);
00299                     }
00300                     break;
00301             }
00302 
00303             if (strlen($rdata) < 16) {
00304                 return $ret;
00305             }
00306 
00307             $data = unpack("Cterm/C3const1/Vnumoccur/Vconst2/Vnumexcept", $rdata);
00308 
00309             $rdata = substr($rdata, 16);
00310 
00311             $ret["term"] = $data["term"];
00312             $ret["numoccur"] = $data["numoccur"];
00313             $ret["numexcept"] = $data["numexcept"];
00314 
00315             // exc_base_dates are *all* the base dates that have been either deleted or modified
00316             $exc_base_dates = array();
00317             for($i = 0; $i < $ret["numexcept"]; $i++)
00318             {
00319                 if (strlen($rdata) < 4) {
00320                     // We shouldn't arrive here, because that implies
00321                     // numexcept does not match the amount of data
00322                     // which is available for the exceptions.
00323                     return $ret;
00324                 }
00325                 $data = unpack("Vbasedate", $rdata);
00326                 $rdata = substr($rdata, 4);
00327                 $exc_base_dates[] = $this->recurDataToUnixData($data["basedate"]);
00328             }
00329 
00330             if (strlen($rdata) < 4) {
00331                 return $ret;
00332             }
00333 
00334             $data = unpack("Vnumexceptmod", $rdata);
00335             $rdata = substr($rdata, 4);
00336 
00337             $ret["numexceptmod"] = $data["numexceptmod"];
00338 
00339             // exc_changed are the base dates of *modified* occurrences. exactly what is modified
00340             // is in the attachments *and* in the data further down this function.
00341             $exc_changed = array();
00342             for($i = 0; $i < $ret["numexceptmod"]; $i++)
00343             {
00344                 if (strlen($rdata) < 4) {
00345                     // We shouldn't arrive here, because that implies
00346                     // numexceptmod does not match the amount of data
00347                     // which is available for the exceptions.
00348                     return $ret;
00349                 }
00350                 $data = unpack("Vstartdate", $rdata);
00351                 $rdata = substr($rdata, 4);
00352                 $exc_changed[] = $this->recurDataToUnixData($data["startdate"]);
00353             }
00354 
00355             if (strlen($rdata) < 8) {
00356                 return $ret;
00357             }
00358 
00359             $data = unpack("Vstart/Vend", $rdata);
00360             $rdata = substr($rdata, 8);
00361 
00362             $ret["start"] = $this->recurDataToUnixData($data["start"]);
00363             $ret["end"] = $this->recurDataToUnixData($data["end"]);
00364 
00365             // this is where task recurrence stop
00366             if (strlen($rdata) < 16) {
00367                 return $ret;
00368             }
00369 
00370             $data = unpack("Vreaderversion/Vwriterversion/Vstartmin/Vendmin", $rdata);
00371             $rdata = substr($rdata, 16);
00372 
00373             $ret["startocc"] = $data["startmin"];
00374             $ret["endocc"] = $data["endmin"];
00375             $readerversion = $data["readerversion"];
00376             $writerversion = $data["writerversion"];
00377 
00378             $data = unpack("vnumber", $rdata);
00379             $rdata = substr($rdata, 2);
00380 
00381             $nexceptions = $data["number"];
00382             $exc_changed_details = array();
00383 
00384             // Parse n modified exceptions
00385             for($i=0;$i<$nexceptions;$i++)
00386             {
00387                 $item = array();
00388 
00389                 // Get exception startdate, enddate and basedate (the date at which the occurrence would have started)
00390                 $data = unpack("Vstartdate/Venddate/Vbasedate", $rdata);
00391                 $rdata = substr($rdata, 12);
00392 
00393                 // Convert recurtimestamp to unix timestamp
00394                 $startdate = $this->recurDataToUnixData($data["startdate"]);
00395                 $enddate = $this->recurDataToUnixData($data["enddate"]);
00396                 $basedate = $this->recurDataToUnixData($data["basedate"]);
00397 
00398                 // Set the right properties
00399                 $item["basedate"] = $this->dayStartOf($basedate);
00400                 $item["start"] = $startdate;
00401                 $item["end"] = $enddate;
00402 
00403                 $data = unpack("vbitmask", $rdata);
00404                 $rdata = substr($rdata, 2);
00405                 $item["bitmask"] = $data["bitmask"]; // save bitmask for extended exceptions
00406 
00407                 // Bitmask to verify what properties are changed
00408                 $bitmask = $data["bitmask"];
00409 
00410                 // ARO_SUBJECT: 0x0001
00411                 // Look for field: SubjectLength (2b), SubjectLength2 (2b) and Subject
00412                 if(($bitmask &(1 << 0))) {
00413                     $data = unpack("vnull_length/vlength", $rdata);
00414                     $rdata = substr($rdata, 4);
00415 
00416                     $length = $data["length"];
00417                     $item["subject"] = ""; // Normalized subject
00418                     for($j = 0; $j < $length && strlen($rdata); $j++)
00419                     {
00420                         $data = unpack("Cchar", $rdata);
00421                         $rdata = substr($rdata, 1);
00422 
00423                         $item["subject"] .= chr($data["char"]);
00424                     }
00425                 }
00426 
00427                 // ARO_MEETINGTYPE: 0x0002
00428                 if(($bitmask &(1 << 1))) {
00429                     $rdata = substr($rdata, 4);
00430                     // Attendees modified: no data here (only in attachment)
00431                 }
00432 
00433                 // ARO_REMINDERDELTA: 0x0004
00434                 // Look for field: ReminderDelta (4b)
00435                 if(($bitmask &(1 << 2))) {
00436                     $data = unpack("Vremind_before", $rdata);
00437                     $rdata = substr($rdata, 4);
00438 
00439                     $item["remind_before"] = $data["remind_before"];
00440                 }
00441 
00442                 // ARO_REMINDER: 0x0008
00443                 // Look field: ReminderSet (4b)
00444                 if(($bitmask &(1 << 3))) {
00445                     $data = unpack("Vreminder_set", $rdata);
00446                     $rdata = substr($rdata, 4);
00447 
00448                     $item["reminder_set"] = $data["reminder_set"];
00449                 }
00450 
00451                 // ARO_LOCATION: 0x0010
00452                 // Look for fields: LocationLength (2b), LocationLength2 (2b) and Location
00453                 // Similar to ARO_SUBJECT above.
00454                 if(($bitmask &(1 << 4))) {
00455                     $data = unpack("vnull_length/vlength", $rdata);
00456                     $rdata = substr($rdata, 4);
00457 
00458                     $item["location"] = "";
00459 
00460                     $length = $data["length"];
00461                     $data = substr($rdata, 0, $length);
00462                     $rdata = substr($rdata, $length);
00463 
00464                     $item["location"] .= $data;
00465                 }
00466 
00467                 // ARO_BUSYSTATUS: 0x0020
00468                 // Look for field: BusyStatus (4b)
00469                 if(($bitmask &(1 << 5))) {
00470                     $data = unpack("Vbusystatus", $rdata);
00471                     $rdata = substr($rdata, 4);
00472 
00473                     $item["busystatus"] = $data["busystatus"];
00474                 }
00475 
00476                 // ARO_ATTACHMENT: 0x0040
00477                 if(($bitmask &(1 << 6))) {
00478                     // no data: RESERVED
00479                     $rdata = substr($rdata, 4);
00480                 }
00481 
00482                 // ARO_SUBTYPE: 0x0080
00483                 // Look for field: SubType (4b). Determines whether it is an allday event.
00484                 if(($bitmask &(1 << 7))) {
00485                     $data = unpack("Vallday", $rdata);
00486                     $rdata = substr($rdata, 4);
00487 
00488                     $item["alldayevent"] = $data["allday"];
00489                 }
00490 
00491                 // ARO_APPTCOLOR: 0x0100
00492                 // Look for field: AppointmentColor (4b)
00493                 if(($bitmask &(1 << 8))) {
00494                     $data = unpack("Vlabel", $rdata);
00495                     $rdata = substr($rdata, 4);
00496 
00497                     $item["label"] = $data["label"];
00498                 }
00499 
00500                 // ARO_EXCEPTIONAL_BODY: 0x0200
00501                 if(($bitmask &(1 << 9))) {
00502                     // Notes or Attachments modified: no data here (only in attachment)
00503                 }
00504 
00505                 array_push($exc_changed_details, $item);
00506             }
00507 
00515             // Find deleted occurrences
00516             $deleted_occurences = array();
00517 
00518             foreach($exc_base_dates as $base_date) {
00519                 $found = false;
00520 
00521                 foreach($exc_changed_details as $details) {
00522                     if($details["basedate"] == $base_date) {
00523                         $found = true;
00524                         break;
00525                     }
00526                 }
00527                 if(! $found) {
00528                     // item was not in exc_changed_details, so it must be deleted
00529                     $deleted_occurences[] = $base_date;
00530                 }
00531             }
00532 
00533             $ret["deleted_occurences"] = $deleted_occurences;
00534             $ret["changed_occurences"] = $exc_changed_details;
00535 
00536             // enough data for normal exception (no extended data)
00537             if (strlen($rdata) < 16) {
00538                 return $ret;
00539             }
00540 
00541             $data = unpack("Vreservedsize", $rdata);
00542             $rdata = substr($rdata, 4 + $data["reservedsize"]);
00543 
00544             for($i=0;$i<$nexceptions;$i++)
00545             {
00546                 // subject and location in ucs-2 to utf-8
00547                 if ($writerversion >= 0x3009) {
00548                     $data = unpack("Vsize/Vvalue", $rdata); // size includes sizeof(value)==4
00549                     $rdata = substr($rdata, 4 + $data["size"]);
00550                 }
00551 
00552                 $data = unpack("Vreservedsize", $rdata);
00553                 $rdata = substr($rdata, 4 + $data["reservedsize"]);
00554 
00555                 // ARO_SUBJECT(0x01) | ARO_LOCATION(0x10)
00556                 if ($exc_changed_details[$i]["bitmask"] & 0x11) {
00557                     $data = unpack("Vstart/Vend/Vorig", $rdata);
00558                     $rdata = substr($rdata, 4 * 3);
00559 
00560                     $exc_changed_details[$i]["ex_start_datetime"] = $data["start"];
00561                     $exc_changed_details[$i]["ex_end_datetime"] = $data["end"];
00562                     $exc_changed_details[$i]["ex_orig_date"] = $data["orig"];
00563                 }
00564 
00565                 // ARO_SUBJECT
00566                 if ($exc_changed_details[$i]["bitmask"] & 0x01) {
00567                     // decode ucs2 string to utf-8
00568                     $data = unpack("vlength", $rdata);
00569                     $rdata = substr($rdata, 2);
00570                     $length = $data["length"];
00571                     $data = substr($rdata, 0, $length * 2);
00572                     $rdata = substr($rdata, $length * 2);
00573                     $subject = iconv("UCS-2LE", "UTF-8", $data);
00574                     // replace subject with unicode subject
00575                     $exc_changed_details[$i]["subject"] = $subject;
00576                 }
00577 
00578                 // ARO_LOCATION
00579                 if ($exc_changed_details[$i]["bitmask"] & 0x10) {
00580                     // decode ucs2 string to utf-8
00581                     $data = unpack("vlength", $rdata);
00582                     $rdata = substr($rdata, 2);
00583                     $length = $data["length"];
00584                     $data = substr($rdata, 0, $length * 2);
00585                     $rdata = substr($rdata, $length * 2);
00586                     $location = iconv("UCS-2LE", "UTF-8", $data);
00587                     // replace subject with unicode subject
00588                     $exc_changed_details[$i]["location"] = $location;
00589                 }
00590 
00591                 // ARO_SUBJECT(0x01) | ARO_LOCATION(0x10)
00592                 if ($exc_changed_details[$i]["bitmask"] & 0x11) {
00593                     $data = unpack("Vreservedsize", $rdata);
00594                     $rdata = substr($rdata, 4 + $data["reservedsize"]);
00595                 }
00596             }
00597 
00598             // update with extended data
00599             $ret["changed_occurences"] = $exc_changed_details;
00600 
00601             return $ret;
00602         }
00603 
00609         function saveRecurrence()
00610         {
00611             // Only save if a message was passed
00612             if(!isset($this->message))
00613                 return;
00614 
00615             // Abort if no recurrence was set
00616             if(!isset($this->recur["type"]) && !isset($this->recur["subtype"])) {
00617                 return;
00618             }
00619 
00620             if(!isset($this->recur["start"]) && !isset($this->recur["end"])) {
00621                 return;
00622             }
00623 
00624             if(!isset($this->recur["startocc"]) && !isset($this->recur["endocc"])) {
00625                 return;
00626             }
00627 
00628             $rdata = pack("CCCCCCV", 0x04, 0x30, 0x04, 0x30, (int) $this->recur["type"], 0x20, (int) $this->recur["subtype"]);
00629 
00630             $weekstart = 1; //monday
00631             $forwardcount = 0;
00632             $restocc = 0;
00633             $dayofweek = (int) gmdate("w", (int) $this->recur["start"]); //0 (for Sunday) through 6 (for Saturday)
00634 
00635             $term = (int) $this->recur["type"];
00636             switch($term)
00637             {
00638                 case 0x0A:
00639                     // Daily
00640                     if(!isset($this->recur["everyn"])) {
00641                         return;
00642                     }
00643 
00644                     if($this->recur["subtype"] == 1) {
00645 
00646                         // Daily every workday
00647                         $rdata .= pack("VVVV", (6 * 24 * 60), 1, 0, 0x3E);
00648                     } else {
00649                         // Daily every N days (everyN in minutes)
00650 
00651                         $everyn =  ((int) $this->recur["everyn"]) / 1440;
00652 
00653                         // Calc first occ
00654                         $firstocc = $this->unixDataToRecurData($this->recur["start"]) % ((int) $this->recur["everyn"]);
00655 
00656                         $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], $this->recur["regen"] ? 1 : 0);
00657                     }
00658                     break;
00659                 case 0x0B:
00660                     // Weekly
00661                     if(!isset($this->recur["everyn"])) {
00662                         return;
00663                     }
00664 
00665                     if (!$this->recur["regen"] && !isset($this->recur["weekdays"])) {
00666                         return;
00667                     }
00668 
00669                     // No need to calculate startdate if sliding flag was set.
00670                     if (!$this->recur['regen']) {
00671                         // Calculate start date of recurrence
00672 
00673                         // Find the first day that matches one of the weekdays selected
00674                         $daycount = 0;
00675                         $dayskip = -1;
00676                         for($j = 0; $j < 7; $j++) {
00677                             if(((int) $this->recur["weekdays"]) & (1<<( ($dayofweek+$j)%7)) ) {
00678                                 if($dayskip == -1)
00679                                     $dayskip = $j;
00680 
00681                                 $daycount++;
00682                             }
00683                         }
00684 
00685                         // $dayskip is the number of days to skip from the startdate until the first occurrence
00686                         // $daycount is the number of days per week that an occurrence occurs
00687 
00688                         $weekskip = 0;
00689                         if(($dayofweek < $weekstart && $dayskip > 0) || ($dayofweek+$dayskip) > 6)
00690                             $weekskip = 1;
00691 
00692                         // Check if the recurrence ends after a number of occurences, in that case we must calculate the
00693                         // remaining occurences based on the start of the recurrence.
00694                         if (((int) $this->recur["term"]) == 0x22) {
00695                             // $weekskip is the amount of weeks to skip from the startdate before the first occurence
00696                             // $forwardcount is the maximum number of week occurrences we can go ahead after the first occurrence that
00697                             // is still inside the recurrence. We subtract one to make sure that the last week is never forwarded over
00698                             // (eg when numoccur = 2, and daycount = 1)
00699                             $forwardcount = floor( (int) ($this->recur["numoccur"] -1 ) / $daycount);
00700 
00701                             // $restocc is the number of occurrences left after $forwardcount whole weeks of occurrences, minus one
00702                             // for the occurrence on the first day
00703                             $restocc = ((int) $this->recur["numoccur"]) - ($forwardcount*$daycount) - 1;
00704 
00705                             // $forwardcount is now the number of weeks we can go forward and still be inside the recurrence
00706                             $forwardcount *= (int) $this->recur["everyn"];
00707                         }
00708 
00709                         // The real start is start + dayskip + weekskip-1 (since dayskip will already bring us into the next week)
00710                         $this->recur["start"] = ((int) $this->recur["start"]) + ($dayskip * 24*60*60)+ ($weekskip *(((int) $this->recur["everyn"]) - 1) * 7 * 24*60*60);
00711                     }
00712 
00713                     // Calc first occ
00714                     $firstocc = ($this->unixDataToRecurData($this->recur["start"]) ) % ( ((int) $this->recur["everyn"]) * 7 * 24 * 60);
00715 
00716                     $firstocc -= (((int) gmdate("w", (int) $this->recur["start"])) - 1) * 24 * 60;
00717 
00718                     if ($this->recur["regen"])
00719                         $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], 1);
00720                     else
00721                         $rdata .= pack("VVVV", $firstocc, (int) $this->recur["everyn"], 0, (int) $this->recur["weekdays"]);
00722                     break;
00723                 case 0x0C:
00724                     // Monthly
00725                 case 0x0D:
00726                     // Yearly
00727                     if(!isset($this->recur["everyn"])) {
00728                         return;
00729                     }
00730                     if($term == 0x0D /*yearly*/ && !isset($this->recur["month"])) {
00731                         return;
00732                     }
00733 
00734                     if($term == 0x0C /*monthly*/) {
00735                         $everyn = (int) $this->recur["everyn"];
00736                     }else {
00737                         $everyn = $this->recur["regen"] ? ((int) $this->recur["everyn"]) * 12 : 12;
00738                     }
00739 
00740                     // Get montday/month/year of original start
00741                     $curmonthday = gmdate("j", (int) $this->recur["start"] );
00742                     $curyear = gmdate("Y", (int) $this->recur["start"] );
00743                     $curmonth = gmdate("n", (int) $this->recur["start"] );
00744 
00745                     // Check if the recurrence ends after a number of occurences, in that case we must calculate the
00746                     // remaining occurences based on the start of the recurrence.
00747                     if (((int) $this->recur["term"]) == 0x22) {
00748                         // $forwardcount is the number of occurrences we can skip and still be inside the recurrence range (minus
00749                         // one to make sure there are always at least one occurrence left)
00750                         $forwardcount = ((((int) $this->recur["numoccur"])-1) * $everyn );
00751                     }
00752 
00753                     // Get month for yearly on D'th day of month M
00754                     if($term == 0x0D /*yearly*/) {
00755                         $selmonth = floor(((int) $this->recur["month"]) / (24 * 60 *29)) + 1; // 1=jan, 2=feb, eg
00756                     }
00757 
00758                     switch((int) $this->recur["subtype"])
00759                     {
00760                         // on D day of every M month
00761                         case 2:
00762                             if(!isset($this->recur["monthday"])) {
00763                                 return;
00764                             }
00765                             // Recalc startdate
00766 
00767                             // Set on the right begin day
00768 
00769                             // Go the beginning of the month
00770                             $this->recur["start"] -= ($curmonthday-1) * 24*60*60;
00771                             // Go the the correct month day
00772                             $this->recur["start"] += (((int) $this->recur["monthday"])-1) * 24*60*60;
00773 
00774                             // If the previous calculation gave us a start date *before* the original start date, then we need to skip to the next occurrence
00775                             if ( ($term == 0x0C /*monthly*/ && ((int) $this->recur["monthday"]) < $curmonthday) ||
00776                                 ($term == 0x0D /*yearly*/ &&( $selmonth < $curmonth || ($selmonth == $curmonth && ((int) $this->recur["monthday"]) < $curmonthday)) ))
00777                             {
00778                                 if($term == 0x0D /*yearly*/)
00779                                     $count = ($everyn - ($curmonth - $selmonth)); // Yearly, go to next occurrence in 'everyn' months minus difference in first occurence and original date
00780                                 else
00781                                     $count = $everyn; // Monthly, go to next occurrence in 'everyn' months
00782 
00783                                 // Forward by $count months. This is done by getting the number of days in that month and forwarding that many days
00784                                 for($i=0; $i < $count; $i++) {
00785                                     $this->recur["start"] += $this->getMonthInSeconds($curyear, $curmonth);
00786 
00787                                     if($curmonth == 12) {
00788                                         $curyear++;
00789                                         $curmonth = 0;
00790                                     }
00791                                     $curmonth++;
00792                                 }
00793                             }
00794 
00795                             // "start" is now pointing to the first occurrence, except that it will overshoot if the
00796                             // month in which it occurs has less days than specified as the day of the month. So 31st
00797                             // of each month will overshoot in february (29 days). We compensate for that by checking
00798                             // if the day of the month we got is wrong, and then back up to the last day of the previous
00799                             // month.
00800                             if(((int) $this->recur["monthday"]) >=28 && ((int) $this->recur["monthday"]) <= 31 &&
00801                                 gmdate("j", ((int) $this->recur["start"])) < ((int) $this->recur["monthday"]))
00802                             {
00803                                 $this->recur["start"] -= gmdate("j", ((int) $this->recur["start"])) * 24 * 60 *60;
00804                             }
00805 
00806                             // "start" is now the first occurrence
00807 
00808                             if($term == 0x0C /*monthly*/) {
00809                                 // Calc first occ
00810                                 $monthIndex = ((((12%$everyn) * ((((int) gmdate("Y", $this->recur["start"])) - 1601)%$everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1))%$everyn;
00811 
00812                                 $firstocc = 0;
00813                                 for($i=0; $i < $monthIndex; $i++) {
00814                                     $firstocc+= $this->getMonthInSeconds(1601 + floor($i/12), ($i%12)+1) / 60;
00815                                 }
00816 
00817                                 $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
00818                             } else{
00819                                 // Calc first occ
00820                                 $firstocc = 0;
00821                                 $monthIndex = (int) gmdate("n", $this->recur["start"]);
00822                                 for($i=1; $i < $monthIndex; $i++) {
00823                                     $firstocc+= $this->getMonthInSeconds(1601 + floor($i/12), $i) / 60;
00824                                 }
00825 
00826                                 $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
00827                             }
00828                             break;
00829 
00830                         case 3:
00831                             // monthly: on Nth weekday of every M month
00832                             // yearly: on Nth weekday of M month
00833                             if(!isset($this->recur["weekdays"]) && !isset($this->recur["nday"])) {
00834                                 return;
00835                             }
00836 
00837                             $weekdays = (int) $this->recur["weekdays"];
00838                             $nday = (int) $this->recur["nday"];
00839 
00840                             // Calc startdate
00841                             $monthbegindow = (int) $this->recur["start"];
00842 
00843                             if($nday == 5) {
00844                                 // Set date on the last day of the last month
00845                                 $monthbegindow += (gmdate("t", $monthbegindow ) - gmdate("j", $monthbegindow )) * 24 * 60 * 60;
00846                             }else {
00847                                 // Set on the first day of the month
00848                                 $monthbegindow -= ((gmdate("j", $monthbegindow )-1) * 24 * 60 * 60);
00849                             }
00850 
00851                             if($term == 0x0D /*yearly*/) {
00852                                 // Set on right month
00853                                 if($selmonth < $curmonth)
00854                                     $tmp = 12 - $curmonth + $selmonth;
00855                                 else
00856                                     $tmp = ($selmonth - $curmonth);
00857 
00858                                 for($i=0; $i < $tmp; $i++) {
00859                                     $monthbegindow += $this->getMonthInSeconds($curyear, $curmonth);
00860 
00861                                     if($curmonth == 12) {
00862                                         $curyear++;
00863                                         $curmonth = 0;
00864                                     }
00865                                     $curmonth++;
00866                                 }
00867 
00868                             }else {
00869                                 // Check or you exist in the right month
00870 
00871                                 for($i = 0; $i < 7; $i++) {
00872                                     if($nday == 5 && (1<<( (gmdate("w", $monthbegindow)-$i)%7) ) & $weekdays) {
00873                                         $day = gmdate("j", $monthbegindow) - $i;
00874                                         break;
00875                                     }else if($nday != 5 && (1<<( (gmdate("w", $monthbegindow )+$i)%7) ) & $weekdays) {
00876                                         $day = (($nday-1)*7) + ($i+1);
00877                                         break;
00878                                     }
00879                                 }
00880 
00881                                 // Goto the next X month
00882                                 if(isset($day) && ($day < gmdate("j", (int) $this->recur["start"])) ) {
00883                                     if($nday == 5) {
00884                                         $monthbegindow += 24 * 60 * 60;
00885                                         if($curmonth == 12) {
00886                                             $curyear++;
00887                                             $curmonth = 0;
00888                                         }
00889                                         $curmonth++;
00890                                     }
00891 
00892                                     for($i=0; $i < $everyn; $i++) {
00893                                         $monthbegindow += $this->getMonthInSeconds($curyear, $curmonth);
00894 
00895                                         if($curmonth == 12) {
00896                                             $curyear++;
00897                                             $curmonth = 0;
00898                                         }
00899                                         $curmonth++;
00900                                     }
00901 
00902                                     if($nday == 5) {
00903                                         $monthbegindow -= 24 * 60 * 60;
00904                                     }
00905                                 }
00906                             }
00907 
00908                             //FIXME: weekstart?
00909 
00910                             $day = 0;
00911                             // Set start on the right day
00912                             for($i = 0; $i < 7; $i++) {
00913                                 if($nday == 5 && (1<<( (gmdate("w", $monthbegindow )-$i)%7) ) & $weekdays) {
00914                                     $day = $i;
00915                                     break;
00916                                 }else if($nday != 5 && (1<<( (gmdate("w", $monthbegindow )+$i)%7) ) & $weekdays) {
00917                                     $day = ($nday - 1) * 7 + ($i+1);
00918                                     break;
00919                                 }
00920                             }
00921                             if($nday == 5)
00922                                 $monthbegindow -= $day * 24 * 60 *60;
00923                             else
00924                                 $monthbegindow += ($day-1) * 24 * 60 *60;
00925 
00926                             $firstocc = 0;
00927 
00928                             if($term == 0x0C /*monthly*/) {
00929                                 // Calc first occ
00930                                 $monthIndex = ((((12%$everyn) * (((int) gmdate("Y", $this->recur["start"]) - 1601)%$everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1))%$everyn;
00931 
00932                                 for($i=0; $i < $monthIndex; $i++) {
00933                                     $firstocc+= $this->getMonthInSeconds(1601 + floor($i/12), ($i%12)+1) / 60;
00934                                 }
00935 
00936                                 $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday);
00937                             } else {
00938                                 // Calc first occ
00939                                 $monthIndex = (int) gmdate("n", $this->recur["start"]);
00940 
00941                                 for($i=1; $i < $monthIndex; $i++) {
00942                                     $firstocc+= $this->getMonthInSeconds(1601 + floor($i/12), $i) / 60;
00943                                 }
00944 
00945                                 $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday);
00946                             }
00947                             break;
00948                     }
00949                     break;
00950 
00951 
00952 
00953             }
00954 
00955             if(!isset($this->recur["term"])) {
00956                 return;
00957             }
00958 
00959             // Terminate
00960             $term = (int) $this->recur["term"];
00961             $rdata .= pack("CCCC", $term, 0x20, 0x00, 0x00);
00962 
00963             switch($term)
00964             {
00965                 // After the given enddate
00966                 case 0x21:
00967                     $rdata .= pack("V", 10);
00968                     break;
00969                 // After a number of times
00970                 case 0x22:
00971                     if(!isset($this->recur["numoccur"])) {
00972                         return;
00973                     }
00974 
00975                     $rdata .= pack("V", (int) $this->recur["numoccur"]);
00976                     break;
00977                 // Never ends
00978                 case 0x23:
00979                     $rdata .= pack("V", 0);
00980                     break;
00981             }
00982 
00983             // Strange little thing for the recurrence type "every workday"
00984             if(((int) $this->recur["type"]) == 0x0B && ((int) $this->recur["subtype"]) == 1) {
00985                 $rdata .= pack("V", 1);
00986             } else { // Other recurrences
00987                 $rdata .= pack("V", 0);
00988             }
00989 
00990             // Exception data
00991 
00992             // Get all exceptions
00993             $deleted_items = $this->recur["deleted_occurences"];
00994             $changed_items = $this->recur["changed_occurences"];
00995 
00996             // Merge deleted and changed items into one list
00997             $items = $deleted_items;
00998 
00999             foreach($changed_items as $changed_item)
01000                 array_push($items, $changed_item["basedate"]);
01001 
01002             sort($items);
01003 
01004             // Add the merged list in to the rdata
01005             $rdata .= pack("V", count($items));
01006             foreach($items as $item)
01007                 $rdata .= pack("V", $this->unixDataToRecurData($item));
01008 
01009             // Loop through the changed exceptions (not deleted)
01010             $rdata .= pack("V", count($changed_items));
01011             $items = array();
01012 
01013             foreach($changed_items as $changed_item)
01014             {
01015                 $items[] = $this->dayStartOf($changed_item["start"]);
01016             }
01017 
01018             sort($items);
01019 
01020             // Add the changed items list int the rdata
01021             foreach($items as $item)
01022                 $rdata .= pack("V", $this->unixDataToRecurData($item));
01023 
01024             // Set start date
01025             $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["start"]));
01026 
01027             // Set enddate
01028             switch($term)
01029             {
01030                 // After the given enddate
01031                 case 0x21:
01032                     $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]));
01033                     break;
01034                 // After a number of times
01035                 case 0x22:
01036                     // @todo: calculate enddate with intval($this->recur["startocc"]) + intval($this->recur["duration"]) > 24 hour
01037                     $occenddate = (int) $this->recur["start"];
01038 
01039                     switch((int) $this->recur["type"]) {
01040                         case 0x0A: //daily
01041 
01042                             if($this->recur["subtype"] == 1) {
01043                                 // Daily every workday
01044                                 $restocc = (int) $this->recur["numoccur"];
01045 
01046                                 // Get starting weekday
01047                                 $nowtime = $this->gmtime($occenddate);
01048                                 $j = $nowtime["tm_wday"];
01049 
01050                                 while(1)
01051                                 {
01052                                     if(($j%7) > 0 && ($j%7)<6 ) {
01053                                         $restocc--;
01054                                     }
01055 
01056                                     $j++;
01057 
01058                                     if($restocc <= 0)
01059                                         break;
01060 
01061                                     $occenddate += 24*60*60;
01062                                 }
01063 
01064                             } else {
01065                                 // -1 because the first day already counts (from 1-1-1980 to 1-1-1980 is 1 occurrence)
01066                                 $occenddate += (((int) $this->recur["everyn"]) * 60 * (((int) $this->recur["numoccur"]-1)));
01067                             }
01068                             break;
01069                         case 0x0B: //weekly
01070                             // Needed values
01071                             // $forwardcount - number of weeks we can skip forward
01072                             // $restocc - number of remaning occurrences after the week skip
01073 
01074                             // Add the weeks till the last item
01075                             $occenddate+=($forwardcount*7*24*60*60);
01076 
01077                             $dayofweek = gmdate("w", $occenddate);
01078 
01079                             // Loop through the last occurrences until we have had them all
01080                             for($j = 1; $restocc>0; $j++)
01081                             {
01082                                 // Jump to the next week (which may be N weeks away) when going over the week boundary
01083                                 if((($dayofweek+$j)%7) == $weekstart)
01084                                     $occenddate += (((int) $this->recur["everyn"])-1) * 7 * 24*60*60;
01085 
01086                                 // If this is a matching day, once less occurrence to process
01087                                 if(((int) $this->recur["weekdays"]) & (1<<(($dayofweek+$j)%7)) ) {
01088                                     $restocc--;
01089                                 }
01090 
01091                                 // Next day
01092                                 $occenddate += 24*60*60;
01093                             }
01094 
01095                             break;
01096                         case 0x0C: //monthly
01097                         case 0x0D: //yearly
01098 
01099                             $curyear = gmdate("Y", (int) $this->recur["start"] );
01100                             $curmonth = gmdate("n", (int) $this->recur["start"] );
01101                             // $forwardcount = months
01102 
01103                             switch((int) $this->recur["subtype"])
01104                             {
01105                                 case 2: // on D day of every M month
01106                                     while($forwardcount > 0)
01107                                     {
01108                                         $occenddate += $this->getMonthInSeconds($curyear, $curmonth);
01109 
01110                                         if($curmonth >=12) {
01111                                             $curmonth = 1;
01112                                             $curyear++;
01113                                         } else {
01114                                             $curmonth++;
01115                                         }
01116                                         $forwardcount--;
01117                                     }
01118 
01119                                     // compensation between 28 and 31
01120                                     if(((int) $this->recur["monthday"]) >=28 && ((int) $this->recur["monthday"]) <= 31 &&
01121                                         gmdate("j", $occenddate) < ((int) $this->recur["monthday"]))
01122                                     {
01123                                         if(gmdate("j", $occenddate) < 28)
01124                                             $occenddate -= gmdate("j", $occenddate) * 24 * 60 *60;
01125                                         else
01126                                             $occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 *60;
01127                                     }
01128 
01129 
01130                                     break;
01131                                 case 3: // on Nth weekday of every M month
01132                                     $nday = (int) $this->recur["nday"]; //1 tot 5
01133                                     $weekdays = (int) $this->recur["weekdays"];
01134 
01135 
01136                                     while($forwardcount > 0)
01137                                     {
01138                                         $occenddate += $this->getMonthInSeconds($curyear, $curmonth);
01139                                         if($curmonth >=12) {
01140                                             $curmonth = 1;
01141                                             $curyear++;
01142                                         } else {
01143                                             $curmonth++;
01144                                         }
01145 
01146                                         $forwardcount--;
01147                                     }
01148 
01149                                     if($nday == 5) {
01150                                         // Set date on the last day of the last month
01151                                         $occenddate += (gmdate("t", $occenddate ) - gmdate("j", $occenddate )) * 24 * 60 * 60;
01152                                     }else {
01153                                         // Set date on the first day of the last month
01154                                         $occenddate -= (gmdate("j", $occenddate )-1) * 24 * 60 * 60;
01155                                     }
01156 
01157                                     for($i = 0; $i < 7; $i++) {
01158                                         if( $nday == 5 && (1<<( (gmdate("w", $occenddate)-$i)%7) ) & $weekdays) {
01159                                             $occenddate -= $i * 24 * 60 * 60;
01160                                             break;
01161                                         }else if($nday != 5 && (1<<( (gmdate("w", $occenddate)+$i)%7) ) & $weekdays) {
01162                                             $occenddate +=  ($i + (($nday-1) *7)) * 24 * 60 * 60;
01163                                             break;
01164                                         }
01165                                     }
01166 
01167                                 break; //case 3:
01168                                 }
01169 
01170                             break;
01171 
01172                     }
01173 
01174                     if (defined("PHP_INT_MAX") && $occenddate > PHP_INT_MAX)
01175                         $occenddate = PHP_INT_MAX;
01176 
01177                     $this->recur["end"] = $occenddate;
01178 
01179                     $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]) );
01180                     break;
01181                 // Never ends
01182                 case 0x23:
01183                 default:
01184                     $this->recur["end"] = 0x7fffffff; // max date -> 2038
01185                     $rdata .= pack("V", 0x5AE980DF);
01186                     break;
01187             }
01188 
01189             // UTC date
01190             $utcstart = $this->toGMT($this->tz, (int) $this->recur["start"]);
01191             $utcend = $this->toGMT($this->tz, (int) $this->recur["end"]);
01192 
01193             //utc date+time
01194             $utcfirstoccstartdatetime = (isset($this->recur["startocc"])) ? $utcstart + (((int) $this->recur["startocc"])*60) : $utcstart;
01195             $utcfirstoccenddatetime = (isset($this->recur["endocc"])) ? $utcstart + (((int) $this->recur["endocc"]) * 60) : $utcstart;
01196 
01197             // update reminder time
01198             mapi_setprops($this->message, Array($this->proptags["reminder_time"] => $utcfirstoccstartdatetime ));
01199 
01200             // update first occurrence date
01201             mapi_setprops($this->message, Array($this->proptags["startdate"] => $utcfirstoccstartdatetime ));
01202             mapi_setprops($this->message, Array($this->proptags["duedate"] => $utcfirstoccenddatetime ));
01203             mapi_setprops($this->message, Array($this->proptags["commonstart"] => $utcfirstoccstartdatetime ));
01204             mapi_setprops($this->message, Array($this->proptags["commonend"] => $utcfirstoccenddatetime ));
01205 
01206             // Set Outlook properties, if it is an appointment
01207             if (isset($this->recur["message_class"]) && $this->recur["message_class"] == "IPM.Appointment") {
01208                 // update real begin and real end date
01209                 mapi_setprops($this->message, Array($this->proptags["startdate_recurring"] => $utcstart));
01210                 mapi_setprops($this->message, Array($this->proptags["enddate_recurring"] => $utcend));
01211 
01212                 // recurrencetype
01213                 // Strange enough is the property recurrencetype, (type-0x9) and not the CDO recurrencetype
01214                 mapi_setprops($this->message, Array($this->proptags["recurrencetype"] => ((int) $this->recur["type"]) - 0x9));
01215 
01216                 // set named prop 'side_effects' to 369, needed for Outlook to ask for single or total recurrence when deleting
01217                 mapi_setprops($this->message, Array($this->proptags["side_effects"] => 369));
01218             } else {
01219                 mapi_setprops($this->message, Array($this->proptags["side_effects"] => 3441));
01220             }
01221 
01222             // FlagDueBy is datetime of the first reminder occurrence. Outlook gives on this time a reminder popup dialog
01223             // Any change of the recurrence (including changing and deleting exceptions) causes the flagdueby to be reset
01224             // to the 'next' occurrence; this makes sure that deleting the next ocurrence will correctly set the reminder to
01225             // the occurrence after that. The 'next' occurrence is defined as being the first occurrence that starts at moment X (server time)
01226             // with the reminder flag set.
01227             $reminderprops = mapi_getprops($this->message, array($this->proptags["reminder_minutes"]) );
01228             if(isset($reminderprops[$this->proptags["reminder_minutes"]]) ) {
01229                 $occ = false;
01230                 $occurrences = $this->getItems(time(), 0x7ff00000, 3, true);
01231 
01232                 for($i = 0, $len = count($occurrences) ; $i < $len; $i++) {
01233                     // This will actually also give us appointments that have already started, but not yet ended. Since we want the next
01234                     // reminder that occurs after time(), we may have to skip the first few entries. We get 3 entries since that is the maximum
01235                     // number that would be needed (assuming reminder for item X cannot be before the previous occurrence starts). Worst case:
01236                     // time() is currently after start but before end of item, but reminder of next item has already passed (reminder for next item
01237                     // can be DURING the previous item, eg daily allday events). In that case, the first and second items must be skipped.
01238 
01239                     if(($occurrences[$i][$this->proptags["startdate"]] - $reminderprops[$this->proptags["reminder_minutes"]] * 60) > time()) {
01240                         $occ = $occurrences[$i];
01241                         break;
01242                     }
01243                 }
01244 
01245                 if($occ) {
01246                     mapi_setprops($this->message, Array($this->proptags["flagdueby"] => $occ[$this->proptags["startdate"]] - ($reminderprops[$this->proptags["reminder_minutes"]] * 60) ));
01247                 } else {
01248                     // Last reminder passed, no reminders any more.
01249                     mapi_setprops($this->message, Array($this->proptags["reminder"] => false, $this->proptags["flagdueby"] => 0x7ff00000));
01250                 }
01251             }
01252 
01253             // Default data
01254             // Second item (0x08) indicates the Outlook version (see documentation at the bottom of this file for more information)
01255             $rdata .= pack("VCCCC", 0x00003006, 0x08, 0x30, 0x00, 0x00);
01256 
01257             if(isset($this->recur["startocc"]) && isset($this->recur["endocc"])) {
01258                 // Set start and endtime in minutes
01259                 $rdata .= pack("VV", (int) $this->recur["startocc"], (int) $this->recur["endocc"]);
01260             }
01261 
01262             // Detailed exception data
01263 
01264             $changed_items = $this->recur["changed_occurences"];
01265 
01266             $rdata .= pack("v", count($changed_items));
01267 
01268             foreach($changed_items as $changed_item)
01269             {
01270                 // Set start and end time of exception
01271                 $rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"]));
01272                 $rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"]));
01273                 $rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"]));
01274 
01275                 //Bitmask
01276                 $bitmask = 0;
01277 
01278                 // Check for changed strings
01279                 if(isset($changed_item["subject"]))    {
01280                     $bitmask |= 1 << 0;
01281                 }
01282 
01283                 if(isset($changed_item["remind_before"])) {
01284                     $bitmask |= 1 << 2;
01285                 }
01286 
01287                 if(isset($changed_item["reminder_set"])) {
01288                     $bitmask |= 1 << 3;
01289                 }
01290 
01291                 if(isset($changed_item["location"])) {
01292                     $bitmask |= 1 << 4;
01293                 }
01294 
01295                 if(isset($changed_item["busystatus"])) {
01296                     $bitmask |= 1 << 5;
01297                 }
01298 
01299                 if(isset($changed_item["alldayevent"])) {
01300                     $bitmask |= 1 << 7;
01301                 }
01302 
01303                 if(isset($changed_item["label"])) {
01304                     $bitmask |= 1 << 8;
01305                 }
01306 
01307                 $rdata .= pack("v", $bitmask);
01308 
01309                 // Set "subject"
01310                 if(isset($changed_item["subject"])) {
01311                     // convert utf-8 to non-unicode blob string (us-ascii?)
01312                     $subject = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["subject"]);
01313                     $length = strlen($subject);
01314                     $rdata .= pack("vv", $length + 1, $length);
01315                     $rdata .= pack("a".$length, $subject);
01316                 }
01317 
01318                 if(isset($changed_item["remind_before"])) {
01319                     $rdata .= pack("V", $changed_item["remind_before"]);
01320                 }
01321 
01322                 if(isset($changed_item["reminder_set"])) {
01323                     $rdata .= pack("V", $changed_item["reminder_set"]);
01324                 }
01325 
01326                 if(isset($changed_item["location"])) {
01327                     $location = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["location"]);
01328                     $length = strlen($location);
01329                     $rdata .= pack("vv", $length + 1, $length);
01330                     $rdata .= pack("a".$length, $location);
01331                 }
01332 
01333                 if(isset($changed_item["busystatus"])) {
01334                     $rdata .= pack("V", $changed_item["busystatus"]);
01335                 }
01336 
01337                 if(isset($changed_item["alldayevent"])) {
01338                     $rdata .= pack("V", $changed_item["alldayevent"]);
01339                 }
01340 
01341                 if(isset($changed_item["label"])) {
01342                     $rdata .= pack("V", $changed_item["label"]);
01343                 }
01344             }
01345 
01346             $rdata .= pack("V", 0);
01347 
01348             // write extended data
01349             foreach($changed_items as $changed_item)
01350             {
01351                 $rdata .= pack("V", 0);
01352                 if(isset($changed_item["subject"]) || isset($changed_item["location"])) {
01353                     $rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"]));
01354                     $rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"]));
01355                     $rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"]));
01356                 }
01357 
01358                 if(isset($changed_item["subject"])) {
01359                     $subject = iconv("UTF-8", "UCS-2LE", $changed_item["subject"]);
01360                     $length = iconv_strlen($subject, "UCS-2LE");
01361                     $rdata .= pack("v", $length);
01362                     $rdata .= pack("a".$length*2, $subject);
01363                 }
01364 
01365                 if(isset($changed_item["location"])) {
01366                     $location = iconv("UTF-8", "UCS-2LE", $changed_item["location"]);
01367                     $length = iconv_strlen($location, "UCS-2LE");
01368                     $rdata .= pack("v", $length);
01369                     $rdata .= pack("a".$length*2, $location);
01370                 }
01371 
01372                 if(isset($changed_item["subject"]) || isset($changed_item["location"])) {
01373                     $rdata .= pack("V", 0);
01374                 }
01375             }
01376 
01377             $rdata .= pack("V", 0);
01378 
01379             // Set props
01380             mapi_setprops($this->message, Array($this->proptags["recurring_data"] => $rdata, $this->proptags["recurring"] => true));
01381             if(isset($this->tz) && $this->tz){
01382                 $timezone = "GMT";
01383                 if ($this->tz["timezone"]!=0){
01384                     // Create user readable timezone information
01385                     $timezone = sprintf("(GMT %s%02d:%02d)",    (-$this->tz["timezone"]>0 ? "+" : "-"),
01386                                                             abs($this->tz["timezone"]/60),
01387                                                             abs($this->tz["timezone"]%60));
01388                 }
01389                 mapi_setprops($this->message, Array($this->proptags["timezone_data"] => $this->getTimezoneData($this->tz),
01390                                                     $this->proptags["timezone"] => $timezone));
01391             }
01392         }
01393 
01400         function recurDataToUnixData($rdate)
01401         {
01402             return ($rdate - 194074560) * 60 ;
01403         }
01404 
01411         function unixDataToRecurData($date)
01412         {
01413             return ($date / 60) + 194074560;
01414         }
01415 
01420         function GetTZOffset($ts)
01421         {
01422             $Offset = date("O", $ts);
01423 
01424             $Parity = $Offset < 0 ? -1 : 1;
01425             $Offset = $Parity * $Offset;
01426             $Offset = ($Offset - ($Offset % 100)) / 100 * 60 + $Offset % 100;
01427 
01428             return $Parity * $Offset;
01429         }
01430 
01437         function gmtime($time)
01438         {
01439             $TZOffset = $this->GetTZOffset($time);
01440 
01441             $t_time = $time - $TZOffset * 60; #Counter adjust for localtime()
01442             $t_arr = localtime($t_time, 1);
01443 
01444             return $t_arr;
01445         }
01446 
01447         function isLeapYear($year) {
01448             return ( $year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0) );
01449         }
01450 
01451         function getMonthInSeconds($year, $month)
01452         {
01453             if( in_array($month, array(1,3,5,7,8,10,12) ) ) {
01454                 $day = 31;
01455             } else if( in_array($month, array(4,6,9,11) ) ) {
01456                 $day = 30;
01457             } else {
01458                 $day = 28;
01459                 if( $this->isLeapYear($year) == 1 )
01460                     $day++;
01461             }
01462             return $day * 24 * 60 * 60;
01463         }
01464 
01474         function getDateByYearMonthWeekDayHour($year, $month, $week, $day, $hour)
01475         {
01476             // get first day of month
01477             $date = gmmktime(0,0,0,$month,0,$year + 1900);
01478 
01479             // get wday info
01480             $gmdate = $this->gmtime($date);
01481 
01482             $date -= $gmdate["tm_wday"] * 24 * 60 * 60; // back up to start of week
01483 
01484             $date += $week * 7 * 24 * 60 * 60; // go to correct week nr
01485             $date += $day * 24 * 60 * 60;
01486             $date += $hour * 60 * 60;
01487 
01488             $gmdate = $this->gmtime($date);
01489 
01490             // if we are in the next month, then back up a week, because week '5' means
01491             // 'last week of month'
01492 
01493             if($gmdate["tm_mon"]+1 != $month)
01494                 $date -= 7 * 24 * 60 * 60;
01495 
01496             return $date;
01497         }
01498 
01503         function getTimezone($tz, $date)
01504         {
01505             // No timezone -> GMT (+0)
01506             if(!isset($tz["timezone"]))
01507                 return 0;
01508 
01509             $dst = false;
01510             $gmdate = $this->gmtime($date);
01511 
01512             $dststart = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dststartmonth"], $tz["dststartweek"], 0, $tz["dststarthour"]);
01513             $dstend = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dstendmonth"], $tz["dstendweek"], 0, $tz["dstendhour"]);
01514 
01515             if($dststart <= $dstend) {
01516                 // Northern hemisphere, eg DST is during Mar-Oct
01517                 if($date > $dststart && $date < $dstend) {
01518                     $dst = true;
01519                 }
01520             } else {
01521                 // Southern hemisphere, eg DST is during Oct-Mar
01522                 if($date < $dstend || $date > $dststart) {
01523                     $dst = true;
01524                 }
01525             }
01526 
01527             if($dst) {
01528                 return $tz["timezone"] + $tz["timezonedst"];
01529             } else {
01530                 return $tz["timezone"];
01531             }
01532         }
01533 
01537         function getWeekNr($date)
01538         {
01539             $gmdate = gmtime($date);
01540             $gmdate["tm_mday"] = 0;
01541             return strftime("%W", $date) - strftime("%W", gmmktime($gmdate)) + 1;
01542         }
01543 
01549         function parseTimezone($data)
01550         {
01551             if(strlen($data) < 48)
01552                 return;
01553 
01554             $tz = unpack("ltimezone/lunk/ltimezonedst/lunk/ldstendmonth/vdstendweek/vdstendhour/lunk/lunk/vunk/ldststartmonth/vdststartweek/vdststarthour/lunk/vunk", $data);
01555             return $tz;
01556         }
01557 
01558         function getTimezoneData($tz)
01559         {
01560             $data = pack("lllllvvllvlvvlv", $tz["timezone"], 0, $tz["timezonedst"], 0, $tz["dstendmonth"], $tz["dstendweek"], $tz["dstendhour"], 0, 0, 0, $tz["dststartmonth"], $tz["dststartweek"], $tz["dststarthour"], 0 ,0);
01561 
01562             return $data;
01563         }
01564 
01570         function createTimezone($tz)
01571         {
01572             $data = pack("lxxxxlxxxxlvvxxxxxxxxxxlvvxxxxxx",
01573                         $tz["timezone"],
01574                         array_key_exists("timezonedst",$tz)?$tz["timezonedst"]:0,
01575                         array_key_exists("dstendmonth",$tz)?$tz["dstendmonth"]:0,
01576                         array_key_exists("dstendweek",$tz)?$tz["dstendweek"]:0,
01577                         array_key_exists("dstendhour",$tz)?$tz["dstendhour"]:0,
01578                         array_key_exists("dststartmonth",$tz)?$tz["dststartmonth"]:0,
01579                         array_key_exists("dststartweek",$tz)?$tz["dststartweek"]:0,
01580                         array_key_exists("dststarthour",$tz)?$tz["dststarthour"]:0
01581                     );
01582 
01583             return $data;
01584         }
01585 
01589         function toGMT($tz, $date) {
01590             if(!isset($tz['timezone']))
01591                 return $date;
01592             $offset = $this->getTimezone($tz, $date);
01593 
01594             return $date + $offset * 60;
01595         }
01596 
01600         function fromGMT($tz, $date) {
01601             $offset = $this->getTimezone($tz, $date);
01602 
01603             return $date - $offset * 60;
01604         }
01605 
01611         function dayStartOf($date)
01612         {
01613             $time1 = $this->gmtime($date);
01614 
01615             return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, $time1["tm_mday"], $time1["tm_year"] + 1900);
01616         }
01617 
01623         function monthStartOf($date)
01624         {
01625             $time1 = $this->gmtime($date);
01626 
01627             return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, 1, $time1["tm_year"] + 1900);
01628         }
01629 
01635         function yearStartOf($date)
01636         {
01637             $time1 = $this->gmtime($date);
01638 
01639             return gmmktime(0, 0, 0, 1, 1, $time1["tm_year"] + 1900);
01640         }
01641 
01642 
01652         function getItems($start, $end, $limit = 0, $remindersonly = false)
01653         {
01654             $items = array();
01655 
01656             if(isset($this->recur)) {
01657 
01658                 // Optimization: remindersonly and default reminder is off; since only exceptions with reminder set will match, just look which
01659                 // exceptions are in range and have a reminder set
01660                 if($remindersonly && (!isset($this->messageprops[$this->proptags["reminder"]]) || $this->messageprops[$this->proptags["reminder"]] == false)) {
01661                     // Sort exceptions by start time
01662                     uasort($this->recur["changed_occurences"], array($this, "sortExceptionStart"));
01663 
01664                     // Loop through all changed exceptions
01665                     foreach($this->recur["changed_occurences"] as $exception) {
01666                         // Check reminder set
01667                         if(!isset($exception["reminder"]) || $exception["reminder"] == false)
01668                             continue;
01669 
01670                         // Convert to GMT
01671                         $occstart = $this->toGMT($this->tz, $exception["start"]); // seb changed $tz to $this->tz
01672                         $occend = $this->toGMT($this->tz, $exception["end"]); // seb changed $tz to $this->tz
01673 
01674                         // Check range criterium
01675                         if($occstart > $end || $occend < $start)
01676                             continue;
01677 
01678                         // OK, add to items.
01679                         array_push($items, $this->getExceptionProperties($exception));
01680                         if($limit && (count($items) == $limit))
01681                             break;
01682                     }
01683 
01684                     uasort($items, array($this, "sortStarttime"));
01685 
01686                     return $items;
01687                 }
01688 
01689                 // From here on, the dates of the occurrences are calculated in local time, so the days we're looking
01690                 // at are calculated from the local time dates of $start and $end
01691 
01692                 if ($this->recur['regen'] && isset($this->action['datecompleted'])) {
01693                     $daystart = $this->dayStartOf($this->action['datecompleted']);
01694                 } else {
01695                     $daystart = $this->dayStartOf($this->recur["start"]); // start on first day of occurrence
01696                 }
01697 
01698                 // Calculate the last day on which we want to be looking at a recurrence; this is either the end of the view
01699                 // or the end of the recurrence, whichever comes first
01700                 if($end > $this->toGMT($this->tz, $this->recur["end"])) {
01701                     $rangeend = $this->toGMT($this->tz, $this->recur["end"]);
01702                 } else {
01703                     $rangeend = $end;
01704                 }
01705 
01706                 $dayend = $this->dayStartOf($this->fromGMT($this->tz, $rangeend));
01707 
01708                 // Loop through the entire recurrence range of dates, and check for each occurrence whether it is in the view range.
01709 
01710                 switch($this->recur["type"])
01711                 {
01712                 case 10:
01713                     // Daily
01714                     if($this->recur["everyn"] <= 0)
01715                         $this->recur["everyn"] = 1440;
01716 
01717                     if($this->recur["subtype"] == 0) {
01718                         // Every Nth day
01719                         for($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * $this->recur["everyn"]) {
01720                             $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
01721                         }
01722                     } else {
01723                         // Every workday
01724                         for($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * 1440)
01725                         {
01726                             $nowtime = $this->gmtime($now);
01727                             if ($nowtime["tm_wday"] > 0 && $nowtime["tm_wday"] < 6) { // only add items in the given timespace
01728                                 $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
01729                             }
01730                         }
01731                     }
01732                     break;
01733                 case 11:
01734                     // Weekly
01735                     if($this->recur["everyn"] <= 0)
01736                         $this->recur["everyn"] = 1;
01737 
01738                     // If sliding flag is set then move to 'n' weeks
01739                     if ($this->recur['regen']) $daystart += (60 * 60 * 24 * 7 * $this->recur["everyn"]);
01740 
01741                     for($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += (60 * 60 * 24 * 7 * $this->recur["everyn"]))
01742                     {
01743                         if ($this->recur['regen']) {
01744                             $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
01745                         } else {
01746                             // Loop through the whole following week to the first occurrence of the week, add each day that is specified
01747                             for($wday = 0; $wday < 7; $wday++)
01748                             {
01749                                 $daynow = $now + $wday * 60 * 60 * 24;
01750                                 //checks weather the next coming day in recurring pattern is less than or equal to end day of the recurring item
01751                                 if ($daynow <= $dayend){
01752                                     $nowtime = $this->gmtime($daynow); // Get the weekday of the current day
01753                                     if(($this->recur["weekdays"] &(1 << $nowtime["tm_wday"]))) { // Selected ?
01754                                         $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
01755                                     }
01756                                 }
01757                             }
01758                         }
01759                     }
01760                     break;
01761                 case 12:
01762                     // Monthly
01763                     if($this->recur["everyn"] <= 0)
01764                         $this->recur["everyn"] = 1;
01765 
01766                     // Loop through all months from start to end of occurrence, starting at beginning of first month
01767                     for($now = $this->monthStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60 )
01768                     {
01769                         if(isset($this->recur["monthday"]) &&($this->recur['monthday'] != "undefined") && !$this->recur['regen']) { // Day M of every N months
01770                             $difference = 1;
01771                             if ($this->daysInMonth($now, $this->recur["everyn"]) < $this->recur["monthday"]) {
01772                                 $difference = $this->recur["monthday"] - $this->daysInMonth($now, $this->recur["everyn"]) + 1;
01773                             }
01774                             $daynow = $now + (($this->recur["monthday"] - $difference) * 24 * 60 * 60);
01775                             //checks weather the next coming day in recurrence pattern is less than or equal to end day of the recurring item
01776                             if ($daynow <= $dayend){
01777                                 $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
01778                             }
01779                         }
01780                         else if(isset($this->recur["nday"]) && isset($this->recur["weekdays"])) { // Nth [weekday] of every N months
01781                             // Sanitize input
01782                             if($this->recur["weekdays"] == 0)
01783                                 $this->recur["weekdays"] = 1;
01784 
01785                             // If nday is not set to the last day in the month
01786                             if ($this->recur["nday"] < 5) {
01787                                 // keep the track of no. of time correct selection pattern(like 2nd weekday, 4th fiday, etc.)is matched
01788                                 $ndaycounter = 0;
01789                                 // Find matching weekday in this month
01790                                 for($day = 0; $day < $this->daysInMonth($now, 1); $day++)
01791                                 {
01792                                     $daynow = $now + $day * 60 * 60 * 24;
01793                                     $nowtime = $this->gmtime($daynow); // Get the weekday of the current day
01794 
01795                                     if($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
01796                                         $ndaycounter ++;
01797                                     }
01798                                     // check the selected pattern is same as asked Nth weekday,If so set the firstday
01799                                     if($this->recur["nday"] == $ndaycounter){
01800                                         $firstday = $day;
01801                                         break;
01802                                     }
01803                                 }
01804                                 // $firstday is the day of the month on which the asked pattern of nth weekday matches
01805                                 $daynow = $now + $firstday * 60 * 60 * 24;
01806                             }else{
01807                                 // Find last day in the month ($now is the firstday of the month)
01808                                 $NumDaysInMonth =  $this->daysInMonth($now, 1);
01809                                 $daynow = $now + (($NumDaysInMonth-1) * 24*60*60);
01810 
01811                                 $nowtime = $this->gmtime($daynow);
01812                                 while (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"]))==0){
01813                                     $daynow -= 86400;
01814                                     $nowtime = $this->gmtime($daynow);
01815                                 }
01816                             }
01817 
01821                             if ($daynow <= $dayend && $daynow >= $daystart){
01822                                 $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz , $remindersonly);
01823                             }
01824                         } else if ($this->recur['regen']) {
01825                             $next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60);
01826                             $now = $daystart +($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60);
01827 
01828                             if ($now <= $dayend) {
01829                                 $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
01830                             }
01831                         }
01832                     }
01833                     break;
01834                 case 13:
01835                     // Yearly
01836                     if($this->recur["everyn"] <= 0)
01837                         $this->recur["everyn"] = 12;
01838 
01839                     for($now = $this->yearStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60 )
01840                     {
01841                         if(isset($this->recur["monthday"]) && !$this->recur['regen']) { // same as monthly, but in a specific month
01842                             // recur["month"] is in minutes since the beginning of the year
01843                             $month = $this->monthOfYear($this->recur["month"]); // $month is now month of year [0..11]
01844                             $monthday = $this->recur["monthday"]; // $monthday is day of the month [1..31]
01845                             $monthstart = $now + $this->daysInMonth($now, $month) * 24 * 60 * 60; // $monthstart is the timestamp of the beginning of the month
01846                             if($monthday > $this->daysInMonth($monthstart, 1))
01847                                 $monthday = $this->daysInMonth($monthstart, 1);    // Cap $monthday on month length (eg 28 feb instead of 29 feb)
01848                             $daynow = $monthstart + ($monthday-1) * 24 * 60 * 60;
01849                             $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
01850                         }
01851                         else if(isset($this->recur["nday"]) && isset($this->recur["weekdays"])) { // Nth [weekday] in month X of every N years
01852 
01853                             // Go the correct month
01854                             $monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60;
01855 
01856                             // Find first matching weekday in this month
01857                             for($wday = 0; $wday < 7; $wday++)
01858                             {
01859                                 $daynow = $monthnow + $wday * 60 * 60 * 24;
01860                                 $nowtime = $this->gmtime($daynow); // Get the weekday of the current day
01861 
01862                                 if($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
01863                                     $firstday = $wday;
01864                                     break;
01865                                 }
01866                             }
01867 
01868                             // Same as above (monthly)
01869                             $daynow = $monthnow + ($firstday + ($this->recur["nday"]-1)*7) * 60 * 60 * 24;
01870 
01871                             while($this->monthStartOf($daynow) != $this->monthStartOf($monthnow)) {
01872                                 $daynow -= 7 * 60 * 60 * 24;
01873                             }
01874 
01875                             $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
01876                         } else if ($this->recur['regen']) {
01877                             $year_starttime = $this->gmtime($now);
01878                             $is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1);    // +1 next year
01879                             $now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /*year in seconds*/);
01880 
01881                             if ($now <= $dayend) {
01882                                 $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
01883                             }
01884                         }
01885                     }
01886                 }
01887                 //to get all exception items
01888                 if (!empty($this->recur['changed_occurences']))
01889                     $this->processExceptionItems($items, $start, $end);
01890             }
01891 
01892             // sort items on starttime
01893             usort($items, array($this, "sortStarttime"));
01894 
01895             // Return the MAPI-compatible list of items for this object
01896             return $items;
01897         }
01898 
01899         function sortStarttime($a, $b)
01900         {
01901             $aTime = $a[$this->proptags["startdate"]];
01902             $bTime = $b[$this->proptags["startdate"]];
01903 
01904             return $aTime==$bTime?0:($aTime>$bTime?1:-1);
01905         }
01906 
01919         function daysInMonth($date, $months) {
01920             $days = 0;
01921 
01922             for($i=0;$i<$months;$i++) {
01923                 $days += date("t", $date + $days * 24 * 60 * 60);
01924             }
01925 
01926             return $days;
01927         }
01928 
01929         // Converts MAPI-style 'minutes' into the month of the year [0..11]
01930         function monthOfYear($minutes) {
01931             $d = gmmktime(0,0,0,1,1,2001); // The year 2001 was a non-leap year, and the minutes provided are always in non-leap-year-minutes
01932 
01933             $d += $minutes*60;
01934 
01935             $dtime = $this->gmtime($d);
01936 
01937             return $dtime["tm_mon"];
01938         }
01939 
01940         function sortExceptionStart($a, $b)
01941         {
01942             return $a["start"] == $b["start"] ? 0 : ($a["start"]  > $b["start"] ? 1 : -1 );
01943         }
01944     }
01945 ?>