Back to index

d-push  2.0
Public Member Functions | Public Attributes
BaseRecurrence Class Reference

BaseRecurrence this class is superclass for recurrence for appointments and tasks. More...

Inheritance diagram for BaseRecurrence:
Inheritance graph
[legend]

List of all members.

Public Member Functions

 BaseRecurrence ($store, $message)
 Constructor.
 getRecurrence ()
 getFullRecurrenceBlob ()
 parseRecurrence ($rdata)
 Function for parsing the Recurrence value of a Calendar item.
 saveRecurrence ()
 Saves the recurrence data to the recurrence property.
 recurDataToUnixData ($rdate)
 Function which converts a recurrence date timestamp to an unix date timestamp.
 unixDataToRecurData ($date)
 Function which converts an unix date timestamp to recurrence date timestamp.
 GetTZOffset ($ts)
 gmtime() doesn't exist in standard PHP, so we have to implement it ourselves
 gmtime ($time)
 gmtime() doesn't exist in standard PHP, so we have to implement it ourselves
 isLeapYear ($year)
 getMonthInSeconds ($year, $month)
 getDateByYearMonthWeekDayHour ($year, $month, $week, $day, $hour)
 Function to get a date by Year Nr, Month Nr, Week Nr, Day Nr, and hour.
 getTimezone ($tz, $date)
 getTimezone gives the timezone offset (in minutes) of the given local date/time according to the given TZ info
 getWeekNr ($date)
 getWeekNr() returns the week nr of the month (ie first week of february is 1)
 parseTimezone ($data)
 parseTimezone parses the timezone as specified in named property 0x8233 in Outlook calendar messages.
 getTimezoneData ($tz)
 createTimezone ($tz)
 createTimezone creates the timezone as specified in the named property 0x8233 see also parseTimezone() $tz is an array with the timezone data
 toGMT ($tz, $date)
 toGMT returns a timestamp in GMT time for the time and timezone given
 fromGMT ($tz, $date)
 fromGMT returns a timestamp in the local timezone given from the GMT time given
 dayStartOf ($date)
 Function to get timestamp of the beginning of the day of the timestamp given.
 monthStartOf ($date)
 Function to get timestamp of the beginning of the month of the timestamp given.
 yearStartOf ($date)
 Function to get timestamp of the beginning of the year of the timestamp given.
 getItems ($start, $end, $limit=0, $remindersonly=false)
 Function which returns the items in a given interval.
 sortStarttime ($a, $b)
 daysInMonth ($date, $months)
 daysInMonth
 monthOfYear ($minutes)
 sortExceptionStart ($a, $b)

Public Attributes

 $store
 $message
 $messageprops
 $proptags
 $recur
 $tz

Detailed Description

BaseRecurrence this class is superclass for recurrence for appointments and tasks.

This class provides all basic features of recurrence.

Definition at line 56 of file class.baserecurrence.php.


Member Function Documentation

BaseRecurrence::BaseRecurrence ( store,
message 
)

Constructor.

Parameters:
resource$storeMAPI Message Store Object
resource$messagethe MAPI (appointment) message
array$propertiesthe list of MAPI properties the message has.

Definition at line 94 of file class.baserecurrence.php.

        {
            $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"]]);
            }
        }

Here is the call graph for this function:

Here is the caller graph for this function:

createTimezone creates the timezone as specified in the named property 0x8233 see also parseTimezone() $tz is an array with the timezone data

Definition at line 1570 of file class.baserecurrence.php.

        {
            $data = pack("lxxxxlxxxxlvvxxxxxxxxxxlvvxxxxxx",
                        $tz["timezone"],
                        array_key_exists("timezonedst",$tz)?$tz["timezonedst"]:0,
                        array_key_exists("dstendmonth",$tz)?$tz["dstendmonth"]:0,
                        array_key_exists("dstendweek",$tz)?$tz["dstendweek"]:0,
                        array_key_exists("dstendhour",$tz)?$tz["dstendhour"]:0,
                        array_key_exists("dststartmonth",$tz)?$tz["dststartmonth"]:0,
                        array_key_exists("dststartweek",$tz)?$tz["dststartweek"]:0,
                        array_key_exists("dststarthour",$tz)?$tz["dststarthour"]:0
                    );

            return $data;
        }
