/** * select components from calendar on date or selectOption basis * * Ensure DTSTART is set for every component. * No date controls occurs. * * @author Kjell-Inge Gustafsson, kigkonsult <*****@*****.**> * @since 2.18.19 - 2014-02-01 * @param mixed $startY optional, start Year, default current Year ALT. array selecOptions ( *[ <propName> => <uniqueValue> ] ) * @param int $startM optional, start Month, default current Month * @param int $startD optional, start Day, default current Day * @param int $endY optional, end Year, default $startY * @param int $endY optional, end Month, default $startM * @param int $endY optional, end Day, default $startD * @param mixed $cType optional, calendar component type(-s), default FALSE=all else string/array type(-s) * @param bool $flat optional, FALSE (default) => output : array[Year][Month][Day][] * TRUE => output : array[] (ignores split) * @param bool $any optional, TRUE (default) - select component(-s) that occurs within period * FALSE - only component(-s) that starts within period * @param bool $split optional, TRUE (default) - one component copy every DAY it occurs during the * period (implies flat=FALSE) * FALSE - one occurance of component only in output array * @return array or FALSE */ function selectComponents($startY = FALSE, $startM = FALSE, $startD = FALSE, $endY = FALSE, $endM = FALSE, $endD = FALSE, $cType = FALSE, $flat = FALSE, $any = TRUE, $split = TRUE) { /* check if empty calendar */ if (0 >= count($this->components)) { return FALSE; } if (is_array($startY)) { return $this->selectComponents2($startY); } /* check default dates */ if (!$startY) { $startY = date('Y'); } if (!$startM) { $startM = date('m'); } if (!$startD) { $startD = date('d'); } $startDate = mktime(0, 0, 0, $startM, $startD, $startY); if (!$endY) { $endY = $startY; } if (!$endM) { $endM = $startM; } if (!$endD) { $endD = $startD; } $endDate = mktime(23, 59, 59, $endM, $endD, $endY); // echo 'selectComp arg='.date( 'Y-m-d H:i:s', $startDate).' -- '.date( 'Y-m-d H:i:s', $endDate)."<br>\n"; $tcnt = 0;// test ### /* check component types */ $validTypes = array('vevent', 'vtodo', 'vjournal', 'vfreebusy'); if (empty($cType)) { $cType = $validTypes; } else { if (!is_array($cType)) { $cType = array($cType); } $cType = array_map('strtolower', $cType); foreach ($cType as $cix => $theType) { $cType[$cix] = $theType; if (!in_array($theType, $validTypes)) { $cType[$cix] = 'vevent'; } } $cType = array_unique($cType); } if (FALSE === $flat && FALSE === $any) { // invalid combination $split = FALSE; } if (TRUE === $flat && TRUE === $split) { // invalid combination $split = FALSE; } /* iterate components */ $result = array(); $this->sort('UID'); $compUIDcmp = null; $recurridList = array(); foreach ($this->components as $cix => $component) { if (empty($component)) { continue; } unset($start); /* deselect unvalid type components */ if (!in_array($component->objName, $cType)) { continue; } $start = $component->getProperty('dtstart', FALSE, TRUE); /* select due when dtstart is missing */ if (empty($start) && $component->objName == 'vtodo' && FALSE === ($start = $component->getProperty('due', FALSE, TRUE))) { continue; } if (empty($start)) { continue; } if (!isset($start['value']['tz']) && isset($start['params']['TZID'])) { $start['value']['tz'] = $start['params']['TZID']; } $start = $start['value']; $compUID = $component->getProperty('UID'); if ($compUIDcmp != $compUID) { $compUIDcmp = $compUID; unset($exdatelist, $recurridList); } $SCbools = array('dtendExist' => FALSE, 'dueExist' => FALSE, 'durationExist' => FALSE, 'endAllDayEvent' => FALSE); $recurrid = FALSE; $dateFormat = array(); unset($end, $startWdate, $endWdate, $rdurWsecs, $rdur, $workstart, $workend); // clean up $startWdate = iCalUtilityFunctions::_SCsetXCurrentDateZ(iCalUtilityFunctions::_date2timestamp($start), $start); $dateFormat['start'] = isset($start['hour']) ? 'Y-m-d H:i:s' : 'Y-m-d'; /* get end date from dtend/due/duration properties */ $end = $component->getProperty('dtend', FALSE, TRUE); if (!empty($end)) { $SCbools['dtendExist'] = TRUE; $dateFormat['end'] = isset($end['value']['hour']) ? 'Y-m-d H:i:s' : 'Y-m-d'; } if (!isset($end['value']['tz']) && isset($end['params']['TZID'])) { $end['value']['tz'] = $end['params']['TZID']; } $end = $end['value']; if (empty($end) && $component->objName == 'vtodo') { $end = $component->getProperty('due'); if (!empty($end)) { $SCbools['dueExist'] = TRUE; $dateFormat['end'] = isset($end['hour']) ? 'Y-m-d H:i:s' : 'Y-m-d'; } } if (!empty($end) && !isset($end['hour'])) { /* a DTEND without time part regards an event that ends the day before, for an all-day event DTSTART=20071201 DTEND=20071202 (taking place 20071201!!! */ $SCbools['endAllDayEvent'] = TRUE; $endWdate = mktime(23, 59, 59, $end['month'], $end['day'] - 1, $end['year']); $end['year'] = date('Y', $endWdate); $end['month'] = date('m', $endWdate); $end['day'] = date('d', $endWdate); $end['hour'] = 23; $end['min'] = $end['sec'] = 59; } if (empty($end)) { $end = $component->getProperty('duration', FALSE, FALSE, TRUE); // in dtend (array) format if (!empty($end)) { if (isset($start['tz'])) { $end['tz'] = $start['tz']; } } $SCbools['durationExist'] = TRUE; $dateFormat['end'] = isset($start['hour']) ? 'Y-m-d H:i:s' : 'Y-m-d'; // if( !empty($end)) echo 'selectComp 4 start='.implode('-',$start).' end='.implode('-',$end)."<br>\n"; // test ### } if (empty($end)) { // assume one day duration if missing end date $end = array('year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59); if (isset($start['tz'])) { $end['tz'] = $start['tz']; } } // if( isset($end)) echo 'selectComp 5 start='.implode('-',$start).' end='.implode('-',$end)."<br>\n"; // test ### $endWdate = iCalUtilityFunctions::_SCsetXCurrentDateZ(iCalUtilityFunctions::_date2timestamp($end), $end); if ($endWdate < $startWdate) { // MUST be after start date!! $end = array('year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59); $endWdate = iCalUtilityFunctions::_date2timestamp($end); } $rdurWsecs = $endWdate - $startWdate; // compute event (component) duration in seconds /* make a list of optional exclude dates for component occurence from exrule and exdate */ $exdatelist = array(); $workstart = iCalUtilityFunctions::_timestamp2date($startDate - $rdurWsecs, 6); $workend = iCalUtilityFunctions::_timestamp2date($endDate + $rdurWsecs, 6); while (FALSE !== ($exrule = $component->getProperty('exrule'))) { // check exrule iCalUtilityFunctions::_recur2date($exdatelist, $exrule, $start, $workstart, $workend); } while (FALSE !== ($exdate = $component->getProperty('exdate'))) { // check exdate foreach ($exdate as $theExdate) { $exWdate = iCalUtilityFunctions::_date2timestamp($theExdate); $exWdate = mktime(0, 0, 0, date('m', $exWdate), date('d', $exWdate), date('Y', $exWdate)); // on a day-basis !!! if ($startDate - $rdurWsecs <= $exWdate && $endDate >= $exWdate) { $exdatelist[$exWdate] = TRUE; } } // end - foreach( $exdate as $theExdate ) } // end - check exdate /* check recurrence-id (note, a missing sequence is the same as sequence=0 so don't test for sequence), remove hit with reccurr-id date */ if (FALSE !== ($recurrid = $component->getProperty('recurrence-id'))) { $recurrid = iCalUtilityFunctions::_date2timestamp($recurrid); $recurrid = mktime(0, 0, 0, date('m', $recurrid), date('d', $recurrid), date('Y', $recurrid)); // on a day-basis !!! $recurridList[$recurrid] = TRUE; // no recurring to start this day // echo "adding comp no:$cix with date=".implode($start)." and recurrid=".implode($recurrid)." to recurridList id=$recurrid<br>\n"; // test ### } // end recurrence-id/sequence test /* select only components with.. . */ if (!$any && $startWdate >= $startDate && $startWdate <= $endDate || $any && $startWdate < $endDate && $endWdate >= $startDate) { // occurs within the period /* add the selected component (WITHIN valid dates) to output array */ if ($flat) { // any=true/false, ignores split if (!$recurrid) { $result[$compUID] = $component->copy(); } // copy original to output (but not anyone with recurrence-id) } elseif ($split) { // split the original component if ($endWdate > $endDate) { $endWdate = $endDate; } // use period end date $rstart = $startWdate < $startDate ? $startDate : $startWdate; // use period start date $startYMD = $rstartYMD = date('Ymd', $rstart); $endYMD = date('Ymd', $endWdate); $checkDate = mktime(0, 0, 0, date('m', $rstart), date('d', $rstart), date('Y', $rstart)); // on a day-basis !!! // echo "going to test comp no:$cix with rstartYMD=$rstartYMD, endYMD=$endYMD and checkDate($checkDate) with recurridList=".implode(',',array_keys($recurridList))."<br>\n"; // test ### if (!isset($exdatelist[$checkDate])) { // exclude any recurrence START date, found in exdatelist while ($rstartYMD <= $endYMD) { // iterate if (isset($exdatelist[$checkDate]) || isset($recurridList[$checkDate]) && !$recurrid) { // or in the recurridList, but not itself // echo "skipping comp no:$cix with datestart=$rstartYMD and checkdate=$checkDate<br>\n"; // test ### $rstart += 24 * 3600; // step one day $rstartYMD = date('Ymd', $rstart); continue; } iCalUtilityFunctions::_SCsetXCurrentStart($component, $dateFormat, $checkDate, $rstartYMD, $rstart, $startYMD, $start); iCalUtilityFunctions::_SCsetXCurrentEnd($component, $dateFormat, $rstart, $rstartYMD, $endWdate, $endYMD, $end, $SCbools); $wd = getdate($rstart); $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output $rstart += 24 * 3600; // step one day $rstartYMD = date('Ymd', $rstart); $checkDate = mktime(0, 0, 0, date('m', $rstart), date('d', $rstart), date('Y', $rstart)); // on a day-basis !!! } // end while( $rstart <= $endWdate ) } // end if( !isset( $exdatelist[$checkDate] )) } elseif ($recurrid && !$flat && !$any && !$split) { $continue = TRUE; } else { // !$flat && !$split, i.e. no flat array and DTSTART within period $checkDate = mktime(0, 0, 0, date('m', $startWdate), date('d', $startWdate), date('Y', $startWdate)); // on a day-basis !!! // echo "going to test comp no:$cix with checkDate=$checkDate with recurridList=".implode(',',array_keys($recurridList)); // test ### if ((!$any || !isset($exdatelist[$checkDate])) && (!isset($recurridList[$checkDate]) || $recurrid)) { // or in the recurridList, but not itself // echo " and copied to output<br>\n"; // test ### $wd = getdate($startWdate); $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output } } } // end if(( $startWdate >= $startDate ) && ( $startWdate <= $endDate )) /* if 'any' components, check components with reccurrence rules, removing all excluding dates */ if (TRUE === $any) { /* make a list of optional repeating dates for component occurence, rrule, rdate */ $recurlist = array(); while (FALSE !== ($rrule = $component->getProperty('rrule'))) { // check rrule iCalUtilityFunctions::_recur2date($recurlist, $rrule, $start, $workstart, $workend); } foreach ($recurlist as $recurkey => $recurvalue) { // key=match date as timestamp $recurlist[$recurkey] = $rdurWsecs; } // add duration in seconds while (FALSE !== ($rdate = $component->getProperty('rdate'))) { // check rdate foreach ($rdate as $theRdate) { if (is_array($theRdate) && 2 == count($theRdate) && array_key_exists('0', $theRdate) && array_key_exists('1', $theRdate)) { $rstart = iCalUtilityFunctions::_date2timestamp($theRdate[0]); if ($rstart < $startDate - $rdurWsecs || $rstart > $endDate) { continue; } if (isset($theRdate[1]['year'])) { // date-date period $rend = iCalUtilityFunctions::_date2timestamp($theRdate[1]); } else { // date-duration period $rend = iCalUtilityFunctions::_duration2date($theRdate[0], $theRdate[1]); $rend = iCalUtilityFunctions::_date2timestamp($rend); } while ($rstart < $rend) { $recurlist[$rstart] = $rdurWsecs; // set start date for recurrence instance + rdate duration in seconds $rstart = mktime(date('H', $rstart), date('i', $rstart), date('s', $rstart), date('m', $rstart), date('d', $rstart) + 1, date('Y', $rstart)); // step one day } } else { // single date $theRdate = iCalUtilityFunctions::_date2timestamp($theRdate); if ($startDate - $rdurWsecs <= $theRdate && $endDate >= $theRdate) { $recurlist[$theRdate] = $rdurWsecs; } // set start date for recurrence instance + event duration in seconds } } } // end - check rdate foreach ($recurlist as $recurkey => $durvalue) { // remove all recurrence START dates found in the exdatelist $checkDate = mktime(0, 0, 0, date('m', $recurkey), date('d', $recurkey), date('Y', $recurkey)); // on a day-basis !!! if (isset($exdatelist[$checkDate])) { // no recurring to start this day unset($recurlist[$recurkey]); } } if (0 < count($recurlist)) { ksort($recurlist); $xRecurrence = 1; $component2 = $component->copy(); $compUID = $component2->getProperty('UID'); foreach ($recurlist as $recurkey => $durvalue) { // echo "recurKey=".date( 'Y-m-d H:i:s', $recurkey ).' dur='.iCalUtilityFunctions::offsetSec2His( $durvalue )."<br>\n"; // test ###; if ($startDate - $rdurWsecs > $recurkey || $endDate < $recurkey) { // not within period continue; } $checkDate = mktime(0, 0, 0, date('m', $recurkey), date('d', $recurkey), date('Y', $recurkey)); // on a day-basis !!! if (isset($recurridList[$checkDate])) { // no recurring to start this day continue; } if (isset($exdatelist[$checkDate])) { // check excluded dates continue; } if ($startWdate >= $recurkey) { // exclude component start date continue; } $rstart = $recurkey; $rend = $recurkey + $durvalue; /* add repeating components within valid dates to output array, only start date set */ if ($flat) { if (!isset($result[$compUID])) { // only one comp $result[$compUID] = $component2->copy(); } // copy to output } elseif ($split) { $xRecurrence += 1; if ($rend > $endDate) { $rend = $endDate; } $startYMD = $rstartYMD = date('Ymd', $rstart); $endYMD = date('Ymd', $rend); // echo "splitStart=".date( 'Y-m-d H:i:s', $rstart ).' end='.date( 'Y-m-d H:i:s', $rend )."<br>\n"; // test ###; while ($rstartYMD <= $endYMD) { // iterate.. . $checkDate = mktime(0, 0, 0, date('m', $rstart), date('d', $rstart), date('Y', $rstart)); // on a day-basis !!! if (isset($recurridList[$checkDate])) { // no recurring to start this day break; } if (isset($exdatelist[$checkDate])) { // exclude any recurrence START date, found in exdatelist break; } // echo "checking date after startdate=".date( 'Y-m-d H:i:s', $rstart ).' mot '.date( 'Y-m-d H:i:s', $startDate )."<br>"; // test ###; if ($rstart >= $startDate) { // date after dtstart iCalUtilityFunctions::_SCsetXCurrentStart($component2, $dateFormat, $checkDate, $rstartYMD, $rstart, $startYMD, $start); iCalUtilityFunctions::_SCsetXCurrentEnd($component2, $dateFormat, $rstart, $rstartYMD, $endWdate, $endYMD, $end, $SCbools); $component2->setProperty('X-RECURRENCE', $xRecurrence); $wd = getdate($rstart); $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output } // end if$rstart >= $startDate { // date after dtstart $rstart += 24 * 3600; // step one day $rstartYMD = date('Ymd', $rstart); } // end while( $rstart <= $rend ) } elseif ($rstart >= $startDate) { // date within period //* flat=FALSE && split=FALSE => one comp every recur startdate *// $xRecurrence += 1; $checkDate = mktime(0, 0, 0, date('m', $rstart), date('d', $rstart), date('Y', $rstart)); // on a day-basis !!! if (!isset($exdatelist[$checkDate])) { // exclude any recurrence START date, found in exdatelist iCalUtilityFunctions::_SCsetXCurrentStart($component2, $dateFormat, $rstart, FALSE, FALSE, FALSE, $start); $tend = $rstart + $rdurWsecs; iCalUtilityFunctions::_SCsetXCurrentEnd($component2, $dateFormat, $tend, date('Ymd', $tend), $endWdate, date('Ymd', $endWdate), $end, $SCbools); $component2->setProperty('X-RECURRENCE', $xRecurrence); $wd = getdate($rstart); $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output } // end if( !isset( $exdatelist[$checkDate] )) } // end elseif( $rstart >= $startDate ) } // end foreach( $recurlist as $recurkey => $durvalue ) unset($component2); } // end if( 0 < count( $recurlist )) /* deselect components with startdate/enddate not within period */ if ($endWdate < $startDate || $startWdate > $endDate) { continue; } } // end if( TRUE === $any ) } // end foreach ( $this->components as $cix => $component ) unset($SCbools, $recurrid, $recurridList, $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $exdatelist, $recurlist, $workstart, $workend, $dateFormat); // clean up if (0 >= count($result)) { return FALSE; } elseif (!$flat) { foreach ($result as $y => $yeararr) { foreach ($yeararr as $m => $montharr) { foreach ($montharr as $d => $dayarr) { if (empty($result[$y][$m][$d])) { unset($result[$y][$m][$d]); } else { $result[$y][$m][$d] = array_values($dayarr); // skip tricky UID-index if (1 < count($result[$y][$m][$d])) { foreach ($result[$y][$m][$d] as &$c) { // sort iCalUtilityFunctions::_setSortArgs($c); } usort($result[$y][$m][$d], array('iCalUtilityFunctions', '_cmpfcn')); } } } // end foreach( $montharr as $d => $dayarr ) if (empty($result[$y][$m])) { unset($result[$y][$m]); } else { ksort($result[$y][$m]); } } // end foreach( $yeararr as $m => $montharr ) if (empty($result[$y])) { unset($result[$y]); } else { ksort($result[$y]); } } // end foreach( $result as $y => $yeararr ) if (empty($result)) { unset($result); } else { ksort($result); } } // end elseif( !$flat ) if (0 >= count($result)) { return FALSE; } return $result; }