/backend/zarafa/mapi/class.baserecurrence.php
PHP | 1945 lines | 1154 code | 338 blank | 453 comment | 335 complexity | 6fc3151affb8bb22e76ab3bcd182af25 MD5 | raw file
Possible License(s): AGPL-3.0
Large files files are truncated, but you can click here to view the full file
- <?php
- /*
- * Copyright 2005 - 2012 Zarafa B.V.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation with the following additional
- * term according to sec. 7:
- *
- * According to sec. 7 of the GNU Affero General Public License, version
- * 3, the terms of the AGPL are supplemented with the following terms:
- *
- * "Zarafa" is a registered trademark of Zarafa B.V. The licensing of
- * the Program under the AGPL does not imply a trademark license.
- * Therefore any rights, title and interest in our trademarks remain
- * entirely with us.
- *
- * However, if you propagate an unmodified version of the Program you are
- * allowed to use the term "Zarafa" to indicate that you distribute the
- * Program. Furthermore you may use our trademarks where it is necessary
- * to indicate the intended purpose of a product or service provided you
- * use it in accordance with honest practices in industrial or commercial
- * matters. If you want to propagate modified versions of the Program
- * under the name "Zarafa" or "Zarafa Server", you may only do so if you
- * have a written permission by Zarafa B.V. (to acquire a permission
- * please contact Zarafa at trademark@zarafa.com).
- *
- * The interactive user interface of the software displays an attribution
- * notice containing the term "Zarafa" and/or the logo of Zarafa.
- * Interactive user interfaces of unmodified and modified versions must
- * display Appropriate Legal Notices according to sec. 5 of the GNU
- * Affero General Public License, version 3, when you propagate
- * unmodified or modified versions of the Program. In accordance with
- * sec. 7 b) of the GNU Affero General Public License, version 3, these
- * Appropriate Legal Notices must retain the logo of Zarafa or display
- * the words "Initial Development by Zarafa" if the display of the logo
- * is not reasonably feasible for technical reasons. The use of the logo
- * of Zarafa in Legal Notices is allowed for unmodified and modified
- * versions of the software.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- /**
- * BaseRecurrence
- * this class is superclass for recurrence for appointments and tasks. This class provides all
- * basic features of recurrence.
- */
- class BaseRecurrence
- {
- /**
- * @var object Mapi Message Store (may be null if readonly)
- */
- var $store;
- /**
- * @var object Mapi Message (may be null if readonly)
- */
- var $message;
- /**
- * @var array Message Properties
- */
- var $messageprops;
- /**
- * @var array list of property tags
- */
- var $proptags;
- /**
- * @var recurrence data of this calendar item
- */
- var $recur;
- /**
- * @var Timezone data of this calendar item
- */
- var $tz;
- /**
- * Constructor
- * @param resource $store MAPI Message Store Object
- * @param resource $message the MAPI (appointment) message
- * @param array $properties the list of MAPI properties the message has.
- */
- function BaseRecurrence($store, $message)
- {
- $this->store = $store;
- if(is_array($message)) {
- $this->messageprops = $message;
- } else {
- $this->message = $message;
- $this->messageprops = mapi_getprops($this->message, $this->proptags);
- }
- if(isset($this->messageprops[$this->proptags["recurring_data"]])) {
- // There is a possibility that recurr blob can be more than 255 bytes so get full blob through stream interface
- if (strlen($this->messageprops[$this->proptags["recurring_data"]]) >= 255) {
- $this->getFullRecurrenceBlob();
- }
- $this->recur = $this->parseRecurrence($this->messageprops[$this->proptags["recurring_data"]]);
- }
- if(isset($this->proptags["timezone_data"]) && isset($this->messageprops[$this->proptags["timezone_data"]])) {
- $this->tz = $this->parseTimezone($this->messageprops[$this->proptags["timezone_data"]]);
- }
- }
- function getRecurrence()
- {
- return $this->recur;
- }
- function getFullRecurrenceBlob()
- {
- $message = mapi_msgstore_openentry($this->store, $this->messageprops[PR_ENTRYID]);
- $recurrBlob = '';
- $stream = mapi_openproperty($message, $this->proptags["recurring_data"], IID_IStream, 0, 0);
- $stat = mapi_stream_stat($stream);
- for ($i = 0; $i < $stat['cb']; $i += 1024) {
- $recurrBlob .= mapi_stream_read($stream, 1024);
- }
- if (!empty($recurrBlob)) {
- $this->messageprops[$this->proptags["recurring_data"]] = $recurrBlob;
- }
- }
- /**
- * Function for parsing the Recurrence value of a Calendar item.
- *
- * Retrieve it from Named Property 0x8216 as a PT_BINARY and pass the
- * data to this function
- *
- * Returns a structure containing the data:
- *
- * type - type of recurrence: day=10, week=11, month=12, year=13
- * subtype - type of day recurrence: 2=monthday (ie 21st day of month), 3=nday'th weekdays (ie. 2nd Tuesday and Wednesday)
- * start - unix timestamp of first occurrence
- * end - unix timestamp of last occurrence (up to and including), so when start == end -> occurrences = 1
- * numoccur - occurrences (may be very large when there is no end data)
- *
- * then, for each type:
- *
- * Daily:
- * everyn - every [everyn] days in minutes
- * regen - regenerating event (like tasks)
- *
- * Weekly:
- * everyn - every [everyn] weeks in weeks
- * regen - regenerating event (like tasks)
- * weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
- *
- * Monthly:
- * everyn - every [everyn] months
- * regen - regenerating event (like tasks)
- *
- * subtype 2:
- * monthday - on day [monthday] of the month
- *
- * subtype 3:
- * weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
- * nday - on [nday]'th [weekdays] of the month
- *
- * Yearly:
- * everyn - every [everyn] months (12, 24, 36, ...)
- * month - in month [month] (although the month is encoded in minutes since the startning of the year ........)
- * regen - regenerating event (like tasks)
- *
- * subtype 2:
- * monthday - on day [monthday] of the month
- *
- * subtype 3:
- * weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
- * nday - on [nday]'th [weekdays] of the month [month]
- * @param string $rdata Binary string
- * @return array recurrence data.
- */
- function parseRecurrence($rdata)
- {
- if (strlen($rdata) < 10) {
- return;
- }
- $ret["changed_occurences"] = array();
- $ret["deleted_occurences"] = array();
- $data = unpack("Vconst1/Crtype/Cconst2/Vrtype2", $rdata);
- $ret["type"] = $data["rtype"];
- $ret["subtype"] = $data["rtype2"];
- $rdata = substr($rdata, 10);
- switch ($data["rtype"])
- {
- case 0x0a:
- // Daily
- if (strlen($rdata) < 12) {
- return $ret;
- }
- $data = unpack("Vunknown/Veveryn/Vregen", $rdata);
- $ret["everyn"] = $data["everyn"];
- $ret["regen"] = $data["regen"];
- switch($ret["subtype"])
- {
- case 0:
- $rdata = substr($rdata, 12);
- break;
- case 1:
- $rdata = substr($rdata, 16);
- break;
- }
- break;
- case 0x0b:
- // Weekly
- if (strlen($rdata) < 16) {
- return $ret;
- }
- $data = unpack("Vconst1/Veveryn/Vregen", $rdata);
- $rdata = substr($rdata, 12);
- $ret["everyn"] = $data["everyn"];
- $ret["regen"] = $data["regen"];
- $ret["weekdays"] = 0;
- if ($data["regen"] == 0) {
- $data = unpack("Vweekdays", $rdata);
- $rdata = substr($rdata, 4);
- $ret["weekdays"] = $data["weekdays"];
- }
- break;
- case 0x0c:
- // Monthly
- if (strlen($rdata) < 16) {
- return $ret;
- }
- $data = unpack("Vconst1/Veveryn/Vregen/Vmonthday", $rdata);
- $ret["everyn"] = $data["everyn"];
- $ret["regen"] = $data["regen"];
- if ($ret["subtype"] == 3) {
- $ret["weekdays"] = $data["monthday"];
- } else {
- $ret["monthday"] = $data["monthday"];
- }
- $rdata = substr($rdata, 16);
- if ($ret["subtype"] == 3) {
- $data = unpack("Vnday", $rdata);
- $ret["nday"] = $data["nday"];
- $rdata = substr($rdata, 4);
- }
- break;
- case 0x0d:
- // Yearly
- if (strlen($rdata) < 16)
- return $ret;
- $data = unpack("Vmonth/Veveryn/Vregen/Vmonthday", $rdata);
- $ret["month"] = $data["month"];
- $ret["everyn"] = $data["everyn"];
- $ret["regen"] = $data["regen"];
- if ($ret["subtype"] == 3) {
- $ret["weekdays"] = $data["monthday"];
- } else {
- $ret["monthday"] = $data["monthday"];
- }
- $rdata = substr($rdata, 16);
- if ($ret["subtype"] == 3) {
- $data = unpack("Vnday", $rdata);
- $ret["nday"] = $data["nday"];
- $rdata = substr($rdata, 4);
- }
- break;
- }
- if (strlen($rdata) < 16) {
- return $ret;
- }
- $data = unpack("Cterm/C3const1/Vnumoccur/Vconst2/Vnumexcept", $rdata);
- $rdata = substr($rdata, 16);
- $ret["term"] = $data["term"];
- $ret["numoccur"] = $data["numoccur"];
- $ret["numexcept"] = $data["numexcept"];
- // exc_base_dates are *all* the base dates that have been either deleted or modified
- $exc_base_dates = array();
- for($i = 0; $i < $ret["numexcept"]; $i++)
- {
- if (strlen($rdata) < 4) {
- // We shouldn't arrive here, because that implies
- // numexcept does not match the amount of data
- // which is available for the exceptions.
- return $ret;
- }
- $data = unpack("Vbasedate", $rdata);
- $rdata = substr($rdata, 4);
- $exc_base_dates[] = $this->recurDataToUnixData($data["basedate"]);
- }
- if (strlen($rdata) < 4) {
- return $ret;
- }
- $data = unpack("Vnumexceptmod", $rdata);
- $rdata = substr($rdata, 4);
- $ret["numexceptmod"] = $data["numexceptmod"];
- // exc_changed are the base dates of *modified* occurrences. exactly what is modified
- // is in the attachments *and* in the data further down this function.
- $exc_changed = array();
- for($i = 0; $i < $ret["numexceptmod"]; $i++)
- {
- if (strlen($rdata) < 4) {
- // We shouldn't arrive here, because that implies
- // numexceptmod does not match the amount of data
- // which is available for the exceptions.
- return $ret;
- }
- $data = unpack("Vstartdate", $rdata);
- $rdata = substr($rdata, 4);
- $exc_changed[] = $this->recurDataToUnixData($data["startdate"]);
- }
- if (strlen($rdata) < 8) {
- return $ret;
- }
- $data = unpack("Vstart/Vend", $rdata);
- $rdata = substr($rdata, 8);
- $ret["start"] = $this->recurDataToUnixData($data["start"]);
- $ret["end"] = $this->recurDataToUnixData($data["end"]);
- // this is where task recurrence stop
- if (strlen($rdata) < 16) {
- return $ret;
- }
- $data = unpack("Vreaderversion/Vwriterversion/Vstartmin/Vendmin", $rdata);
- $rdata = substr($rdata, 16);
- $ret["startocc"] = $data["startmin"];
- $ret["endocc"] = $data["endmin"];
- $readerversion = $data["readerversion"];
- $writerversion = $data["writerversion"];
- $data = unpack("vnumber", $rdata);
- $rdata = substr($rdata, 2);
- $nexceptions = $data["number"];
- $exc_changed_details = array();
- // Parse n modified exceptions
- for($i=0;$i<$nexceptions;$i++)
- {
- $item = array();
- // Get exception startdate, enddate and basedate (the date at which the occurrence would have started)
- $data = unpack("Vstartdate/Venddate/Vbasedate", $rdata);
- $rdata = substr($rdata, 12);
- // Convert recurtimestamp to unix timestamp
- $startdate = $this->recurDataToUnixData($data["startdate"]);
- $enddate = $this->recurDataToUnixData($data["enddate"]);
- $basedate = $this->recurDataToUnixData($data["basedate"]);
- // Set the right properties
- $item["basedate"] = $this->dayStartOf($basedate);
- $item["start"] = $startdate;
- $item["end"] = $enddate;
- $data = unpack("vbitmask", $rdata);
- $rdata = substr($rdata, 2);
- $item["bitmask"] = $data["bitmask"]; // save bitmask for extended exceptions
- // Bitmask to verify what properties are changed
- $bitmask = $data["bitmask"];
- // ARO_SUBJECT: 0x0001
- // Look for field: SubjectLength (2b), SubjectLength2 (2b) and Subject
- if(($bitmask &(1 << 0))) {
- $data = unpack("vnull_length/vlength", $rdata);
- $rdata = substr($rdata, 4);
- $length = $data["length"];
- $item["subject"] = ""; // Normalized subject
- for($j = 0; $j < $length && strlen($rdata); $j++)
- {
- $data = unpack("Cchar", $rdata);
- $rdata = substr($rdata, 1);
- $item["subject"] .= chr($data["char"]);
- }
- }
- // ARO_MEETINGTYPE: 0x0002
- if(($bitmask &(1 << 1))) {
- $rdata = substr($rdata, 4);
- // Attendees modified: no data here (only in attachment)
- }
- // ARO_REMINDERDELTA: 0x0004
- // Look for field: ReminderDelta (4b)
- if(($bitmask &(1 << 2))) {
- $data = unpack("Vremind_before", $rdata);
- $rdata = substr($rdata, 4);
- $item["remind_before"] = $data["remind_before"];
- }
- // ARO_REMINDER: 0x0008
- // Look field: ReminderSet (4b)
- if(($bitmask &(1 << 3))) {
- $data = unpack("Vreminder_set", $rdata);
- $rdata = substr($rdata, 4);
- $item["reminder_set"] = $data["reminder_set"];
- }
- // ARO_LOCATION: 0x0010
- // Look for fields: LocationLength (2b), LocationLength2 (2b) and Location
- // Similar to ARO_SUBJECT above.
- if(($bitmask &(1 << 4))) {
- $data = unpack("vnull_length/vlength", $rdata);
- $rdata = substr($rdata, 4);
- $item["location"] = "";
- $length = $data["length"];
- $data = substr($rdata, 0, $length);
- $rdata = substr($rdata, $length);
- $item["location"] .= $data;
- }
- // ARO_BUSYSTATUS: 0x0020
- // Look for field: BusyStatus (4b)
- if(($bitmask &(1 << 5))) {
- $data = unpack("Vbusystatus", $rdata);
- $rdata = substr($rdata, 4);
- $item["busystatus"] = $data["busystatus"];
- }
- // ARO_ATTACHMENT: 0x0040
- if(($bitmask &(1 << 6))) {
- // no data: RESERVED
- $rdata = substr($rdata, 4);
- }
- // ARO_SUBTYPE: 0x0080
- // Look for field: SubType (4b). Determines whether it is an allday event.
- if(($bitmask &(1 << 7))) {
- $data = unpack("Vallday", $rdata);
- $rdata = substr($rdata, 4);
- $item["alldayevent"] = $data["allday"];
- }
- // ARO_APPTCOLOR: 0x0100
- // Look for field: AppointmentColor (4b)
- if(($bitmask &(1 << 8))) {
- $data = unpack("Vlabel", $rdata);
- $rdata = substr($rdata, 4);
- $item["label"] = $data["label"];
- }
- // ARO_EXCEPTIONAL_BODY: 0x0200
- if(($bitmask &(1 << 9))) {
- // Notes or Attachments modified: no data here (only in attachment)
- }
- array_push($exc_changed_details, $item);
- }
- /**
- * We now have $exc_changed, $exc_base_dates and $exc_changed_details
- * We will ignore $exc_changed, as this information is available in $exc_changed_details
- * also. If an item is in $exc_base_dates and NOT in $exc_changed_details, then the item
- * has been deleted.
- */
- // Find deleted occurrences
- $deleted_occurences = array();
- foreach($exc_base_dates as $base_date) {
- $found = false;
- foreach($exc_changed_details as $details) {
- if($details["basedate"] == $base_date) {
- $found = true;
- break;
- }
- }
- if(! $found) {
- // item was not in exc_changed_details, so it must be deleted
- $deleted_occurences[] = $base_date;
- }
- }
- $ret["deleted_occurences"] = $deleted_occurences;
- $ret["changed_occurences"] = $exc_changed_details;
- // enough data for normal exception (no extended data)
- if (strlen($rdata) < 16) {
- return $ret;
- }
- $data = unpack("Vreservedsize", $rdata);
- $rdata = substr($rdata, 4 + $data["reservedsize"]);
- for($i=0;$i<$nexceptions;$i++)
- {
- // subject and location in ucs-2 to utf-8
- if ($writerversion >= 0x3009) {
- $data = unpack("Vsize/Vvalue", $rdata); // size includes sizeof(value)==4
- $rdata = substr($rdata, 4 + $data["size"]);
- }
- $data = unpack("Vreservedsize", $rdata);
- $rdata = substr($rdata, 4 + $data["reservedsize"]);
- // ARO_SUBJECT(0x01) | ARO_LOCATION(0x10)
- if ($exc_changed_details[$i]["bitmask"] & 0x11) {
- $data = unpack("Vstart/Vend/Vorig", $rdata);
- $rdata = substr($rdata, 4 * 3);
- $exc_changed_details[$i]["ex_start_datetime"] = $data["start"];
- $exc_changed_details[$i]["ex_end_datetime"] = $data["end"];
- $exc_changed_details[$i]["ex_orig_date"] = $data["orig"];
- }
- // ARO_SUBJECT
- if ($exc_changed_details[$i]["bitmask"] & 0x01) {
- // decode ucs2 string to utf-8
- $data = unpack("vlength", $rdata);
- $rdata = substr($rdata, 2);
- $length = $data["length"];
- $data = substr($rdata, 0, $length * 2);
- $rdata = substr($rdata, $length * 2);
- $subject = iconv("UCS-2LE", "UTF-8", $data);
- // replace subject with unicode subject
- $exc_changed_details[$i]["subject"] = $subject;
- }
- // ARO_LOCATION
- if ($exc_changed_details[$i]["bitmask"] & 0x10) {
- // decode ucs2 string to utf-8
- $data = unpack("vlength", $rdata);
- $rdata = substr($rdata, 2);
- $length = $data["length"];
- $data = substr($rdata, 0, $length * 2);
- $rdata = substr($rdata, $length * 2);
- $location = iconv("UCS-2LE", "UTF-8", $data);
- // replace subject with unicode subject
- $exc_changed_details[$i]["location"] = $location;
- }
- // ARO_SUBJECT(0x01) | ARO_LOCATION(0x10)
- if ($exc_changed_details[$i]["bitmask"] & 0x11) {
- $data = unpack("Vreservedsize", $rdata);
- $rdata = substr($rdata, 4 + $data["reservedsize"]);
- }
- }
- // update with extended data
- $ret["changed_occurences"] = $exc_changed_details;
- return $ret;
- }
- /**
- * Saves the recurrence data to the recurrence property
- * @param array $properties the recurrence data.
- * @return string binary string
- */
- function saveRecurrence()
- {
- // Only save if a message was passed
- if(!isset($this->message))
- return;
- // Abort if no recurrence was set
- if(!isset($this->recur["type"]) && !isset($this->recur["subtype"])) {
- return;
- }
- if(!isset($this->recur["start"]) && !isset($this->recur["end"])) {
- return;
- }
- if(!isset($this->recur["startocc"]) && !isset($this->recur["endocc"])) {
- return;
- }
- $rdata = pack("CCCCCCV", 0x04, 0x30, 0x04, 0x30, (int) $this->recur["type"], 0x20, (int) $this->recur["subtype"]);
- $weekstart = 1; //monday
- $forwardcount = 0;
- $restocc = 0;
- $dayofweek = (int) gmdate("w", (int) $this->recur["start"]); //0 (for Sunday) through 6 (for Saturday)
- $term = (int) $this->recur["type"];
- switch($term)
- {
- case 0x0A:
- // Daily
- if(!isset($this->recur["everyn"])) {
- return;
- }
- if($this->recur["subtype"] == 1) {
- // Daily every workday
- $rdata .= pack("VVVV", (6 * 24 * 60), 1, 0, 0x3E);
- } else {
- // Daily every N days (everyN in minutes)
- $everyn = ((int) $this->recur["everyn"]) / 1440;
- // Calc first occ
- $firstocc = $this->unixDataToRecurData($this->recur["start"]) % ((int) $this->recur["everyn"]);
- $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], $this->recur["regen"] ? 1 : 0);
- }
- break;
- case 0x0B:
- // Weekly
- if(!isset($this->recur["everyn"])) {
- return;
- }
- if (!$this->recur["regen"] && !isset($this->recur["weekdays"])) {
- return;
- }
- // No need to calculate startdate if sliding flag was set.
- if (!$this->recur['regen']) {
- // Calculate start date of recurrence
- // Find the first day that matches one of the weekdays selected
- $daycount = 0;
- $dayskip = -1;
- for($j = 0; $j < 7; $j++) {
- if(((int) $this->recur["weekdays"]) & (1<<( ($dayofweek+$j)%7)) ) {
- if($dayskip == -1)
- $dayskip = $j;
- $daycount++;
- }
- }
- // $dayskip is the number of days to skip from the startdate until the first occurrence
- // $daycount is the number of days per week that an occurrence occurs
- $weekskip = 0;
- if(($dayofweek < $weekstart && $dayskip > 0) || ($dayofweek+$dayskip) > 6)
- $weekskip = 1;
- // Check if the recurrence ends after a number of occurences, in that case we must calculate the
- // remaining occurences based on the start of the recurrence.
- if (((int) $this->recur["term"]) == 0x22) {
- // $weekskip is the amount of weeks to skip from the startdate before the first occurence
- // $forwardcount is the maximum number of week occurrences we can go ahead after the first occurrence that
- // is still inside the recurrence. We subtract one to make sure that the last week is never forwarded over
- // (eg when numoccur = 2, and daycount = 1)
- $forwardcount = floor( (int) ($this->recur["numoccur"] -1 ) / $daycount);
- // $restocc is the number of occurrences left after $forwardcount whole weeks of occurrences, minus one
- // for the occurrence on the first day
- $restocc = ((int) $this->recur["numoccur"]) - ($forwardcount*$daycount) - 1;
- // $forwardcount is now the number of weeks we can go forward and still be inside the recurrence
- $forwardcount *= (int) $this->recur["everyn"];
- }
- // The real start is start + dayskip + weekskip-1 (since dayskip will already bring us into the next week)
- $this->recur["start"] = ((int) $this->recur["start"]) + ($dayskip * 24*60*60)+ ($weekskip *(((int) $this->recur["everyn"]) - 1) * 7 * 24*60*60);
- }
- // Calc first occ
- $firstocc = ($this->unixDataToRecurData($this->recur["start"]) ) % ( ((int) $this->recur["everyn"]) * 7 * 24 * 60);
- $firstocc -= (((int) gmdate("w", (int) $this->recur["start"])) - 1) * 24 * 60;
- if ($this->recur["regen"])
- $rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], 1);
- else
- $rdata .= pack("VVVV", $firstocc, (int) $this->recur["everyn"], 0, (int) $this->recur["weekdays"]);
- break;
- case 0x0C:
- // Monthly
- case 0x0D:
- // Yearly
- if(!isset($this->recur["everyn"])) {
- return;
- }
- if($term == 0x0D /*yearly*/ && !isset($this->recur["month"])) {
- return;
- }
- if($term == 0x0C /*monthly*/) {
- $everyn = (int) $this->recur["everyn"];
- }else {
- $everyn = $this->recur["regen"] ? ((int) $this->recur["everyn"]) * 12 : 12;
- }
- // Get montday/month/year of original start
- $curmonthday = gmdate("j", (int) $this->recur["start"] );
- $curyear = gmdate("Y", (int) $this->recur["start"] );
- $curmonth = gmdate("n", (int) $this->recur["start"] );
- // Check if the recurrence ends after a number of occurences, in that case we must calculate the
- // remaining occurences based on the start of the recurrence.
- if (((int) $this->recur["term"]) == 0x22) {
- // $forwardcount is the number of occurrences we can skip and still be inside the recurrence range (minus
- // one to make sure there are always at least one occurrence left)
- $forwardcount = ((((int) $this->recur["numoccur"])-1) * $everyn );
- }
- // Get month for yearly on D'th day of month M
- if($term == 0x0D /*yearly*/) {
- $selmonth = floor(((int) $this->recur["month"]) / (24 * 60 *29)) + 1; // 1=jan, 2=feb, eg
- }
- switch((int) $this->recur["subtype"])
- {
- // on D day of every M month
- case 2:
- if(!isset($this->recur["monthday"])) {
- return;
- }
- // Recalc startdate
- // Set on the right begin day
- // Go the beginning of the month
- $this->recur["start"] -= ($curmonthday-1) * 24*60*60;
- // Go the the correct month day
- $this->recur["start"] += (((int) $this->recur["monthday"])-1) * 24*60*60;
- // If the previous calculation gave us a start date *before* the original start date, then we need to skip to the next occurrence
- if ( ($term == 0x0C /*monthly*/ && ((int) $this->recur["monthday"]) < $curmonthday) ||
- ($term == 0x0D /*yearly*/ &&( $selmonth < $curmonth || ($selmonth == $curmonth && ((int) $this->recur["monthday"]) < $curmonthday)) ))
- {
- if($term == 0x0D /*yearly*/)
- $count = ($everyn - ($curmonth - $selmonth)); // Yearly, go to next occurrence in 'everyn' months minus difference in first occurence and original date
- else
- $count = $everyn; // Monthly, go to next occurrence in 'everyn' months
- // Forward by $count months. This is done by getting the number of days in that month and forwarding that many days
- for($i=0; $i < $count; $i++) {
- $this->recur["start"] += $this->getMonthInSeconds($curyear, $curmonth);
- if($curmonth == 12) {
- $curyear++;
- $curmonth = 0;
- }
- $curmonth++;
- }
- }
- // "start" is now pointing to the first occurrence, except that it will overshoot if the
- // month in which it occurs has less days than specified as the day of the month. So 31st
- // of each month will overshoot in february (29 days). We compensate for that by checking
- // if the day of the month we got is wrong, and then back up to the last day of the previous
- // month.
- if(((int) $this->recur["monthday"]) >=28 && ((int) $this->recur["monthday"]) <= 31 &&
- gmdate("j", ((int) $this->recur["start"])) < ((int) $this->recur["monthday"]))
- {
- $this->recur["start"] -= gmdate("j", ((int) $this->recur["start"])) * 24 * 60 *60;
- }
- // "start" is now the first occurrence
- if($term == 0x0C /*monthly*/) {
- // Calc first occ
- $monthIndex = ((((12%$everyn) * ((((int) gmdate("Y", $this->recur["start"])) - 1601)%$everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1))%$everyn;
- $firstocc = 0;
- for($i=0; $i < $monthIndex; $i++) {
- $firstocc+= $this->getMonthInSeconds(1601 + floor($i/12), ($i%12)+1) / 60;
- }
- $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
- } else{
- // Calc first occ
- $firstocc = 0;
- $monthIndex = (int) gmdate("n", $this->recur["start"]);
- for($i=1; $i < $monthIndex; $i++) {
- $firstocc+= $this->getMonthInSeconds(1601 + floor($i/12), $i) / 60;
- }
- $rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
- }
- break;
- case 3:
- // monthly: on Nth weekday of every M month
- // yearly: on Nth weekday of M month
- if(!isset($this->recur["weekdays"]) && !isset($this->recur["nday"])) {
- return;
- }
- $weekdays = (int) $this->recur["weekdays"];
- $nday = (int) $this->recur["nday"];
- // Calc startdate
- $monthbegindow = (int) $this->recur["start"];
- if($nday == 5) {
- // Set date on the last day of the last month
- $monthbegindow += (gmdate("t", $monthbegindow ) - gmdate("j", $monthbegindow )) * 24 * 60 * 60;
- }else {
- // Set on the first day of the month
- $monthbegindow -= ((gmdate("j", $monthbegindow )-1) * 24 * 60 * 60);
- }
- if($term == 0x0D /*yearly*/) {
- // Set on right month
- if($selmonth < $curmonth)
- $tmp = 12 - $curmonth + $selmonth;
- else
- $tmp = ($selmonth - $curmonth);
- for($i=0; $i < $tmp; $i++) {
- $monthbegindow += $this->getMonthInSeconds($curyear, $curmonth);
- if($curmonth == 12) {
- $curyear++;
- $curmonth = 0;
- }
- $curmonth++;
- }
- }else {
- // Check or you exist in the right month
- for($i = 0; $i < 7; $i++) {
- if($nday == 5 && (1<<( (gmdate("w", $monthbegindow)-$i)%7) ) & $weekdays) {
- $day = gmdate("j", $monthbegindow) - $i;
- break;
- }else if($nday != 5 && (1<<( (gmdate("w", $monthbegindow )+$i)%7) ) & $weekdays) {
- $day = (($nday-1)*7) + ($i+1);
- break;
- }
- }
- // Goto the next X month
- if(isset($day) && ($day < gmdate("j", (int) $this->recur["start"])) ) {
- if($nday == 5) {
- $monthbegindow += 24 * 60 * 60;
- if($curmonth == 12) {
- $curyear++;
- $curmonth = 0;
- }
- $curmonth++;
- }
- for($i=0; $i < $everyn; $i++) {
- $monthbegindow += $this->getMonthInSeconds($curyear, $curmonth);
- if($curmonth == 12) {
- $curyear++;
- $curmonth = 0;
- }
- $curmonth++;
- }
- if($nday == 5) {
- $monthbegindow -= 24 * 60 * 60;
- }
- }
- }
- //FIXME: weekstart?
- $day = 0;
- // Set start on the right day
- for($i = 0; $i < 7; $i++) {
- if($nday == 5 && (1<<( (gmdate("w", $monthbegindow )-$i)%7) ) & $weekdays) {
- $day = $i;
- break;
- }else if($nday != 5 && (1<<( (gmdate("w", $monthbegindow )+$i)%7) ) & $weekdays) {
- $day = ($nday - 1) * 7 + ($i+1);
- break;
- }
- }
- if($nday == 5)
- $monthbegindow -= $day * 24 * 60 *60;
- else
- $monthbegindow += ($day-1) * 24 * 60 *60;
- $firstocc = 0;
- if($term == 0x0C /*monthly*/) {
- // Calc first occ
- $monthIndex = ((((12%$everyn) * (((int) gmdate("Y", $this->recur["start"]) - 1601)%$everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1))%$everyn;
- for($i=0; $i < $monthIndex; $i++) {
- $firstocc+= $this->getMonthInSeconds(1601 + floor($i/12), ($i%12)+1) / 60;
- }
- $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday);
- } else {
- // Calc first occ
- $monthIndex = (int) gmdate("n", $this->recur["start"]);
- for($i=1; $i < $monthIndex; $i++) {
- $firstocc+= $this->getMonthInSeconds(1601 + floor($i/12), $i) / 60;
- }
- $rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday);
- }
- break;
- }
- break;
- }
- if(!isset($this->recur["term"])) {
- return;
- }
- // Terminate
- $term = (int) $this->recur["term"];
- $rdata .= pack("CCCC", $term, 0x20, 0x00, 0x00);
- switch($term)
- {
- // After the given enddate
- case 0x21:
- $rdata .= pack("V", 10);
- break;
- // After a number of times
- case 0x22:
- if(!isset($this->recur["numoccur"])) {
- return;
- }
- $rdata .= pack("V", (int) $this->recur["numoccur"]);
- break;
- // Never ends
- case 0x23:
- $rdata .= pack("V", 0);
- break;
- }
- // Strange little thing for the recurrence type "every workday"
- if(((int) $this->recur["type"]) == 0x0B && ((int) $this->recur["subtype"]) == 1) {
- $rdata .= pack("V", 1);
- } else { // Other recurrences
- $rdata .= pack("V", 0);
- }
- // Exception data
- // Get all exceptions
- $deleted_items = $this->recur["deleted_occurences"];
- $changed_items = $this->recur["changed_occurences"];
- // Merge deleted and changed items into one list
- $items = $deleted_items;
- foreach($changed_items as $changed_item)
- array_push($items, $changed_item["basedate"]);
- sort($items);
- // Add the merged list in to the rdata
- $rdata .= pack("V", count($items));
- foreach($items as $item)
- $rdata .= pack("V", $this->unixDataToRecurData($item));
- // Loop through the changed exceptions (not deleted)
- $rdata .= pack("V", count($changed_items));
- $items = array();
- foreach($changed_items as $changed_item)
- {
- $items[] = $this->dayStartOf($changed_item["start"]);
- }
- sort($items);
- // Add the changed items list int the rdata
- foreach($items as $item)
- $rdata .= pack("V", $this->unixDataToRecurData($item));
- // Set start date
- $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["start"]));
- // Set enddate
- switch($term)
- {
- // After the given enddate
- case 0x21:
- $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]));
- break;
- // After a number of times
- case 0x22:
- // @todo: calculate enddate with intval($this->recur["startocc"]) + intval($this->recur["duration"]) > 24 hour
- $occenddate = (int) $this->recur["start"];
- switch((int) $this->recur["type"]) {
- case 0x0A: //daily
- if($this->recur["subtype"] == 1) {
- // Daily every workday
- $restocc = (int) $this->recur["numoccur"];
- // Get starting weekday
- $nowtime = $this->gmtime($occenddate);
- $j = $nowtime["tm_wday"];
- while(1)
- {
- if(($j%7) > 0 && ($j%7)<6 ) {
- $restocc--;
- }
- $j++;
- if($restocc <= 0)
- break;
- $occenddate += 24*60*60;
- }
- } else {
- // -1 because the first day already counts (from 1-1-1980 to 1-1-1980 is 1 occurrence)
- $occenddate += (((int) $this->recur["everyn"]) * 60 * (((int) $this->recur["numoccur"]-1)));
- }
- break;
- case 0x0B: //weekly
- // Needed values
- // $forwardcount - number of weeks we can skip forward
- // $restocc - number of remaning occurrences after the week skip
- // Add the weeks till the last item
- $occenddate+=($forwardcount*7*24*60*60);
- $dayofweek = gmdate("w", $occenddate);
- // Loop through the last occurrences until we have had them all
- for($j = 1; $restocc>0; $j++)
- {
- // Jump to the next week (which may be N weeks away) when going over the week boundary
- if((($dayofweek+$j)%7) == $weekstart)
- $occenddate += (((int) $this->recur["everyn"])-1) * 7 * 24*60*60;
- // If this is a matching day, once less occurrence to process
- if(((int) $this->recur["weekdays"]) & (1<<(($dayofweek+$j)%7)) ) {
- $restocc--;
- }
- // Next day
- $occenddate += 24*60*60;
- }
- break;
- case 0x0C: //monthly
- case 0x0D: //yearly
- $curyear = gmdate("Y", (int) $this->recur["start"] );
- $curmonth = gmdate("n", (int) $this->recur["start"] );
- // $forwardcount = months
- switch((int) $this->recur["subtype"])
- {
- case 2: // on D day of every M month
- while($forwardcount > 0)
- {
- $occenddate += $this->getMonthInSeconds($curyear, $curmonth);
- if($curmonth >=12) {
- $curmonth = 1;
- $curyear++;
- } else {
- $curmonth++;
- }
- $forwardcount--;
- }
- // compensation between 28 and 31
- if(((int) $this->recur["monthday"]) >=28 && ((int) $this->recur["monthday"]) <= 31 &&
- gmdate("j", $occenddate) < ((int) $this->recur["monthday"]))
- {
- if(gmdate("j", $occenddate) < 28)
- $occenddate -= gmdate("j", $occenddate) * 24 * 60 *60;
- else
- $occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 *60;
- }
- break;
- case 3: // on Nth weekday of every M month
- $nday = (int) $this->recur["nday"]; //1 tot 5
- $weekdays = (int) $this->recur["weekdays"];
- while($forwardcount > 0)
- {
- $occenddate += $this->getMonthInSeconds($curyear, $curmonth);
- if($curmonth >=12) {
- $curmonth = 1;
- $curyear++;
- } else {
- $curmonth++;
- }
- $forwardcount--;
- }
- if($nday == 5) {
- // Set date on the last day of the last month
- $occenddate += (gmdate("t", $occenddate ) - gmdate("j", $occenddate )) * 24 * 60 * 60;
- …
Large files files are truncated, but you can click here to view the full file