BaseRecurrence::daysInMonth ( date,
months 
)

daysInMonth

Returns the number of days in the upcoming number of months. If you specify 1 month as $months it will give you the number of days in the month of $date. If you specify more it will also count the days in the upcomming months and add that to the number of days. So if you have a date in march and you specify $months as 2 it will return 61.

Parameters:
Integer$dateSpecified date as timestamp from which you want to know the number of days in the month.
Integer$monthsNumber of months you want to know the number of days in.
Returns:
Integer Number of days in the specified amount of months.

Definition at line 1919 of file class.baserecurrence.php.

                                             {
            $days = 0;

            for($i=0;$i<$months;$i++) {
                $days += date("t", $date + $days * 24 * 60 * 60);
            }

            return $days;
        }

Here is the caller graph for this function:

Function to get timestamp of the beginning of the day of the timestamp given.

Parameters:
date$date
Returns:
date timestamp referring to same day but at 00:00:00

Definition at line 1611 of file class.baserecurrence.php.

        {
            $time1 = $this->gmtime($date);

            return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, $time1["tm_mday"], $time1["tm_year"] + 1900);
        }

Here is the call graph for this function:

Here is the caller graph for this function:

BaseRecurrence::fromGMT ( tz,
date 
)

fromGMT returns a timestamp in the local timezone given from the GMT time given

Definition at line 1600 of file class.baserecurrence.php.

                                     {
            $offset = $this->getTimezone($tz, $date);

            return $date - $offset * 60;
        }

Here is the call graph for this function:

Here is the caller graph for this function:

BaseRecurrence::getDateByYearMonthWeekDayHour ( year,
month,
week,
day,
hour 
)

Function to get a date by Year Nr, Month Nr, Week Nr, Day Nr, and hour.

Parameters:
int$year
int$month
int$week
int$day
int$hour
Returns:
returns the timestamp of the given date, timezone-independant

Definition at line 1474 of file class.baserecurrence.php.

        {
            // get first day of month
            $date = gmmktime(0,0,0,$month,0,$year + 1900);

            // get wday info
            $gmdate = $this->gmtime($date);

            $date -= $gmdate["tm_wday"] * 24 * 60 * 60; // back up to start of week

            $date += $week * 7 * 24 * 60 * 60; // go to correct week nr
            $date += $day * 24 * 60 * 60;
            $date += $hour * 60 * 60;

            $gmdate = $this->gmtime($date);

            // if we are in the next month, then back up a week, because week '5' means
            // 'last week of month'

            if($gmdate["tm_mon"]+1 != $month)
                $date -= 7 * 24 * 60 * 60;

            return $date;
        }

Here is the call graph for this function:

Here is the caller graph for this function:

Definition at line 123 of file class.baserecurrence.php.

        {
            $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;
            }
        }

Here is the caller graph for this function:

BaseRecurrence::getItems ( start,
end,
limit = 0,
remindersonly = false 
)

Function which returns the items in a given interval.

This included expansion of the recurrence and processing of exceptions (modified and deleted).

Parameters:
string$entryidthe entryid of the message
array$propsthe properties of the message
date$startstart time of the interval (GMT)
date$endend time of the interval (GMT)

checks weather the next coming day in recurrence pattern is less than or equal to end day of the * recurring item.Also check weather the coming day in recurrence pattern is greater than or equal to start * of recurring pattern, so that appointment that fall under the recurrence range are only displayed.

