File: /home/confeduphaar/backip-old-files/components/com_jevents/libraries/iCalRRule.php
<?php
/**
* JEvents Component for Joomla! 3.x
*
* @version $Id: iCalRRule.php 3467 2012-04-03 09:36:16Z geraintedwards $
* @package JEvents
* @copyright Copyright (C) 2008-2020 GWESystems Ltd, 2006-2008 JEvents Project Group
* @license GNU/GPLv2, see http://www.gnu.org/licenses/gpl-2.0.html
* @link http://www.jevents.net
*/
// no direct access
defined('_JEXEC') or die('Restricted access');
use Joomla\CMS\Factory;
use Joomla\String\StringHelper;
class iCalRRule extends Joomla\CMS\Table\Table
{
/** @var int Primary key */
var $rr_id = null;
/**
* This holds the raw data as an array
*
* @var array
*/
var $data;
var $freq;
// array of exception dates
var $_exdate = array();
/**
* Null Constructor
*/
function __construct(&$db)
{
parent::__construct('#__jevents_rrule', 'rr_id', $db);
}
/**
* Pseudo Constructor
*
* @param iCal Entry parsed from ICS file as an array $ice
*
* @return n/a
*/
public static function iCalRRuleFromData($rrule)
{
$db = Factory::getDbo();
$temp = new iCalRRule($db);
$temp->data = $rrule;
$temp->freq = $temp->data['FREQ'];
// Should really test count
$temp->processField("count", 999);
$temp->processField("interval", 1);
//interval ios a mysql reserved word
$temp->rinterval = $temp->interval;
$temp->_interval = $temp->interval;
unset($temp->interval);
if ($temp->freq == "none")
{
$temp->processField("until", "");
}
else
{
// cap indefinate repeats if count is blank as well as until
if (array_key_exists("COUNT", $temp->data))
{
$temp->processField("until", 0);
}
else
{
$cfg = JEVConfig::getInstance();
$temp->processField("until", JevDate::mktime(23, 59, 59, 12, 12, JEVHelper::getMaxYear()));
$temp->processField("count", 9999);
}
}
$temp->processField("untilraw", "");
$temp->processField("bysecond", "");
$temp->processField("byminute", "");
$temp->processField("byhour", "");
$temp->processField("byday", "");
$temp->processField("bymonthday", "");
$temp->processField("byyearday", "");
$temp->processField("byweekno", "");
$temp->processField("bymonth", "");
$temp->processField("bysetpos", "");
$temp->processField("irregulardates", "");
$temp->irregulardates = json_encode($temp->irregulardates);
$temp->processField("wkst", "");
return $temp;
}
public function processField($field, $default)
{
$this->$field = array_key_exists(strtoupper($field), $this->data) ? $this->data[strtoupper($field)] : $default;
}
public function store($updateNulls = false)
{
return parent::store($updateNulls);
}
/**
* Pseudo Constructor
*
* @param iCal Entry parsed from ICS file as an array $ice
*
* @return n/a
*/
public function iCalRRuleFromDB($icalrowAsArray)
{
$db = Factory::getDbo();
$temp = new iCalRRule($db);
$temp->data = $icalrowAsArray;
// Should really test count
$temp->processField2("freq", "YEARLY");
$temp->processField2("count", 999);
$temp->processField2("rinterval", 1);
//interval ios a mysql reserved word
$temp->_interval = $temp->rinterval;
if ($temp->freq == "none")
{
$temp->processField2("until", "");
}
else
{
$cfg = JEVConfig::getInstance();
// cap indefinate repeats if count is blank as well as until
if (array_key_exists("COUNT", $temp->data))
{
$temp->processField2("until", "");
}
else
{
$temp->processField2("until", JevDate::mktime(23, 59, 59, 12, 12, JEVHelper::getMaxYear()));
}
}
$temp->processField2("untilraw", "");
$temp->processField2("bysecond", "");
$temp->processField2("byminute", "");
$temp->processField2("byhour", "");
$temp->processField2("byday", "");
$temp->processField2("bymonthday", "");
$temp->processField2("byyearday", "");
$temp->processField2("byweekno", "");
$temp->processField2("bymonth", "");
$temp->processField2("bysetpos", "");
$temp->processField2("irregulardates", "");
$temp->processField2("wkst", "");
return $temp;
}
public function processField2($field, $default)
{
$this->$field = array_key_exists(strtolower($field), $this->data) ? $this->data[strtolower($field)] : $default;
}
/**
* Generates repetition from vevent & rrule data from scratch
* The result can then be saved to the database
*/
public function getRepetitions($dtstart, $dtend, $duration, $recreate = false, $exdate = array())
{
// put exdate into somewhere that I can get from _makerepeat
$this->_exdate = $exdate;
// TODO "getRepetitions doesnt yet deal with Short months and 31st or leap years/week 53<br/>";
if ($dtend == 0 && $duration > 0)
{
$dtend = $dtstart + $duration;
}
if (!$recreate && isset($this->_repetitions)) return $this->_repetitions;
$this->_repetitions = array();
if (!isset($this->eventid))
{
echo "no eventid set in generateRepetitions<br/>";
return $this->_repetitions;
}
if ($this->count == 1 && $this->freq != "IRREGULAR")
{
//echo "count=1 returing<br/>";
$this->_makeRepeat($dtstart, $dtend);
return $this->_repetitions;
}
//list ($h,$min,$s,$d,$m,$y) = explode(":",JevDate::strftime("%H:%M:%S:%d:%m:%Y",$end));
list ($startHour, $startMin, $startSecond, $startDay, $startMonth, $startYear, $startWD)
= explode(":", JevDate::strftime("0%H:0%M:0%S:%d:%m:%Y:%w", $dtstart));
//echo "$startHour,$startMin,$startSecond,$startDay,$startMonth,$startYear,$startWD,$dtstart<br/>";
$dtstartMidnight = JevDate::mktime(0, 0, 0, $startMonth, $startDay, $startYear);
list ($endHour, $endMin, $endSecond, $endDay, $endMonth, $endYear, $endWD) = explode(":", JevDate::strftime("0%H:0%M:0%S:%d:%m:%Y:%w", $dtend));
$duration = $dtend - $dtstart;
// duration in days (work in the middle part of the day in case the clocks change and make the end time a couple of hours later too)
$jevStart = JevDate::mktime(12, 0, 0, $startMonth, $startDay, $startYear);
$jevstart = new JevDate($jevStart);
$jevEnd = JevDate::mktime(15, 0, 0, $endMonth, $endDay, $endYear);
$jevend = new JevDate($jevEnd);
$durationdays = $jevstart->diff($jevend)->days;
static $weekdayMap = array("SU" => 0, "MO" => 1, "TU" => 2, "WE" => 3, "TH" => 4, "FR" => 5, "SA" => 6);
static $weekdayReverseMap = array("SU", "MO", "TU", "WE", "TH", "FR", "SA");
static $dailySecs = 86400;
static $weeklySecs = 604800;
// TODO implement byyearday
// TODO do full leap year audit e.g. YEARLY repeats
//echo "freq = ".$this->freq."<br/>";
switch ($this->freq)
{
case "YEARLY":
// TODO the code doesn't yet deal with multiple bymonths
if ($this->bymonth == "") $this->bymonth = $startMonth;
//if ($this->byday=="") $this->byday=$weekdayReverseMap[$startWD];
// If I have byday and bymonthday then the two considions must be met
$weekdays = array();
if ($this->byday != "")
{
foreach (explode(",", str_replace(" ", "", $this->byday)) as $bday)
{
if (array_key_exists($bday, $weekdayMap))
{
$weekdays[] = $weekdayMap[$bday];
}
}
}
if ($this->byyearday != "")
{
echo "byyearday <br/>";
$days = explode(",", str_replace(" ", "", $this->byyearday));
$start = $dtstart;
$end = $dtend;
$countRepeats = 0;
$currentYearStart = JevDate::mktime(0, 0, 0, 1, 1, $startYear);
// do the current month first
while ($countRepeats < $this->count && !$this->_afterUntil($currentYearStart))
{
$currentYear = JevDate::strftime("%Y", $currentYearStart);
$currentYearDays = date("L", $currentYearStart) ? 366 : 365;
foreach ($days as $day)
{
if ($countRepeats >= $this->count || $this->_afterUntil($start)) return $this->_repetitions;
// TODO I am assuming all + or all -ve
$details = array();
preg_match("/(\+|-?)(\d*)/", $day, $details);
list($temp, $plusminus, $daynumber) = $details;
if (StringHelper::strlen($plusminus) == 0) $plusminus = "+";
// do not go over year end
if ($daynumber > $currentYearDays) continue;
if ($plusminus == "+")
{
$targetStart = JevDate::mktime($startHour, $startMin, $startSecond, 12, 31, $currentYear - 1);
$targetStart = JevDate::strtotime("+$daynumber days", $targetStart);
}
else
{
$targetStart = JevDate::mktime($startHour, $startMin, $startSecond, 1, 1, $currentYear + 1);
$targetStart = JevDate::strtotime("-$daynumber days", $targetStart);
}
// TODO - Fix situation where summer time starts or ends for all day event here!!!
$targetEnd = $targetStart + $duration;
if ($countRepeats >= $this->count)
{
return $this->_repetitions;
}
if ($targetStart >= $dtstartMidnight && !$this->_afterUntil($targetStart))
{
// double check for byday constraints
if ($this->byday != "")
{
if (!in_array(JevDate::strftime("%w", $targetStart), $weekdays))
{
continue;
}
}
$countRepeats += $this->_makeRepeat($targetStart, $targetEnd);
}
}
// now ago to the start of next year
$maxyear = (PHP_INT_SIZE === 8) ? 2999 : 2037;
if ($currentYear + $this->rinterval > $maxyear) return $this->_repetitions;
$currentYearStart = JevDate::mktime(0, 0, 0, 1, 1, $currentYear + $this->rinterval);
}
}
// assume for now that its an anniversary of the start month only!
// TODO relzs this assumption !!
else if ($this->bymonthday != "")
{
echo "bymonthday" . $this->bymonthday . " <br/>";
$days = explode(",", str_replace(" ", "", $this->bymonthday));
$start = $dtstart;
$end = $dtend;
$countRepeats = 0;
$currentMonthStart = JevDate::mktime(0, 0, 0, $startMonth, 1, $startYear);
// do the current month first
while ($countRepeats < $this->count && !$this->_afterUntil($currentMonthStart))
{
list ($currentMonth, $currentYear) = explode(":", JevDate::strftime("%m:%Y", $currentMonthStart));
$currentMonthDays = date("t", $currentMonthStart);
foreach ($days as $day)
{
if ($countRepeats >= $this->count || $this->_afterUntil($start)) return $this->_repetitions;
// Assume no negative bymonthday values
// TODO relax this assumption
// do not go over month end
if ($day > $currentMonthDays) continue;
$targetStart = JevDate::mktime($startHour, $startMin, $startSecond, $currentMonth, $day, $currentYear);
$targetEnd = $targetStart + $duration;
if ($countRepeats >= $this->count)
{
return $this->_repetitions;
}
if ($targetStart >= $dtstartMidnight && !$this->_afterUntil($targetStart))
{
// double check for byday constraints
if ($this->byday != "")
{
if (!in_array(JevDate::strftime("%w", $targetStart), $weekdays))
{
continue;
}
}
$countRepeats += $this->_makeRepeat($targetStart, $targetEnd);
}
}
// now ago to the start of next month
if ($currentYear + $this->rinterval > 2099) return $this->_repetitions;
$currentMonthStart = JevDate::mktime(0, 0, 0, $currentMonth, 1, $currentYear + $this->rinterval);
}
}
// see ical RFC 2445 page 42
/*
Each BYDAY value can also be preceded by a positive (+n) or negative
(-n) integer. If present, this indicates the nth occurrence of the
specific day within the MONTHLY or YEARLY RRULE. For example, within
a MONTHLY rule, +1MO (or simply 1MO) represents the first Monday
within the month, whereas -1MO represents the last Monday of the
month. If an integer modifier is not present, it means all days of
this type within the specified frequency. For example, within a
MONTHLY rule, MO represents all Mondays within the month.
*/
// annual repeats of the start date - TODO check this
else if ($this->byday == "")
{
$start = $dtstart;
$end = $dtend;
$countRepeats = 0;
while ($countRepeats < $this->count && !$this->_afterUntil($start))
{
$countRepeats += $this->_makeRepeat($start, $end);
$currentYear = JevDate::strftime("%Y", $start);
list ($h, $min, $s, $d, $m, $y) = explode(":", JevDate::strftime("%H:%M:%S:%d:%m:%Y", $start));
$maxyear = (PHP_INT_SIZE === 8) ? 2999 : 2037;
if (($currentYear + $this->rinterval) >= $maxyear) break;
$start = JevDate::strtotime("+" . $this->rinterval . " years", $start);
$end = JevDate::strtotime("+" . $this->rinterval . " years", $end);
}
}
else
{
$days = explode(",", str_replace(" ", "", $this->byday));
// duplicate where necessary
$extradays = array();
foreach ($days as $day)
{
if (strpos($day, "+") === false && strpos($day, "-") === false)
{
for ($i = 2; $i <= 52; $i++)
{
$extradays[] = "+" . $i . $day;
}
}
}
$days = array_merge($days, $extradays);
$start = $dtstart;
$end = $dtend;
$countRepeats = 0;
$currentYear = JevDate::strftime("%Y", $start);
// do the current month first
while ($countRepeats < $this->count && !$this->_afterUntil($start))
{
$currentMonth = JevDate::strftime("%m", $start);
foreach ($days as $day)
{
if ($countRepeats >= $this->count || $this->_afterUntil($start))
{
return $this->_repetitions;
}
$details = array();
if (strpos($day, "+") === false && strpos($day, "-") === false)
{
$day = "+1" . $day;
}
preg_match("/(\+|-?)(\d+)(.{2})/", $day, $details);
if (count($details) != 4)
{
echo "<br/><br/><b>PROBLEMS with $day</b><br/><br/>";
return $this->_repetitions;
}
else
{
list($temp, $plusminus, $weeknumber, $dayname) = $details;
if (StringHelper::strlen($plusminus) == 0) $plusminus = "+";
if (StringHelper::strlen($weeknumber) == 0) $weeknumber = 1;
// always check for dtstart (nothing is allowed earlier)
if ($plusminus == "-")
{
//echo "count back $weeknumber weeks on $dayname<br/>";
list ($startDay, $startMonth, $startYear, $startWD) = explode(":", JevDate::strftime("%d:%m:%Y:%w", $start));
$startLast = date("t", $start);
$monthEnd = JevDate::mktime(0, 0, 0, $startMonth, $startLast, $startYear);
$meWD = JevDate::strftime("%w", $monthEnd);
$adjustment = $startLast - (7 + $meWD - $weekdayMap[$dayname]) % 7;
$targetstartDay = $adjustment - ($weeknumber - 1) * 7;
$targetendDay = $targetstartDay + $endDay - $startDay;
list ($h, $min, $s, $d, $m, $y) = explode(":", JevDate::strftime("%H:%M:%S:%d:%m:%Y", $start));
$testStart = JevDate::mktime($h, $min, $s, $m, $targetstartDay, $y);
if ($currentMonth == JevDate::strftime("%m", $testStart))
{
$targetStart = $testStart;
// WE can't just add the duration since if summer time starts/ends within the event length then the end and possibly the date could be wrong
$targetEnd = $targetStart + $duration;
$targetEnd = JevDate::mktime($endHour, $endMin, $endSecond, $currentMonth, $targetstartDay + $durationdays, $currentYear);
if ($countRepeats >= $this->count)
{
return $this->_repetitions;
}
if ($targetStart >= $dtstartMidnight && !$this->_afterUntil($targetStart))
{
$countRepeats += $this->_makeRepeat($targetStart, $targetEnd);
}
}
}
else
{
//echo "count forward $weeknumber weeks on $dayname<br/>";
list ($startDay, $startMonth, $startYear, $startWD) = explode(":", JevDate::strftime("%d:%m:%Y:%w", $start));
$monthStart = JevDate::mktime(0, 0, 0, $startMonth, 1, $startYear);
$msWD = JevDate::strftime("%w", $monthStart);
if (!isset($weekdayMap[$dayname]))
{
$x = 1;
}
$adjustment = 1 + (7 + $weekdayMap[$dayname] - $msWD) % 7;
$targetstartDay = $adjustment + ($weeknumber - 1) * 7;
$targetendDay = $targetstartDay + $endDay - $startDay;
list ($h, $min, $s, $d, $m, $y) = explode(":", JevDate::strftime("%H:%M:%S:%d:%m:%Y", $start));
$testStart = JevDate::mktime($h, $min, $s, $m, $targetstartDay, $y);
if ($currentMonth == JevDate::strftime("%m", $testStart))
{
$targetStart = $testStart;
// WE can't just add the duration since if summer time starts/ends within the event length then the end and possibly the date could be wrong
$targetEnd = $targetStart + $duration;
$targetEnd = JevDate::mktime($endHour, $endMin, $endSecond, $currentMonth, $targetstartDay + $durationdays, $currentYear);
if ($countRepeats >= $this->count)
{
return $this->_repetitions;
}
if ($targetStart >= $dtstartMidnight && !$this->_afterUntil($targetStart))
{
$countRepeats += $this->_makeRepeat($targetStart, $targetEnd);
}
}
}
}
}
// now ago to the start of the next month
$start = $targetStart;
$end = $targetEnd;
list ($h, $min, $s, $d, $m, $y) = explode(":", JevDate::strftime("%H:%M:%S:%d:%m:%Y", $start));
if (($y + $this->rinterval + $m / 12) > 2099) return $this->_repetitions;
$start = JevDate::mktime($h, $min, $s, $m, 1, $y + $this->rinterval);
$end = $start + $duration;
}
}
return $this->_repetitions;
break;
case "MONTHLY":
if ($this->bymonthday == "" && $this->byday == "")
{
$this->bymonthday = $startDay;
}
if ($this->bymonthday != "")
{
echo "bymonthday" . $this->bymonthday . " <br/>";
// if not byday then by monthday
$days = explode(",", str_replace(" ", "", $this->bymonthday));
// If I have byday and bymonthday then the two considions must be met
$weekdays = array();
if ($this->byday != "")
{
foreach (explode(",", str_replace(" ", "", $this->byday)) as $bday)
{
$weekdays[] = $weekdayMap[$bday];
}
}
$start = $dtstart;
$end = $dtend;
$countRepeats = 0;
$currentMonthStart = JevDate::mktime(0, 0, 0, $startMonth, 1, $startYear);
// do the current month first
while ($countRepeats < $this->count && !$this->_afterUntil($currentMonthStart))
{
//echo $countRepeats ." ".$this->count." ".$currentMonthStart."<br/>";
list ($currentMonth, $currentYear) = explode(":", JevDate::strftime("%m:%Y", $currentMonthStart));
$currentMonthDays = date("t", $currentMonthStart);
foreach ($days as $day)
{
if ($countRepeats >= $this->count || $this->_afterUntil($start)) return $this->_repetitions;
$details = array();
preg_match("/(\+|-?)(\d+)/", $day, $details);
if (count($details) != 3)
{
echo "<br/><br/><b>PROBLEMS with $day</b><br/><br/>";
return $this->_repetitions;
}
else
{
list($temp, $plusminus, $daynumber) = $details;
if (StringHelper::strlen($plusminus) == 0) $plusminus = "+";
if (StringHelper::strlen($daynumber) == 0) $daynumber = $startDay;
// always check for dtstart (nothing is allowed earlier)
if ($plusminus == "-")
{
// must not go before start of month etc.
if ($daynumber > $currentMonthDays) continue;
echo "I need to check negative bymonth days <br/>";
$targetStart = JevDate::mktime($startHour, $startMin, $startSecond, $currentMonth, $currentMonthDays + 1 - $daynumber, $currentYear);
$targetEnd = $targetStart + $duration;
if ($countRepeats >= $this->count)
{
return $this->_repetitions;
}
if ($targetStart >= $dtstartMidnight && !$this->_afterUntil($targetStart))
{
$countRepeats += $this->_makeRepeat($targetStart, $targetEnd);
}
}
else
{
//echo "$daynumber $currentMonthDays bd=".$this->byday." <br/>";
// must not go over end month etc.
if ($daynumber > $currentMonthDays) continue;
//echo "$startHour,$startMin,$startSecond,$currentMonth,$daynumber,$currentYear<br/>";
$targetStart = JevDate::mktime($startHour, $startMin, $startSecond, $currentMonth, $daynumber, $currentYear);
$targetEnd = $targetStart + $duration;
// WE can't just add the duration since if summer time starts/ends within the event length then the end and possibly the date could be wrong
$targetEnd = JevDate::mktime($endHour, $endMin, $endSecond, $currentMonth, $daynumber + $durationdays, $currentYear);
//echo "$targetStart $targetEnd $dtstartMidnight<br/>";
if ($countRepeats >= $this->count)
{
return $this->_repetitions;
}
if ($targetStart >= $dtstartMidnight && !$this->_afterUntil($targetStart))
{
// double check for byday constraints
if ($this->byday != "")
{
if (!in_array(JevDate::strftime("%w", $targetStart), $weekdays))
{
continue;
}
}
$countRepeats += $this->_makeRepeat($targetStart, $targetEnd);
//echo "countrepeats = $countRepeats<br/>";
}
}
}
}
// now ago to the start of next month
if (($currentYear + ($currentMonth + $this->rinterval) / 12) > 2099) return $this->_repetitions;
$currentMonthStart = JevDate::mktime(0, 0, 0, $currentMonth + $this->rinterval, 1, $currentYear);
}
}
// This is byday
else
{
$days = explode(",", str_replace(" ", "", $this->byday));
// TODO I should also iterate over week number if this is used
//$weeknumbers = explode(",",str_replace(" ", "", $this->byweekno));
if ($this->bysetpos != "")
{
$newdays = array();
$setpositions = explode(",", str_replace(" ", "", $this->bysetpos));
foreach ($setpositions as $setposition)
{
foreach ($days as $day)
{
if (strpos($setposition, "+") === false && strpos($setposition, "-") === false)
{
$setposition = "+" . $setposition;
}
$newdays[] = $setposition . $day;
}
}
$days = $newdays;
$this->byday = implode(",", $days);
}
$start = $dtstart;
$end = $dtend;
$countRepeats = 0;
$currentMonthStart = JevDate::mktime(0, 0, 0, $startMonth, 1, $startYear);
// do the current month first
while ($countRepeats < $this->count && !$this->_afterUntil($currentMonthStart))
{
list ($currentMonth, $currentYear, $currentMonthStartWD) = explode(":", JevDate::strftime("%m:%Y:%w", $currentMonthStart));
$currentMonthDays = date("t", $currentMonthStart);
$this->sortByDays($days, $currentMonthStart, $dtstart);
foreach ($days as $day)
{
if ($countRepeats >= $this->count || $this->_afterUntil($start))
{
return $this->_repetitions;
}
$details = array();
preg_match("/(\+|-?)(\d?)(.+)/", $day, $details);
if (count($details) != 4)
{
echo "<br/><br/><b>PROBLEMS with $day</b><br/><br/>";
return $this->_repetitions;
}
else
{
list($temp, $plusminus, $weeknumber, $dayname) = $details;
if (StringHelper::strlen($plusminus) == 0) $plusminus = "+";
if (StringHelper::strlen($weeknumber) == 0) $weeknumber = 1;
$multiplier = $plusminus == "+" ? 1 : -1;
// always check for dtstart (nothing is allowed earlier)
if ($plusminus == "-")
{
//echo "count back $weeknumber weeks on $dayname<br/>";
$startLast = date("t", $currentMonthStart);
$currentMonthEndWD = ($startLast - 1 + $currentMonthStartWD) % 7;
$adjustment = $startLast - (7 + $currentMonthEndWD - $weekdayMap[$dayname]) % 7;
$targetstartDay = $adjustment - ($weeknumber - 1) * 7;
}
else
{
//echo "count forward $weeknumber weeks on $dayname<br/>";
$adjustment = 1 + (7 + $weekdayMap[$dayname] - $currentMonthStartWD) % 7;
$targetstartDay = $adjustment + ($weeknumber - 1) * 7;
}
$targetStart = JevDate::mktime($startHour, $startMin, $startSecond, $currentMonth, $targetstartDay, $currentYear);
if ($currentMonth == JevDate::strftime("%m", $targetStart))
{
// WE can't just add the duration since if summer time starts/ends within the event length then the end and possibly the date could be wrong
$targetEnd = $targetStart + $duration;
$targetEnd = JevDate::mktime($endHour, $endMin, $endSecond, $currentMonth, $targetstartDay + $durationdays, $currentYear);
if ($countRepeats >= $this->count)
{
return $this->_repetitions;
}
if ($targetStart >= $dtstartMidnight && !$this->_afterUntil($targetStart))
{
$countRepeats += $this->_makeRepeat($targetStart, $targetEnd);
}
}
}
}
// now go to the start of next month
if (($currentYear + ($currentMonth + $this->rinterval) / 12) > 2099) return $this->_repetitions;
$currentMonthStart = JevDate::mktime(0, 0, 0, $currentMonth + $this->rinterval, 1, $currentYear);
}
}
return $this->_repetitions;
break;
case "WEEKLY":
$days = explode(",", str_replace(" ", "", $this->byday));
$start = $dtstart;
$end = $dtend;
$countRepeats = 0;
$currentWeekDay = JevDate::strftime("%w", $start);
// Go to the zero day of the first week (even if this is in the past)
// this will be the base from which we count the weeks and weekdays
$currentWeekStart = JevDate::strtotime("-$currentWeekDay days", $start);
// no BYDAY specified
if ($this->byday == "")
{
$daynames = array("SU", "MO", "TU", "WE", "TH", "FR", "SA", "SU");
$this->byday = "+" . $daynames[$currentWeekDay];
$days = array($this->byday);
}
while ($countRepeats < $this->count && !$this->_afterUntil($currentWeekStart))
{
list ($currentDay, $currentMonth, $currentYear) = explode(":", JevDate::strftime("%d:%m:%Y", $currentWeekStart));
foreach ($days as $day)
{
if ($countRepeats >= $this->count || $this->_afterUntil($start))
{
return $this->_repetitions;
}
$details = array();
preg_match("/(\+|-?)(\d?)(.+)/", $day, $details);
if (count($details) != 4)
{
continue;
echo "<br/><br/><b>PROBLEMS with $day</b><br/><br/>";
return $this->_repetitions;
}
else
{
list($temp, $plusminus, $daynumber, $dayname) = $details;
if (StringHelper::strlen($plusminus) == 0) $plusminus = "+";
// this is not relevant for weekly recurrence ?!?!?
//if (StringHelper::strlen($daynumber)==0) $daynumber=1;
$multiplier = $plusminus == "+" ? 1 : -1;
if ($plusminus == "-")
{
// TODO find out if I ever have this situation?
// It would seem meaningless
}
else
{
//echo "count forward $daynumber days on $dayname<br/>";
$targetstartDay = $currentDay + $weekdayMap[$dayname];
}
$targetStart = JevDate::mktime($startHour, $startMin, $startSecond, $currentMonth, $targetstartDay, $currentYear);
// WE can't just add the duration since if summer time starts/ends within the event length then the end and possibly the date could be wrong
$targetEnd = $targetStart + $duration;
$targetEnd = JevDate::mktime($endHour, $endMin, $endSecond, $currentMonth, $targetstartDay + $durationdays, $currentYear);
if ($countRepeats >= $this->count)
{
return $this->_repetitions;
}
if ($targetStart >= $dtstartMidnight && !$this->_afterUntil($targetStart))
{
$countRepeats += $this->_makeRepeat($targetStart, $targetEnd);
}
}
}
// now go to the start of next week
if ($currentYear + ($currentMonth / 12) > 2099) return $this->_repetitions;
$currentWeekStart = JevDate::strtotime("+" . ($this->rinterval) . " weeks", $currentWeekStart);
}
return $this->_repetitions;
break;
case "DAILY":
$start = $dtstart;
$end = $dtend;
$countRepeats = 0;
$startYear = JevDate::strftime("%Y", $start);
while ($startYear < 2027 && $countRepeats < $this->count && !$this->_afterUntil($start))
{
//while ($startYear<5027 && $countRepeats < $this->count && !$this->_afterUntil($start)) {
$countRepeats += $this->_makeRepeat($start, $end);
$start = JevDate::strtotime("+" . $this->rinterval . " days", $start);
$end = JevDate::strtotime("+" . $this->rinterval . " days", $end);
$startYear = JevDate::strftime("%Y", $start);
}
return $this->_repetitions;
break;
case "IRREGULAR":
$processedDates = array();
// current date is ALWAYS a repeat
$processedDates[] = $dtstart;
$this->_makeRepeat($dtstart, $dtend);
if (is_string($this->irregulardates) && $this->irregulardates != "")
{
$this->irregulardates = @json_decode(str_replace("'", '"', trim($this->irregulardates, '"')));
if (is_array($this->_irregulardates))
{
array_walk($this->irregulardates, function (& $item, $index) {
if (!is_int($item))
{
$item = JevDate::strtotime($item . " 00:00:00");
}
});
}
}
if (!is_array($this->irregulardates))
{
$this->irregulardates = array();
}
sort($this->irregulardates);
foreach ($this->irregulardates as $irregulardate)
{
// avoid duplicate values
if (in_array($irregulardate, $processedDates))
{
//continue;
}
$processedDates[] = $irregulardate;
// find the start and end times of the initial event
$irregulardate += ($dtstart - $dtstartMidnight);
$this->_makeRepeat($irregulardate, $irregulardate + $duration);
}
return $this->_repetitions;
break;
default:
echo "UNKNOWN TYPE<br/>";
return $this->_repetitions;
break;
}
}
/**
* Creates a repeat if not an exception date returns 1 anyway
*
* @param unknown_type $start
* @param unknown_type $end
*
* @return unknown
*/
public function _makeRepeat($start, $end)
{
if (!isset($this->_repetitions)) $this->_repetitions = array();
$db = Factory::getDbo();
$repeat = new iCalRepetition($db);
$repeat->eventid = $this->eventid;
// TODO CHECK THIS logic
$repeat->startrepeat = JevDate::strftime('%Y-%m-%d %H:%M:%S', $start);
// iCal for whole day uses 00:00:00 on the next day JEvents uses 23:59:59 on the same day
list ($h, $m, $s) = explode(":", JevDate::strftime("%H:%M:%S", $end));
if (($h + $m + $s) == 0)
{
// $repeat->endrepeat = JevDate::strftime('%Y-%m-%d 23:59:59',($end-86400));
$duration = $end - $start;
$repeat->endrepeat = JevDate::strftime('%Y-%m-%d %H:%M:%S', $start + $duration - 1);
//$repeat->endrepeat = JevDate::strftime('%Y-%m-%d 23:59:59',$end);
}
else
{
$repeat->endrepeat = JevDate::strftime('%Y-%m-%d %H:%M:%S', $end);
}
$repeat->duplicatecheck = md5($repeat->eventid . $start);
// Double check its not in the list of exception dates
foreach ($this->_exdate as $exdate)
{
// compare based on YYYYMMDD since exceptions are(normally) whole day, "EXDATE;VALUE=DATE"
if (JevDate::strftime('%Y%m%d', $exdate) == JevDate::strftime('%Y%m%d', $start))
{
// return 0;
// exceptions count
return 1;
}
}
// if we are saving a set of irregular repeats initially they have the same time so the duplicatecheck fails
// so spoof this for repeat sother than the first
if (in_array($repeat, $this->_repetitions))
{
static $duplicates = 1;
$repeat->duplicatecheck = md5($repeat->eventid . $start . " " . $duplicates);
$duplicates++;
}
$this->_repetitions[] = $repeat;
return 1;
}
public function _afterUntil($testDate)
{
if (StringHelper::strlen($this->until) == 0 || $this->until === 0) return false;
if (!isset($this->_untilMidnight))
{
list ($d, $m, $y) = explode(":", JevDate::strftime("%d:%m:%Y", $this->until));
$this->_untilMidnight = JevDate::mktime(23, 59, 59, $m, $d, $y);
}
if (StringHelper::strlen($this->until) > 0 && $testDate > intval($this->_untilMidnight))
{
return true;
}
else return false;
}
/**
* Technically this is very complicated
* see http://www.w3.org/2002/12/cal/rfc2445 ad search for "BYxxx rule parts modify the recurrence in some manner"
*
* Priority to 'analysis' should therefore be (??)
*
* FREQ=YEARLY
* BYMONTH
* BYWEEKNO
* BYYEARDAY
* FREQ=MONTHLY ??
* BYMONTHDAY
* FREQ=WEEKLY ??
* BYDAY
* FREQ=DAILY ??
* BYHOUR, BYMINUTE, BYSECOND
* BYSETPOS
*
* INTERVAL always applies to FREQ
*
* So if I go over step in freq units adding the BYMONTH to YEARLY etc.
* then restricting DAILY with BYMONTH (do an excessive loop in this situation and test if the rules hols
* until I get some better logic)
*/
/**
* sort the by days string for negative values so that we start at the beginning of the month/start date
*
* @param unknown_type $days
* @param unknown_type $currentMonthStart
* @param unknown_type $dtstart
*/
public function sortByDays(&$days, $currentMonthStart, $dtstart)
{
if (count($days) == 0) return;
// only sort negative values
if (strpos($days[0], "-") === false) return;
static $weekdayMap = array("SU" => 0, "MO" => 1, "TU" => 2, "WE" => 3, "TH" => 4, "FR" => 5, "SA" => 6);
static $weekdayReverseMap = array("SU", "MO", "TU", "WE", "TH", "FR", "SA");
list ($currentMonth, $currentYear) = explode(":", JevDate::strftime("%m:%Y", $currentMonthStart));
list ($startMonth, $startYear) = explode(":", JevDate::strftime("%m:%Y", $dtstart));
if ($startMonth == $currentMonth && $startYear == $currentYear)
{
$startdate = $dtstart;
}
else
{
$startdate = $currentMonthStart;
}
$startWD = JevDate::strftime("%w", $startdate);
$sorteddays = array();
// start from week -6 and go forward (overkill I know)
for ($w = -6; $w < 0; $w++)
{
// now loop over the week starting at the appropriate day fo the week
for ($i = 0; $i < 7; $i++)
{
$wd = ($startWD + $i) % 7;
$check = strval($w) . $weekdayReverseMap[$wd];
if (in_array($check, $days)) $sorteddays[] = $check;
}
}
$days = $sorteddays;
}
public function dumpData()
{
echo "Freq : $this->freq <br/>";
echo "Interval : " . $this->data['INTERVAL'] . "<br/>";
switch ($this->freq)
{
case "YEARLY":
echo "By Month : " . $this->data['BYMONTH'] . "<br/>";
break;
case "MONTHLY":
echo "By Day : " . $this->data['BYDAY'] . "<br/>";
$days = explode(",", str_replace(" ", "", $this->data['BYDAY']));
foreach ($days as $day)
{
$details = array();
preg_match("/(\+|-?)(\d?)(.+)/", $day, $details);
if (count($details) != 4) echo "<br/><br/><b>PROBLEMS with $day</b><br/><br/>";
else
{
if (StringHelper::strlen($details[1]) == 0) $details[1] = "+";
echo "Event repeat details<br/>";
if ($details[1] == "-") echo "count back $details[2] weeks on $details[3]<br/>";
else echo "count forward $details[2] weeks on $details[3]<br/>";
// Note if no number given then EVERY specified day!!
}
}
break;
case "WEEKLY":
echo "By Day : " . $this->data['BYDAY'] . "<br/>";
$days = explode(",", str_replace(" ", "", $this->data['BYDAY']));
foreach ($days as $day)
{
$details = array();
preg_match("/(\+|-?)(\d?)(.+)/", $day, $details);
if (count($details) != 4) echo "<br/><br/><b>PROBLEMS with $day</b><br/><br/>";
else
{
if (StringHelper::strlen($details[1]) == 0) $details[1] = "+";
echo "Event repeat details<br/>";
if ($details[1] == "-") echo "count back $details[2] weeks on $details[3]<br/>";
else echo "count forward $details[2] weeks on $details[3]<br/>";
// Note if no number given then EVERY specified day!!
}
}
break;
default:
echo "UNKNOWN TYPE<br/>";
break;
}
// Doesnt yet deal with INTERVAL, UNTIL or COUNT
print_r($this->data);
echo "<hr/>";
}
public function eventInPeriod($startDate, $endDate, $start, $end)
{
// stupid verison to start that scans through EVERY single day!!!
$checkDate = $startDate;
while ($checkDate <= $endDate)
{
if ($this->checkDate($checkDate, $start, $end)) return true;
$checkDate += 24 * 60 * 60;
}
return false;
}
public function checkDate($test, $start, $end)
{
if ($test >= $start && $test <= $end) return true;
else return false;
}
public function isDuplicate()
{
$sql = "SELECT rr_id from #__jevents_rrule as rr WHERE rr.eventid = '" . $this->eventid . "'";
$this->_db->setQuery($sql);
$matches = $this->_db->loadObjectList();
if (count($matches) > 0 && isset($matches[0]->rr_id))
{
return $matches[0]->rr_id;
}
return false;
}
}