b0y-101 Mini Shell


Current Path : E:/www/plan/components/com_jevents/libraries/
File Upload :
Current File : E:/www/plan/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-2019 GWE Systems 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\String\StringHelper;

class iCalRRule extends JTable  {

	/** @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 );
	}

	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	= JFactory::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;
	}


	/**
	 * Pseudo Constructor
	 *
	 * @param iCal Entry parsed from ICS file as an array $ice
	 * @return n/a
	 */
	public static  function iCalRRuleFromData($rrule){
		$db	= JFactory::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","");
			}
			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;
	}
	/**
	 * 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	= JFactory::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 (JString::strlen($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 (JString::strlen($this->until)>0 && $testDate>intval($this->_untilMidnight)) {
			return true;
		}
		else return false;
	}


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

	}

	/**
	 * 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)
	 */


	/**
	 * 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 (JString::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;

					// 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 (JString::strlen($plusminus)==0) $plusminus="+";
								if (JString::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)){
										$currentYear = JevDate::strftime("%Y",$start);
										$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 (JString::strlen($plusminus)==0) $plusminus="+";
								if (JString::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 (JString::strlen($plusminus)==0) $plusminus="+";
								if (JString::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 (JString::strlen($plusminus)==0) $plusminus="+";
							// this is not relevant for weekly recurrence ?!?!?
							//if (JString::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;
		}
	}

	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 (JString::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 (JString::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 checkDate($test, $start, $end){
		if ($test>=$start && $test<=$end) return true;
		else return false;
	}

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

Copyright © 2019 by b0y-101