Definition at line 1652 of file class.baserecurrence.php.

        {
            $items = array();

            if(isset($this->recur)) {

                // Optimization: remindersonly and default reminder is off; since only exceptions with reminder set will match, just look which
                // exceptions are in range and have a reminder set
                if($remindersonly && (!isset($this->messageprops[$this->proptags["reminder"]]) || $this->messageprops[$this->proptags["reminder"]] == false)) {
                    // Sort exceptions by start time
                    uasort($this->recur["changed_occurences"], array($this, "sortExceptionStart"));

                    // Loop through all changed exceptions
                    foreach($this->recur["changed_occurences"] as $exception) {
                        // Check reminder set
                        if(!isset($exception["reminder"]) || $exception["reminder"] == false)
                            continue;

                        // Convert to GMT
                        $occstart = $this->toGMT($this->tz, $exception["start"]); // seb changed $tz to $this->tz
                        $occend = $this->toGMT($this->tz, $exception["end"]); // seb changed $tz to $this->tz

                        // Check range criterium
                        if($occstart > $end || $occend < $start)
                            continue;

                        // OK, add to items.
                        array_push($items, $this->getExceptionProperties($exception));
                        if($limit && (count($items) == $limit))
                            break;
                    }

                    uasort($items, array($this, "sortStarttime"));

                    return $items;
                }

                // From here on, the dates of the occurrences are calculated in local time, so the days we're looking
                // at are calculated from the local time dates of $start and $end

                if ($this->recur['regen'] && isset($this->action['datecompleted'])) {
                    $daystart = $this->dayStartOf($this->action['datecompleted']);
                } else {
                    $daystart = $this->dayStartOf($this->recur["start"]); // start on first day of occurrence
                }

                // Calculate the last day on which we want to be looking at a recurrence; this is either the end of the view
                // or the end of the recurrence, whichever comes first
                if($end > $this->toGMT($this->tz, $this->recur["end"])) {
                    $rangeend = $this->toGMT($this->tz, $this->recur["end"]);
                } else {
                    $rangeend = $end;
                }

                $dayend = $this->dayStartOf($this->fromGMT($this->tz, $rangeend));

                // Loop through the entire recurrence range of dates, and check for each occurrence whether it is in the view range.

                switch($this->recur["type"])
                {
                case 10:
                    // Daily
                    if($this->recur["everyn"] <= 0)
                        $this->recur["everyn"] = 1440;

                    if($this->recur["subtype"] == 0) {
                        // Every Nth day
                        for($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * $this->recur["everyn"]) {
                            $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
                        }
                    } else {
                        // Every workday
                        for($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * 1440)
                        {
                            $nowtime = $this->gmtime($now);
                            if ($nowtime["tm_wday"] > 0 && $nowtime["tm_wday"] < 6) { // only add items in the given timespace
                                $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
                            }
                        }
                    }
                    break;
                case 11:
                    // Weekly
                    if($this->recur["everyn"] <= 0)
                        $this->recur["everyn"] = 1;

                    // If sliding flag is set then move to 'n' weeks
                    if ($this->recur['regen']) $daystart += (60 * 60 * 24 * 7 * $this->recur["everyn"]);

                    for($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += (60 * 60 * 24 * 7 * $this->recur["everyn"]))
                    {
                        if ($this->recur['regen']) {
                            $this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
                        } else {
                            // Loop through the whole following week to the first occurrence of the week, add each day that is specified
                            for($wday = 0; $wday < 7; $wday++)
                            {
                                $daynow = $now + $wday * 60 * 60 * 24;
                                //checks weather the next coming day in recurring pattern is less than or equal to end day of the recurring item
                                if ($daynow <= $dayend){
                                    $nowtime = $this->gmtime($daynow); // Get the weekday of the current day
                                    if(($this->recur["weekdays"] &(1 << $nowtime["tm_wday"]))) { // Selected ?
                                        $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
                                    }
                                }
                            }
                        }
                    }
                    break;
                case 12:
                    // Monthly
                    if($this->recur["everyn"] <= 0)
                        $this->recur["everyn"] = 1;

                    // Loop through all months from start to end of occurrence, starting at beginning of first month
                    for($now = $this->monthStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60 )
                    {
                        if(isset($this->recur["monthday"]) &&($this->recur['monthday'] != "undefined") && !$this->recur['regen']) { // Day M of every N months
                            $difference = 1;
                            if ($this->daysInMonth($now, $this->recur["everyn"]) < $this->recur["monthday"]) {
                                $difference = $this->recur["monthday"] - $this->daysInMonth($now, $this->recur["everyn"]) + 1;
                            }
                            $daynow = $now + (($this->recur["monthday"] - $difference) * 24 * 60 * 60);
                            //checks weather the next coming day in recurrence pattern is less than or equal to end day of the recurring item
                            if ($daynow <= $dayend){
                                $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
                            }
                        }
                        else if(isset($this->recur["nday"]) && isset($this->recur["weekdays"])) { // Nth [weekday] of every N months
                            // Sanitize input
                            if($this->recur["weekdays"] == 0)
                                $this->recur["weekdays"] = 1;

                            // If nday is not set to the last day in the month
                            if ($this->recur["nday"] < 5) {
                                // keep the track of no. of time correct selection pattern(like 2nd weekday, 4th fiday, etc.)is matched
                                $ndaycounter = 0;
                                // Find matching weekday in this month
                                for($day = 0; $day < $this->daysInMonth($now, 1); $day++)
                                {
                                    $daynow = $now + $day * 60 * 60 * 24;
                                    $nowtime = $this->gmtime($daynow); // Get the weekday of the current day

                                    if($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
                                        $ndaycounter ++;
                                    }
                                    // check the selected pattern is same as asked Nth weekday,If so set the firstday
                                    if($this->recur["nday"] == $ndaycounter){
                                        $firstday = $day;
                                        break;
                                    }
                                }
                                // $firstday is the day of the month on which the asked pattern of nth weekday matches
                                $daynow = $now + $firstday * 60 * 60 * 24;
                            }else{
                                // Find last day in the month ($now is the firstday of the month)
                                $NumDaysInMonth =  $this->daysInMonth($now, 1);
                                $daynow = $now + (($NumDaysInMonth-1) * 24*60*60);

                                $nowtime = $this->gmtime($daynow);
                                while (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"]))==0){
                                    $daynow -= 86400;
                                    $nowtime = $this->gmtime($daynow);
                                }
                            }

                            if ($daynow <= $dayend && $daynow >= $daystart){
                                $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz , $remindersonly);
                            }
                        } else if ($this->recur['regen']) {
                            $next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60);
                            $now = $daystart +($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60);

                            if ($now <= $dayend) {
                                $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
                            }
                        }
                    }
                    break;
                case 13:
                    // Yearly
                    if($this->recur["everyn"] <= 0)
                        $this->recur["everyn"] = 12;

                    for($now = $this->yearStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60 )
                    {
                        if(isset($this->recur["monthday"]) && !$this->recur['regen']) { // same as monthly, but in a specific month
                            // recur["month"] is in minutes since the beginning of the year
                            $month = $this->monthOfYear($this->recur["month"]); // $month is now month of year [0..11]
                            $monthday = $this->recur["monthday"]; // $monthday is day of the month [1..31]
                            $monthstart = $now + $this->daysInMonth($now, $month) * 24 * 60 * 60; // $monthstart is the timestamp of the beginning of the month
                            if($monthday > $this->daysInMonth($monthstart, 1))
                                $monthday = $this->daysInMonth($monthstart, 1);    // Cap $monthday on month length (eg 28 feb instead of 29 feb)
                            $daynow = $monthstart + ($monthday-1) * 24 * 60 * 60;
                            $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
                        }
                        else if(isset($this->recur["nday"]) && isset($this->recur["weekdays"])) { // Nth [weekday] in month X of every N years

                            // Go the correct month
                            $monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60;

                            // Find first matching weekday in this month
                            for($wday = 0; $wday < 7; $wday++)
                            {
                                $daynow = $monthnow + $wday * 60 * 60 * 24;
                                $nowtime = $this->gmtime($daynow); // Get the weekday of the current day

                                if($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
                                    $firstday = $wday;
                                    break;
                                }
                            }

                            // Same as above (monthly)
                            $daynow = $monthnow + ($firstday + ($this->recur["nday"]-1)*7) * 60 * 60 * 24;

                            while($this->monthStartOf($daynow) != $this->monthStartOf($monthnow)) {
                                $daynow -= 7 * 60 * 60 * 24;
                            }

                            $this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
                        } else if ($this->recur['regen']) {
                            $year_starttime = $this->gmtime($now);
                            $is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1);    // +1 next year
                            $now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /*year in seconds*/);

                            if ($now <= $dayend) {
                                $this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
                            }
                        }
                    }
                }
                //to get all exception items
                if (!empty($this->recur['changed_occurences']))
                    $this->processExceptionItems($items, $start, $end);
            }

            // sort items on starttime
            usort($items, array($this, "sortStarttime"));

            // Return the MAPI-compatible list of items for this object
            return $items;
        }

Here is the call graph for this function:

Here is the caller graph for this function:

BaseRecurrence::getMonthInSeconds ( year,
month 
)

Definition at line 1451 of file class.baserecurrence.php.

        {
            if( in_array($month, array(1,3,5,7,8,10,12) ) ) {
                $day = 31;
            } else if( in_array($month, array(4,6,9,11) ) ) {
                $day = 30;
            } else {
                $day = 28;
                if( $this->isLeapYear($year) == 1 )
                    $day++;
            }
            return $day * 24 * 60 * 60;
        }

Here is the call graph for this function:

Here is the caller graph for this function:

Definition at line 118 of file class.baserecurrence.php.

        {
            return $this->recur;
        }
BaseRecurrence::getTimezone ( tz,
date 
)

getTimezone gives the timezone offset (in minutes) of the given local date/time according to the given TZ info

Definition at line 1503 of file class.baserecurrence.php.

        {
            // No timezone -> GMT (+0)
            if(!isset($tz["timezone"]))
                return 0;

            $dst = false;
            $gmdate = $this->gmtime($date);

            $dststart = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dststartmonth"], $tz["dststartweek"], 0, $tz["dststarthour"]);
            $dstend = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dstendmonth"], $tz["dstendweek"], 0, $tz["dstendhour"]);

            if($dststart <= $dstend) {
                // Northern hemisphere, eg DST is during Mar-Oct
                if($date > $dststart && $date < $dstend) {
                    $dst = true;
                }
            } else {
                // Southern hemisphere, eg DST is during Oct-Mar
                if($date < $dstend || $date > $dststart) {
                    $dst = true;
                }
            }

            if($dst) {
                return $tz["timezone"] + $tz["timezonedst"];
            } else {
                return $tz["timezone"];
            }
        }

Here is the call graph for this function:

Here is the caller graph for this function:

Definition at line 1558 of file class.baserecurrence.php.

        {
            $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);

            return $data;
        }

Here is the caller graph for this function:

gmtime() doesn't exist in standard PHP, so we have to implement it ourselves

Author:
Steve Hardy

Definition at line 1420 of file class.baserecurrence.php.

        {
            $Offset = date("O", $ts);

            $Parity = $Offset < 0 ? -1 : 1;
            $Offset = $Parity * $Offset;
            $Offset = ($Offset - ($Offset % 100)) / 100 * 60 + $Offset % 100;

            return $Parity * $Offset;
        }

Here is the caller graph for this function:

getWeekNr() returns the week nr of the month (ie first week of february is 1)

Definition at line 1537 of file class.baserecurrence.php.

        {
            $gmdate = gmtime($date);
            $gmdate["tm_mday"] = 0;
            return strftime("%W", $date) - strftime("%W", gmmktime($gmdate)) + 1;
        }

Here is the call graph for this function:

gmtime() doesn't exist in standard PHP, so we have to implement it ourselves

Author:
Steve Hardy
Parameters:
Date$time
Returns:
Date GMT Time

Definition at line 1437 of file class.baserecurrence.php.

        {
            $TZOffset = $this->GetTZOffset($time);

            $t_time = $time - $TZOffset * 60; #Counter adjust for localtime()
            $t_arr = localtime($t_time, 1);

            return $t_arr;
        }

Here is the call graph for this function:

Here is the caller graph for this function:

Definition at line 1447 of file class.baserecurrence.php.

                                   {
            return ( $year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0) );
        }

Here is the caller graph for this function:

Definition at line 1930 of file class.baserecurrence.php.

                                       {
            $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

            $d += $minutes*60;

            $dtime = $this->gmtime($d);

            return $dtime["tm_mon"];
        }

Here is the call graph for this function:

Here is the caller graph for this function:

Function to get timestamp of the beginning of the month of the timestamp given.

Parameters:
date$date
Returns:
date Timestamp referring to same month but on the first day, and at 00:00:00

Definition at line 1623 of file class.baserecurrence.php.

        {
            $time1 = $this->gmtime($date);

            return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, 1, $time1["tm_year"] + 1900);
        }

Here is the call graph for this function:

Here is the caller graph for this function:

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 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]

Parameters:
string$rdataBinary string
Returns:
array recurrence data.

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.

Definition at line 190 of file class.baserecurrence.php.

        {
            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);
            }

            // 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;
        }

Here is the call graph for this function:

Here is the caller graph for this function:

parseTimezone parses the timezone as specified in named property 0x8233 in Outlook calendar messages.

Returns the timezone in minutes negative offset (GMT +2:00 -> -120)

Definition at line 1549 of file class.baserecurrence.php.

        {
            if(strlen($data) < 48)
                return;

            $tz = unpack("ltimezone/lunk/ltimezonedst/lunk/ldstendmonth/vdstendweek/vdstendhour/lunk/lunk/vunk/ldststartmonth/vdststartweek/vdststarthour/lunk/vunk", $data);
            return $tz;
        }

Here is the caller graph for this function:

Function which converts a recurrence date timestamp to an unix date timestamp.

Author:
Steve Hardy
Parameters:
Int$rdatethe date which will be converted
Returns:
Int the converted date

Definition at line 1400 of file class.baserecurrence.php.

        {
            return ($rdate - 194074560) * 60 ;
        }

Here is the caller graph for this function:

Saves the recurrence data to the recurrence property.

Parameters:
array$propertiesthe recurrence data.
Returns:
string binary string

Definition at line 609 of file class.baserecurrence.php.

        {
            // 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;
                                    }else {
                                        // Set date on the first day of the last month
                                        $occenddate -= (gmdate("j", $occenddate )-1) * 24 * 60 * 60;
                                    }

                                    for($i = 0; $i < 7; $i++) {
                                        if( $nday == 5 && (1<<( (gmdate("w", $occenddate)-$i)%7) ) & $weekdays) {
                                            $occenddate -= $i * 24 * 60 * 60;
                                            break;
                                        }else if($nday != 5 && (1<<( (gmdate("w", $occenddate)+$i)%7) ) & $weekdays) {
                                            $occenddate +=  ($i + (($nday-1) *7)) * 24 * 60 * 60;
                                            break;
                                        }
                                    }

                                break; //case 3:
                                }

                            break;

                    }

                    if (defined("PHP_INT_MAX") && $occenddate > PHP_INT_MAX)
                        $occenddate = PHP_INT_MAX;

                    $this->recur["end"] = $occenddate;

                    $rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]) );
                    break;
                // Never ends
                case 0x23:
                default:
                    $this->recur["end"] = 0x7fffffff; // max date -> 2038
                    $rdata .= pack("V", 0x5AE980DF);
                    break;
            }

            // UTC date
            $utcstart = $this->toGMT($this->tz, (int) $this->recur["start"]);
            $utcend = $this->toGMT($this->tz, (int) $this->recur["end"]);

            //utc date+time
            $utcfirstoccstartdatetime = (isset($this->recur["startocc"])) ? $utcstart + (((int) $this->recur["startocc"])*60) : $utcstart;
            $utcfirstoccenddatetime = (isset($this->recur["endocc"])) ? $utcstart + (((int) $this->recur["endocc"]) * 60) : $utcstart;

            // update reminder time
            mapi_setprops($this->message, Array($this->proptags["reminder_time"] => $utcfirstoccstartdatetime ));

            // update first occurrence date
            mapi_setprops($this->message, Array($this->proptags["startdate"] => $utcfirstoccstartdatetime ));
            mapi_setprops($this->message, Array($this->proptags["duedate"] => $utcfirstoccenddatetime ));
            mapi_setprops($this->message, Array($this->proptags["commonstart"] => $utcfirstoccstartdatetime ));
            mapi_setprops($this->message, Array($this->proptags["commonend"] => $utcfirstoccenddatetime ));

            // Set Outlook properties, if it is an appointment
            if (isset($this->recur["message_class"]) && $this->recur["message_class"] == "IPM.Appointment") {
                // update real begin and real end date
                mapi_setprops($this->message, Array($this->proptags["startdate_recurring"] => $utcstart));
                mapi_setprops($this->message, Array($this->proptags["enddate_recurring"] => $utcend));

                // recurrencetype
                // Strange enough is the property recurrencetype, (type-0x9) and not the CDO recurrencetype
                mapi_setprops($this->message, Array($this->proptags["recurrencetype"] => ((int) $this->recur["type"]) - 0x9));

                // set named prop 'side_effects' to 369, needed for Outlook to ask for single or total recurrence when deleting
                mapi_setprops($this->message, Array($this->proptags["side_effects"] => 369));
            } else {
                mapi_setprops($this->message, Array($this->proptags["side_effects"] => 3441));
            }

            // FlagDueBy is datetime of the first reminder occurrence. Outlook gives on this time a reminder popup dialog
            // Any change of the recurrence (including changing and deleting exceptions) causes the flagdueby to be reset
            // to the 'next' occurrence; this makes sure that deleting the next ocurrence will correctly set the reminder to
            // the occurrence after that. The 'next' occurrence is defined as being the first occurrence that starts at moment X (server time)
            // with the reminder flag set.
            $reminderprops = mapi_getprops($this->message, array($this->proptags["reminder_minutes"]) );
            if(isset($reminderprops[$this->proptags["reminder_minutes"]]) ) {
                $occ = false;
                $occurrences = $this->getItems(time(), 0x7ff00000, 3, true);

                for($i = 0, $len = count($occurrences) ; $i < $len; $i++) {
                    // This will actually also give us appointments that have already started, but not yet ended. Since we want the next
                    // reminder that occurs after time(), we may have to skip the first few entries. We get 3 entries since that is the maximum
                    // number that would be needed (assuming reminder for item X cannot be before the previous occurrence starts). Worst case:
                    // time() is currently after start but before end of item, but reminder of next item has already passed (reminder for next item
                    // can be DURING the previous item, eg daily allday events). In that case, the first and second items must be skipped.

                    if(($occurrences[$i][$this->proptags["startdate"]] - $reminderprops[$this->proptags["reminder_minutes"]] * 60) > time()) {
                        $occ = $occurrences[$i];
                        break;
                    }
                }

                if($occ) {
                    mapi_setprops($this->message, Array($this->proptags["flagdueby"] => $occ[$this->proptags["startdate"]] - ($reminderprops[$this->proptags["reminder_minutes"]] * 60) ));
                } else {
                    // Last reminder passed, no reminders any more.
                    mapi_setprops($this->message, Array($this->proptags["reminder"] => false, $this->proptags["flagdueby"] => 0x7ff00000));
                }
            }

            // Default data
            // Second item (0x08) indicates the Outlook version (see documentation at the bottom of this file for more information)
            $rdata .= pack("VCCCC", 0x00003006, 0x08, 0x30, 0x00, 0x00);

            if(isset($this->recur["startocc"]) && isset($this->recur["endocc"])) {
                // Set start and endtime in minutes
                $rdata .= pack("VV", (int) $this->recur["startocc"], (int) $this->recur["endocc"]);
            }

            // Detailed exception data

            $changed_items = $this->recur["changed_occurences"];

            $rdata .= pack("v", count($changed_items));

            foreach($changed_items as $changed_item)
            {
                // Set start and end time of exception
                $rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"]));
                $rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"]));
                $rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"]));

                //Bitmask
                $bitmask = 0;

                // Check for changed strings
                if(isset($changed_item["subject"]))    {
                    $bitmask |= 1 << 0;
                }

                if(isset($changed_item["remind_before"])) {
                    $bitmask |= 1 << 2;
                }

                if(isset($changed_item["reminder_set"])) {
                    $bitmask |= 1 << 3;
                }

                if(isset($changed_item["location"])) {
                    $bitmask |= 1 << 4;
                }

                if(isset($changed_item["busystatus"])) {
                    $bitmask |= 1 << 5;
                }

                if(isset($changed_item["alldayevent"])) {
                    $bitmask |= 1 << 7;
                }

                if(isset($changed_item["label"])) {
                    $bitmask |= 1 << 8;
                }

                $rdata .= pack("v", $bitmask);

                // Set "subject"
                if(isset($changed_item["subject"])) {
                    // convert utf-8 to non-unicode blob string (us-ascii?)
                    $subject = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["subject"]);
                    $length = strlen($subject);
                    $rdata .= pack("vv", $length + 1, $length);
                    $rdata .= pack("a".$length, $subject);
                }

                if(isset($changed_item["remind_before"])) {
                    $rdata .= pack("V", $changed_item["remind_before"]);
                }

                if(isset($changed_item["reminder_set"])) {
                    $rdata .= pack("V", $changed_item["reminder_set"]);
                }

                if(isset($changed_item["location"])) {
                    $location = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["location"]);
                    $length = strlen($location);
                    $rdata .= pack("vv", $length + 1, $length);
                    $rdata .= pack("a".$length, $location);
                }

                if(isset($changed_item["busystatus"])) {
                    $rdata .= pack("V", $changed_item["busystatus"]);
                }

                if(isset($changed_item["alldayevent"])) {
                    $rdata .= pack("V", $changed_item["alldayevent"]);
                }

                if(isset($changed_item["label"])) {
                    $rdata .= pack("V", $changed_item["label"]);
                }
            }

            $rdata .= pack("V", 0);

            // write extended data
            foreach($changed_items as $changed_item)
            {
                $rdata .= pack("V", 0);
                if(isset($changed_item["subject"]) || isset($changed_item["location"])) {
                    $rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"]));
                    $rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"]));
                    $rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"]));
                }

                if(isset($changed_item["subject"])) {
                    $subject = iconv("UTF-8", "UCS-2LE", $changed_item["subject"]);
                    $length = iconv_strlen($subject, "UCS-2LE");
                    $rdata .= pack("v", $length);
                    $rdata .= pack("a".$length*2, $subject);
                }

                if(isset($changed_item["location"])) {
                    $location = iconv("UTF-8", "UCS-2LE", $changed_item["location"]);
                    $length = iconv_strlen($location, "UCS-2LE");
                    $rdata .= pack("v", $length);
                    $rdata .= pack("a".$length*2, $location);
                }

                if(isset($changed_item["subject"]) || isset($changed_item["location"])) {
                    $rdata .= pack("V", 0);
                }
            }

            $rdata .= pack("V", 0);

            // Set props
            mapi_setprops($this->message, Array($this->proptags["recurring_data"] => $rdata, $this->proptags["recurring"] => true));
            if(isset($this->tz) && $this->tz){
                $timezone = "GMT";
                if ($this->tz["timezone"]!=0){
                    // Create user readable timezone information
                    $timezone = sprintf("(GMT %s%02d:%02d)",    (-$this->tz["timezone"]>0 ? "+" : "-"),
                                                            abs($this->tz["timezone"]/60),
                                                            abs($this->tz["timezone"]%60));
                }
                mapi_setprops($this->message, Array($this->proptags["timezone_data"] => $this->getTimezoneData($this->tz),
                                                    $this->proptags["timezone"] => $timezone));
            }
        }

Here is the call graph for this function:

Here is the caller graph for this function:

Definition at line 1940 of file class.baserecurrence.php.

        {
            return $a["start"] == $b["start"] ? 0 : ($a["start"]  > $b["start"] ? 1 : -1 );
        }
BaseRecurrence::sortStarttime ( a,
b 
)

Definition at line 1899 of file class.baserecurrence.php.

        {
            $aTime = $a[$this->proptags["startdate"]];
            $bTime = $b[$this->proptags["startdate"]];

            return $aTime==$bTime?0:($aTime>$bTime?1:-1);
        }
BaseRecurrence::toGMT ( tz,
date 
)

toGMT returns a timestamp in GMT time for the time and timezone given

Definition at line 1589 of file class.baserecurrence.php.

                                   {
            if(!isset($tz['timezone']))
                return $date;
            $offset = $this->getTimezone($tz, $date);

            return $date + $offset * 60;
        }

Here is the call graph for this function:

Here is the caller graph for this function:

Function which converts an unix date timestamp to recurrence date timestamp.

Author:
Johnny Biemans
Parameters:
Date$datethe date which will be converted
Returns:
Int the converted date in minutes

Definition at line 1411 of file class.baserecurrence.php.

        {
            return ($date / 60) + 194074560;
        }

Here is the caller graph for this function:

Function to get timestamp of the beginning of the year of the timestamp given.

Parameters:
date$date
Returns:
date Timestamp referring to the same year but on Jan 01, at 00:00:00

Definition at line 1635 of file class.baserecurrence.php.

        {
            $time1 = $this->gmtime($date);

            return gmmktime(0, 0, 0, 1, 1, $time1["tm_year"] + 1900);
        }

Here is the call graph for this function:

Here is the caller graph for this function:


Member Data Documentation

Definition at line 66 of file class.baserecurrence.php.

Definition at line 71 of file class.baserecurrence.php.

Definition at line 76 of file class.baserecurrence.php.

Definition at line 81 of file class.baserecurrence.php.

Definition at line 61 of file class.baserecurrence.php.

Reimplemented in TaskRecurrence.

Definition at line 86 of file class.baserecurrence.php.


The documentation for this class was generated from the following file: