| Total Complexity | 203 |
| Total Lines | 1112 |
| Duplicated Lines | 0 % |
| Changes | 0 | ||
Complex classes like calendar_rrule often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use calendar_rrule, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 33 | class calendar_rrule implements Iterator |
||
| 34 | { |
||
| 35 | /** |
||
| 36 | * No recurrence |
||
| 37 | */ |
||
| 38 | const NONE = 0; |
||
| 39 | /** |
||
| 40 | * Daily recurrence |
||
| 41 | */ |
||
| 42 | const DAILY = 1; |
||
| 43 | /** |
||
| 44 | * Weekly recurrance on day(s) specified by bitfield in $data |
||
| 45 | */ |
||
| 46 | const WEEKLY = 2; |
||
| 47 | /** |
||
| 48 | * Monthly recurrance iCal: monthly_bymonthday |
||
| 49 | */ |
||
| 50 | const MONTHLY_MDAY = 3; |
||
| 51 | /** |
||
| 52 | * Monthly recurrance iCal: BYDAY (by weekday, eg. 1st Friday of month) |
||
| 53 | */ |
||
| 54 | const MONTHLY_WDAY = 4; |
||
| 55 | /** |
||
| 56 | * Yearly recurrance |
||
| 57 | */ |
||
| 58 | const YEARLY = 5; |
||
| 59 | /** |
||
| 60 | * Hourly recurrance |
||
| 61 | */ |
||
| 62 | const HOURLY = 8; |
||
| 63 | /** |
||
| 64 | * Minutely recurrance |
||
| 65 | */ |
||
| 66 | const MINUTELY = 7; |
||
| 67 | |||
| 68 | /** |
||
| 69 | * Translate recure types to labels |
||
| 70 | * |
||
| 71 | * @var array |
||
| 72 | */ |
||
| 73 | static public $types = Array( |
||
| 74 | self::NONE => 'None', |
||
| 75 | self::DAILY => 'Daily', |
||
| 76 | self::WEEKLY => 'Weekly', |
||
| 77 | self::MONTHLY_WDAY => 'Monthly (by day)', |
||
| 78 | self::MONTHLY_MDAY => 'Monthly (by date)', |
||
| 79 | self::YEARLY => 'Yearly', |
||
| 80 | ); |
||
| 81 | |||
| 82 | /** |
||
| 83 | * @var array $recur_egw2ical_2_0 converstaion of egw recur-type => ical FREQ |
||
| 84 | */ |
||
| 85 | static private $recur_egw2ical_2_0 = array( |
||
| 86 | self::DAILY => 'DAILY', |
||
| 87 | self::WEEKLY => 'WEEKLY', |
||
| 88 | self::MONTHLY_WDAY => 'MONTHLY', // BYDAY={1..7, -1}{MO..SO, last workday} |
||
| 89 | self::MONTHLY_MDAY => 'MONTHLY', // BYMONHTDAY={1..31, -1 for last day of month} |
||
| 90 | self::YEARLY => 'YEARLY', |
||
| 91 | self::HOURLY => 'HOURLY', |
||
| 92 | self::MINUTELY => 'MINUTELY', |
||
| 93 | ); |
||
| 94 | |||
| 95 | /** |
||
| 96 | * @var array $recur_egw2ical_1_0 converstaion of egw recur-type => ical FREQ |
||
| 97 | */ |
||
| 98 | static private $recur_egw2ical_1_0 = array( |
||
| 99 | self::DAILY => 'D', |
||
| 100 | self::WEEKLY => 'W', |
||
| 101 | self::MONTHLY_WDAY => 'MP', // BYDAY={1..7,-1}{MO..SO, last workday} |
||
| 102 | self::MONTHLY_MDAY => 'MD', // BYMONHTDAY={1..31,-1} |
||
| 103 | self::YEARLY => 'YM', |
||
| 104 | ); |
||
| 105 | |||
| 106 | /** |
||
| 107 | * RRule type: NONE, DAILY, WEEKLY, MONTHLY_MDAY, MONTHLY_WDAY, YEARLY |
||
| 108 | * |
||
| 109 | * @var int |
||
| 110 | */ |
||
| 111 | public $type = self::NONE; |
||
| 112 | |||
| 113 | /** |
||
| 114 | * Interval |
||
| 115 | * |
||
| 116 | * @var int |
||
| 117 | */ |
||
| 118 | public $interval = 1; |
||
| 119 | |||
| 120 | /** |
||
| 121 | * Number for monthly byday: 1, ..., 5, -1=last weekday of month |
||
| 122 | * |
||
| 123 | * EGroupware Calendar does NOT explicitly store it, it's only implicitly defined by series start date |
||
| 124 | * |
||
| 125 | * @var int |
||
| 126 | */ |
||
| 127 | public $monthly_byday_num; |
||
| 128 | |||
| 129 | /** |
||
| 130 | * Number for monthly bymonthday: 1, ..., 31, -1=last day of month |
||
| 131 | * |
||
| 132 | * EGroupware Calendar does NOT explicitly store it, it's only implicitly defined by series start date |
||
| 133 | * |
||
| 134 | * @var int |
||
| 135 | */ |
||
| 136 | public $monthly_bymonthday; |
||
| 137 | |||
| 138 | /** |
||
| 139 | * Enddate of recurring event or null, if not ending |
||
| 140 | * |
||
| 141 | * @var DateTime |
||
| 142 | */ |
||
| 143 | public $enddate; |
||
| 144 | |||
| 145 | /** |
||
| 146 | * Enddate of recurring event, as Ymd integer (eg. 20091111) |
||
| 147 | * |
||
| 148 | * Or 5 years in future, if no enddate. So iterator is always limited. |
||
| 149 | * |
||
| 150 | * @var int |
||
| 151 | */ |
||
| 152 | public $enddate_ymd; |
||
| 153 | |||
| 154 | /** |
||
| 155 | * Enddate of recurring event, as timestamp |
||
| 156 | * |
||
| 157 | * Or 5 years in future, if no enddate. So iterator is always limited. |
||
| 158 | * |
||
| 159 | * @var int |
||
| 160 | */ |
||
| 161 | public $enddate_ts; |
||
| 162 | |||
| 163 | const SUNDAY = 1; |
||
| 164 | const MONDAY = 2; |
||
| 165 | const TUESDAY = 4; |
||
| 166 | const WEDNESDAY = 8; |
||
| 167 | const THURSDAY = 16; |
||
| 168 | const FRIDAY = 32; |
||
| 169 | const SATURDAY = 64; |
||
| 170 | const WORKDAYS = 62; // Mo, ..., Fr |
||
| 171 | const ALLDAYS = 127; |
||
| 172 | /** |
||
| 173 | * Translate weekday bitmasks to labels |
||
| 174 | * |
||
| 175 | * @var array |
||
| 176 | */ |
||
| 177 | static public $days = array( |
||
| 178 | self::MONDAY => 'Monday', |
||
| 179 | self::TUESDAY => 'Tuesday', |
||
| 180 | self::WEDNESDAY => 'Wednesday', |
||
| 181 | self::THURSDAY => 'Thursday', |
||
| 182 | self::FRIDAY => 'Friday', |
||
| 183 | self::SATURDAY => 'Saturday', |
||
| 184 | self::SUNDAY => 'Sunday', |
||
| 185 | ); |
||
| 186 | /** |
||
| 187 | * Bitmask of valid weekdays for weekly repeating events: self::SUNDAY|...|self::SATURDAY |
||
| 188 | * |
||
| 189 | * @var integer |
||
| 190 | */ |
||
| 191 | public $weekdays; |
||
| 192 | |||
| 193 | /** |
||
| 194 | * Array of exception dates (Ymd strings) |
||
| 195 | * |
||
| 196 | * @var array |
||
| 197 | */ |
||
| 198 | public $exceptions=array(); |
||
| 199 | |||
| 200 | /** |
||
| 201 | * Array of exceptions as DateTime/egw_time objects |
||
| 202 | * |
||
| 203 | * @var array |
||
| 204 | */ |
||
| 205 | public $exceptions_objs=array(); |
||
| 206 | |||
| 207 | /** |
||
| 208 | * Starttime of series |
||
| 209 | * |
||
| 210 | * @var Api\DateTime |
||
| 211 | */ |
||
| 212 | public $time; |
||
| 213 | |||
| 214 | /** |
||
| 215 | * Current "position" / time |
||
| 216 | * |
||
| 217 | * @var Api\DateTime |
||
| 218 | */ |
||
| 219 | public $current; |
||
| 220 | |||
| 221 | /** |
||
| 222 | * Last day of the week according to user preferences |
||
| 223 | * |
||
| 224 | * @var int |
||
| 225 | */ |
||
| 226 | protected $lastdayofweek; |
||
| 227 | |||
| 228 | /** |
||
| 229 | * Cached timezone data |
||
| 230 | * |
||
| 231 | * @var array id => data |
||
| 232 | */ |
||
| 233 | protected static $tz_cache = array(); |
||
| 234 | |||
| 235 | /** |
||
| 236 | * Constructor |
||
| 237 | * |
||
| 238 | * The constructor accepts on DateTime (or decendents like egw_date) for all times, to work timezone-correct. |
||
| 239 | * The timezone of the event is determined by timezone of $time, other times get converted to that timezone. |
||
| 240 | * |
||
| 241 | * @param DateTime $time start of event in it's own timezone |
||
| 242 | * @param int $type self::NONE, self::DAILY, ..., self::YEARLY |
||
| 243 | * @param int $interval =1 1, 2, ... |
||
| 244 | * @param DateTime $enddate =null enddate or null for no enddate (in which case we user '+5 year' on $time) |
||
| 245 | * @param int $weekdays =0 self::SUNDAY=1|self::MONDAY=2|...|self::SATURDAY=64 |
||
| 246 | * @param array $exceptions =null DateTime objects with exceptions |
||
| 247 | */ |
||
| 248 | public function __construct(DateTime $time,$type,$interval=1,DateTime $enddate=null,$weekdays=0,array $exceptions=null) |
||
| 249 | { |
||
| 250 | switch($GLOBALS['egw_info']['user']['preferences']['calendar']['weekdaystarts']) |
||
| 251 | { |
||
| 252 | case 'Sunday': |
||
| 253 | $this->lastdayofweek = self::SATURDAY; |
||
| 254 | break; |
||
| 255 | case 'Saturday': |
||
| 256 | $this->lastdayofweek = self::FRIDAY; |
||
| 257 | break; |
||
| 258 | default: // Monday |
||
| 259 | $this->lastdayofweek = self::SUNDAY; |
||
| 260 | } |
||
| 261 | |||
| 262 | $this->time = $time instanceof Api\DateTime ? $time : new Api\DateTime($time); |
||
| 263 | |||
| 264 | if (!in_array($type,array(self::NONE, self::DAILY, self::WEEKLY, self::MONTHLY_MDAY, self::MONTHLY_WDAY, self::YEARLY, self::HOURLY, self::MINUTELY))) |
||
| 265 | { |
||
| 266 | throw new Api\Exception\WrongParameter(__METHOD__."($time,$type,$interval,$enddate,$weekdays,...) type $type is NOT valid!"); |
||
| 267 | } |
||
| 268 | $this->type = $type; |
||
| 269 | |||
| 270 | // determine only implicit defined rules for RRULE=MONTHLY,BYDAY={-1, 1, ..., 5}{MO,..,SU} |
||
| 271 | if ($type == self::MONTHLY_WDAY) |
||
| 272 | { |
||
| 273 | // check for last week of month |
||
| 274 | if (($day = $this->time->format('d')) >= 21 && $day > self::daysInMonth($this->time)-7) |
||
| 275 | { |
||
| 276 | $this->monthly_byday_num = -1; |
||
| 277 | } |
||
| 278 | else |
||
| 279 | { |
||
| 280 | $this->monthly_byday_num = 1 + floor(($this->time->format('d')-1) / 7); |
||
| 281 | } |
||
| 282 | } |
||
| 283 | elseif($type == self::MONTHLY_MDAY) |
||
| 284 | { |
||
| 285 | $this->monthly_bymonthday = (int)$this->time->format('d'); |
||
| 286 | // check for last day of month |
||
| 287 | if ($this->monthly_bymonthday >= 28) |
||
| 288 | { |
||
| 289 | $test = clone $this->time; |
||
| 290 | $test->modify('1 day'); |
||
| 291 | if ($test->format('m') != $this->time->format('m')) |
||
| 292 | { |
||
| 293 | $this->monthly_bymonthday = -1; |
||
| 294 | } |
||
| 295 | } |
||
| 296 | } |
||
| 297 | |||
| 298 | if ((int)$interval < 1) |
||
| 299 | { |
||
| 300 | $interval = 1; // calendar stores no (extra) interval as null, so using default 1 here |
||
| 301 | } |
||
| 302 | $this->interval = (int)$interval; |
||
| 303 | |||
| 304 | $this->enddate = $enddate; |
||
| 305 | // no recurrence --> current date is enddate |
||
| 306 | if ($type == self::NONE) |
||
| 307 | { |
||
| 308 | $enddate = clone $this->time; |
||
| 309 | } |
||
| 310 | // set a maximum of 5 years if no enddate given |
||
| 311 | elseif (is_null($enddate)) |
||
| 312 | { |
||
| 313 | $enddate = clone $this->time; |
||
| 314 | $enddate->modify('5 year'); |
||
| 315 | } |
||
| 316 | // convert enddate to timezone of time, if necessary |
||
| 317 | else |
||
| 318 | { |
||
| 319 | $enddate->setTimezone($this->time->getTimezone()); |
||
| 320 | } |
||
| 321 | $this->enddate_ymd = (int)$enddate->format('Ymd'); |
||
| 322 | $this->enddate_ts = $enddate->format('ts'); |
||
|
|
|||
| 323 | |||
| 324 | // if no valid weekdays are given for weekly repeating, we use just the current weekday |
||
| 325 | if (!($this->weekdays = (int)$weekdays) && ($type == self::WEEKLY || $type == self::MONTHLY_WDAY)) |
||
| 326 | { |
||
| 327 | $this->weekdays = self::getWeekday($this->time); |
||
| 328 | } |
||
| 329 | if ($exceptions) |
||
| 330 | { |
||
| 331 | foreach($exceptions as $exception) |
||
| 332 | { |
||
| 333 | $exception->setTimezone($this->time->getTimezone()); |
||
| 334 | $this->exceptions[] = $exception->format('Ymd'); |
||
| 335 | } |
||
| 336 | $this->exceptions_objs = $exceptions; |
||
| 337 | } |
||
| 338 | } |
||
| 339 | |||
| 340 | /** |
||
| 341 | * Get recurrence interval duration in seconds |
||
| 342 | * |
||
| 343 | * @param int $type self::(DAILY|WEEKLY|MONTHLY_(M|W)DAY|YEARLY) |
||
| 344 | * @param int $interval =1 |
||
| 345 | * @return int |
||
| 346 | */ |
||
| 347 | public static function recurrence_interval($type, $interval=1) |
||
| 348 | { |
||
| 349 | switch($type) |
||
| 350 | { |
||
| 351 | case self::DAILY: |
||
| 352 | $duration = 24*3600; |
||
| 353 | break; |
||
| 354 | case self::WEEKLY: |
||
| 355 | $duration = 7*24*3600; |
||
| 356 | break; |
||
| 357 | case self::MONTHLY_MDAY: |
||
| 358 | case self::MONTHLY_WDAY: |
||
| 359 | $duration = 31*24*3600; |
||
| 360 | break; |
||
| 361 | case self::YEARLY: |
||
| 362 | $duration = 366*24*3600; |
||
| 363 | break; |
||
| 364 | } |
||
| 365 | if ($interval > 1) $duration *= $interval; |
||
| 366 | |||
| 367 | return $duration; |
||
| 368 | } |
||
| 369 | |||
| 370 | /** |
||
| 371 | * Get number of days in month of given date |
||
| 372 | * |
||
| 373 | * @param DateTime $time |
||
| 374 | * @return int |
||
| 375 | */ |
||
| 376 | private static function daysInMonth(DateTime $time) |
||
| 383 | } |
||
| 384 | |||
| 385 | /** |
||
| 386 | * Return the current element |
||
| 387 | * |
||
| 388 | * @return DateTime |
||
| 389 | */ |
||
| 390 | public function current() |
||
| 391 | { |
||
| 392 | return clone $this->current; |
||
| 393 | } |
||
| 394 | |||
| 395 | /** |
||
| 396 | * Return the key of the current element, we use a Ymd integer as key |
||
| 397 | * |
||
| 398 | * @return int |
||
| 399 | */ |
||
| 400 | public function key() |
||
| 401 | { |
||
| 402 | return (int)$this->current->format('Ymd'); |
||
| 403 | } |
||
| 404 | |||
| 405 | /** |
||
| 406 | * Move forward to next recurence, not caring for exceptions |
||
| 407 | */ |
||
| 408 | public function next_no_exception() |
||
| 409 | { |
||
| 410 | switch($this->type) |
||
| 411 | { |
||
| 412 | case self::NONE: // need to add at least one day, to end "series", as enddate == current date |
||
| 413 | case self::DAILY: |
||
| 414 | $this->current->modify($this->interval.' day'); |
||
| 415 | break; |
||
| 416 | |||
| 417 | case self::WEEKLY: |
||
| 418 | // advance to next valid weekday |
||
| 419 | do |
||
| 420 | { |
||
| 421 | // interval in weekly means event runs on valid days eg. each 2. week |
||
| 422 | // --> on the last day of the week we have to additionally advance interval-1 weeks |
||
| 423 | if ($this->interval > 1 && self::getWeekday($this->current) == $this->lastdayofweek) |
||
| 424 | { |
||
| 425 | $this->current->modify(($this->interval-1).' week'); |
||
| 426 | } |
||
| 427 | $this->current->modify('1 day'); |
||
| 428 | //echo __METHOD__.'() '.$this->current->format('l').', '.$this->current.": $this->weekdays & ".self::getWeekday($this->current)."<br />\n"; |
||
| 429 | } |
||
| 430 | while(!($this->weekdays & self::getWeekday($this->current))); |
||
| 431 | break; |
||
| 432 | |||
| 433 | case self::MONTHLY_WDAY: // iCal: BYDAY={1, ..., 5, -1}{MO..SO} |
||
| 434 | // advance to start of next month |
||
| 435 | list($year,$month) = explode('-',$this->current->format('Y-m')); |
||
| 436 | $month += $this->interval+($this->monthly_byday_num < 0 ? 1 : 0); |
||
| 437 | $this->current->setDate($year,$month,$this->monthly_byday_num < 0 ? 0 : 1); |
||
| 438 | //echo __METHOD__."() $this->monthly_byday_num".substr(self::$days[$this->monthly_byday_wday],0,2).": setDate($year,$month,1): ".$this->current->format('l').', '.$this->current."<br />\n"; |
||
| 439 | // now advance to n-th week |
||
| 440 | if ($this->monthly_byday_num > 1) |
||
| 441 | { |
||
| 442 | $this->current->modify(($this->monthly_byday_num-1).' week'); |
||
| 443 | //echo __METHOD__."() $this->monthly_byday_num".substr(self::$days[$this->monthly_byday_wday],0,2).': modify('.($this->monthly_byday_num-1).' week): '.$this->current->format('l').', '.$this->current."<br />\n"; |
||
| 444 | } |
||
| 445 | // advance to given weekday |
||
| 446 | while(!($this->weekdays & self::getWeekday($this->current))) |
||
| 447 | { |
||
| 448 | $this->current->modify(($this->monthly_byday_num < 0 ? -1 : 1).' day'); |
||
| 449 | //echo __METHOD__."() $this->monthly_byday_num".substr(self::$days[$this->monthly_byday_wday],0,2).': modify(1 day): '.$this->current->format('l').', '.$this->current."<br />\n"; |
||
| 450 | } |
||
| 451 | break; |
||
| 452 | |||
| 453 | case self::MONTHLY_MDAY: // iCal: monthly_bymonthday={1, ..., 31, -1} |
||
| 454 | list($year,$month) = explode('-',$this->current->format('Y-m')); |
||
| 455 | $day = $this->monthly_bymonthday+($this->monthly_bymonthday < 0 ? 1 : 0); |
||
| 456 | $month += $this->interval+($this->monthly_bymonthday < 0 ? 1 : 0); |
||
| 457 | $this->current->setDate($year,$month,$day); |
||
| 458 | //echo __METHOD__."() setDate($year,$month,$day): ".$this->current->format('l').', '.$this->current."<br />\n"; |
||
| 459 | break; |
||
| 460 | |||
| 461 | case self::YEARLY: |
||
| 462 | $this->current->modify($this->interval.' year'); |
||
| 463 | break; |
||
| 464 | |||
| 465 | case self::HOURLY: |
||
| 466 | $this->current->modify($this->interval.' hour'); |
||
| 467 | break; |
||
| 468 | |||
| 469 | case self::MINUTELY: |
||
| 470 | $this->current->modify($this->interval.' minute'); |
||
| 471 | break; |
||
| 472 | |||
| 473 | default: |
||
| 474 | throw new Api\Exception\AssertionFailed(__METHOD__."() invalid type #$this->type !"); |
||
| 475 | } |
||
| 476 | } |
||
| 477 | |||
| 478 | /** |
||
| 479 | * Move forward to next recurence, taking into account exceptions |
||
| 480 | */ |
||
| 481 | public function next() |
||
| 482 | { |
||
| 483 | do |
||
| 484 | { |
||
| 485 | $this->next_no_exception(); |
||
| 486 | } |
||
| 487 | while($this->exceptions && in_array($this->current->format('Ymd'),$this->exceptions)); |
||
| 488 | } |
||
| 489 | |||
| 490 | /** |
||
| 491 | * Get weekday of $time as self::SUNDAY=1, ..., self::SATURDAY=64 integer mask |
||
| 492 | * |
||
| 493 | * @param DateTime $time |
||
| 494 | * @return int self::SUNDAY=1, ..., self::SATURDAY=64 |
||
| 495 | */ |
||
| 496 | static protected function getWeekday(DateTime $time) |
||
| 497 | { |
||
| 498 | //echo __METHOD__.'('.$time->format('l').' '.$time.') 1 << '.$time->format('w').' = '.(1 << (int)$time->format('w'))."<br />\n"; |
||
| 499 | return 1 << (int)$time->format('w'); |
||
| 500 | } |
||
| 501 | |||
| 502 | /** |
||
| 503 | * Get datetime of n-th event, 1. is original event-time |
||
| 504 | * |
||
| 505 | * This is identical on COUNT parameter of RRULE is evaluated, exceptions are NOT taken into account! |
||
| 506 | * |
||
| 507 | * @param int $count |
||
| 508 | * @return DateTime |
||
| 509 | */ |
||
| 510 | public function count2date($count) |
||
| 526 | } |
||
| 527 | |||
| 528 | /** |
||
| 529 | * Fix enddates which are not on a recurrence, eg. for a on Monday recurring weekly event a Tuesday |
||
| 530 | * |
||
| 531 | * @return DateTime |
||
| 532 | */ |
||
| 533 | public function normalize_enddate() |
||
| 534 | { |
||
| 535 | $this->rewind(); |
||
| 536 | while ($this->current < $this->enddate) |
||
| 537 | { |
||
| 538 | $previous = clone $this->current; |
||
| 539 | $this->next_no_exception(); |
||
| 540 | } |
||
| 541 | // if enddate is now before next acurrence, but not on same day, we use previous recurrence |
||
| 542 | // this can happen if client gives an enddate which is NOT a recurrence date |
||
| 543 | // eg. for a on Monday recurring weekly event a Tuesday as enddate |
||
| 544 | if ($this->enddate < $this->current && $this->current->format('Ymd') != $this->enddate->format('Ymd')) |
||
| 545 | { |
||
| 546 | $last = $previous; |
||
| 547 | } |
||
| 548 | else |
||
| 549 | { |
||
| 550 | $last = clone $this->current; |
||
| 551 | } |
||
| 552 | return $last; |
||
| 553 | } |
||
| 554 | |||
| 555 | /** |
||
| 556 | * Rewind the Iterator to the first element (called at beginning of foreach loop) |
||
| 557 | */ |
||
| 558 | public function rewind() |
||
| 566 | } |
||
| 567 | } |
||
| 568 | |||
| 569 | /** |
||
| 570 | * Checks if current position is valid |
||
| 571 | * |
||
| 572 | * @param boolean $use_just_date =false default use also time |
||
| 573 | * @return boolean |
||
| 574 | */ |
||
| 575 | public function valid($use_just_date=false) |
||
| 582 | } |
||
| 583 | |||
| 584 | /** |
||
| 585 | * Return string represenation of RRule |
||
| 586 | * |
||
| 587 | * @return string |
||
| 588 | */ |
||
| 589 | function __toString( ) |
||
| 658 | } |
||
| 659 | |||
| 660 | /** |
||
| 661 | * Generate a VEVENT RRULE |
||
| 662 | * @param string $version ='2.0' could be '1.0' too |
||
| 663 | * |
||
| 664 | * $return array vCalendar RRULE |
||
| 665 | */ |
||
| 666 | public function generate_rrule($version='2.0') |
||
| 667 | { |
||
| 668 | $repeat_days = array(); |
||
| 669 | $rrule = array(); |
||
| 670 | |||
| 671 | if ($this->type == self::NONE) return false; // no recuring event |
||
| 672 | |||
| 673 | if ($version == '1.0') |
||
| 674 | { |
||
| 675 | $rrule['FREQ'] = self::$recur_egw2ical_1_0[$this->type] . $this->interval; |
||
| 676 | switch ($this->type) |
||
| 677 | { |
||
| 678 | case self::WEEKLY: |
||
| 679 | foreach (self::$days as $mask => $label) |
||
| 680 | { |
||
| 681 | if ($this->weekdays & $mask) |
||
| 682 | { |
||
| 683 | $repeat_days[] = strtoupper(substr($label,0,2)); |
||
| 684 | } |
||
| 685 | } |
||
| 686 | $rrule['BYDAY'] = implode(' ', $repeat_days); |
||
| 687 | $rrule['FREQ'] = $rrule['FREQ'].' '.$rrule['BYDAY']; |
||
| 688 | break; |
||
| 689 | |||
| 690 | case self::MONTHLY_MDAY: // date of the month: BYMONTDAY={1..31} |
||
| 691 | break; |
||
| 692 | |||
| 693 | case self::MONTHLY_WDAY: // weekday of the month: BDAY={1..5}+ {MO..SO} |
||
| 694 | $rrule['BYDAY'] = abs($this->monthly_byday_num); |
||
| 695 | $rrule['BYDAY'] .= ($this->monthly_byday_num < 0) ? '- ' : '+ '; |
||
| 696 | $rrule['BYDAY'] .= strtoupper(substr($this->time->format('l'),0,2)); |
||
| 697 | $rrule['FREQ'] = $rrule['FREQ'].' '.$rrule['BYDAY']; |
||
| 698 | break; |
||
| 699 | } |
||
| 700 | |||
| 701 | if (!$this->enddate) |
||
| 702 | { |
||
| 703 | $rrule['UNTIL'] = '#0'; |
||
| 704 | } |
||
| 705 | } |
||
| 706 | else // $version == '2.0' |
||
| 707 | { |
||
| 708 | $rrule['FREQ'] = self::$recur_egw2ical_2_0[$this->type]; |
||
| 709 | switch ($this->type) |
||
| 710 | { |
||
| 711 | case self::WEEKLY: |
||
| 712 | foreach (self::$days as $mask => $label) |
||
| 713 | { |
||
| 714 | if ($this->weekdays & $mask) |
||
| 715 | { |
||
| 716 | $repeat_days[] = strtoupper(substr($label,0,2)); |
||
| 717 | } |
||
| 718 | } |
||
| 719 | $rrule['BYDAY'] = implode(',', $repeat_days); |
||
| 720 | break; |
||
| 721 | |||
| 722 | case self::MONTHLY_MDAY: // date of the month: BYMONTDAY={1..31} |
||
| 723 | $rrule['BYMONTHDAY'] = $this->monthly_bymonthday; |
||
| 724 | break; |
||
| 725 | |||
| 726 | case self::MONTHLY_WDAY: // weekday of the month: BDAY={1..5}{MO..SO} |
||
| 727 | $rrule['BYDAY'] = $this->monthly_byday_num . |
||
| 728 | strtoupper(substr($this->time->format('l'),0,2)); |
||
| 729 | break; |
||
| 730 | } |
||
| 731 | if ($this->interval > 1) |
||
| 732 | { |
||
| 733 | $rrule['INTERVAL'] = $this->interval; |
||
| 734 | } |
||
| 735 | } |
||
| 736 | |||
| 737 | if ($this->enddate) |
||
| 738 | { |
||
| 739 | // our enddate is the end-time, not start-time of last event! |
||
| 740 | $this->rewind(); |
||
| 741 | $enddate = $this->current(); |
||
| 742 | do |
||
| 743 | { |
||
| 744 | $this->next_no_exception(); |
||
| 745 | $occurrence = $this->current(); |
||
| 746 | } |
||
| 747 | while ($this->valid() && ($enddate = $occurrence)); |
||
| 748 | $rrule['UNTIL'] = $enddate; |
||
| 749 | } |
||
| 750 | |||
| 751 | return $rrule; |
||
| 752 | } |
||
| 753 | |||
| 754 | /** |
||
| 755 | * Get instance for a given event array |
||
| 756 | * |
||
| 757 | * @param array $event |
||
| 758 | * @param boolean $usertime =true true: event timestamps are usertime (default for calendar_bo::(read|search), false: servertime |
||
| 759 | * @param string $to_tz timezone for exports (null for event's timezone) |
||
| 760 | * |
||
| 761 | * @return calendar_rrule false on error |
||
| 762 | */ |
||
| 763 | public static function event2rrule(array $event,$usertime=true,$to_tz=null) |
||
| 764 | { |
||
| 765 | if (!is_array($event) || !isset($event['tzid'])) return false; |
||
| 766 | if (!$to_tz) $to_tz = $event['tzid']; |
||
| 767 | $timestamp_tz = $usertime ? Api\DateTime::$user_timezone : Api\DateTime::$server_timezone; |
||
| 768 | $time = is_a($event['start'],'DateTime') ? $event['start'] : new Api\DateTime($event['start'],$timestamp_tz); |
||
| 769 | |||
| 770 | if (!isset(self::$tz_cache[$to_tz])) |
||
| 771 | { |
||
| 772 | self::$tz_cache[$to_tz] = calendar_timezones::DateTimeZone($to_tz); |
||
| 773 | } |
||
| 774 | |||
| 775 | self::rrule2tz($event, $time, $to_tz); |
||
| 776 | |||
| 777 | $time->setTimezone(self::$tz_cache[$to_tz]); |
||
| 778 | |||
| 779 | if ($event['recur_enddate']) |
||
| 780 | { |
||
| 781 | $enddate = is_a($event['recur_enddate'],'DateTime') ? clone $event['recur_enddate'] : new Api\DateTime($event['recur_enddate'],$timestamp_tz); |
||
| 782 | |||
| 783 | // Check to see if switching timezones changes the date, we'll need to adjust for that |
||
| 784 | $enddate_event_timezone = clone $enddate; |
||
| 785 | $enddate->setTimezone($timestamp_tz); |
||
| 786 | $delta = (int)$enddate_event_timezone->format('z') - (int)$enddate->format('z'); |
||
| 787 | $enddate->add("$delta days"); |
||
| 788 | |||
| 789 | $end = is_a($event['end'],'DateTime') ? clone $event['end'] : new Api\DateTime($event['end'],$timestamp_tz); |
||
| 790 | $end->setTimezone($enddate->getTimezone()); |
||
| 791 | $enddate->setTime($end->format('H'),$end->format('i'),0); |
||
| 792 | if($event['whole_day']) |
||
| 793 | { |
||
| 794 | $enddate->setTime(23,59,59); |
||
| 795 | } |
||
| 796 | } |
||
| 797 | if (is_array($event['recur_exception'])) |
||
| 798 | { |
||
| 799 | foreach($event['recur_exception'] as $exception) |
||
| 800 | { |
||
| 801 | $exceptions[] = is_a($exception,'DateTime') ? $exception : new Api\DateTime($exception,$timestamp_tz); |
||
| 802 | } |
||
| 803 | } |
||
| 804 | return new calendar_rrule($time,$event['recur_type'],$event['recur_interval'],$enddate,$event['recur_data'],$exceptions); |
||
| 805 | } |
||
| 806 | |||
| 807 | /** |
||
| 808 | * Generate a rrule from a string generated by __toString(). |
||
| 809 | * |
||
| 810 | * @param String $rrule Recurrence rule in string format, as generated by __toString() |
||
| 811 | * @param DateTime date Optional date to work from, defaults to today |
||
| 812 | */ |
||
| 813 | public static function from_string(String $rrule, DateTime $date) |
||
| 814 | { |
||
| 815 | $time = $date ? $date : new Api\DateTime(); |
||
| 816 | $type_id = self::NONE; |
||
| 817 | $interval = 1; |
||
| 818 | $enddate = null; |
||
| 819 | $weekdays = 0; |
||
| 820 | $exceptions = array(); |
||
| 821 | |||
| 822 | list($type, $sub, $conds) = explode(' (', $rrule); |
||
| 823 | if(!$conds) |
||
| 824 | { |
||
| 825 | $conds = $sub; |
||
| 826 | } |
||
| 827 | else |
||
| 828 | { |
||
| 829 | $type .= " ($sub"; |
||
| 830 | } |
||
| 831 | $conditions = explode(', ', substr($conds, 0, -1)); |
||
| 832 | |||
| 833 | foreach(static::$types as $id => $type_name) |
||
| 834 | { |
||
| 835 | $str = lang($type_name); |
||
| 836 | if($str == $type) |
||
| 837 | { |
||
| 838 | $type_id = $id; |
||
| 839 | break; |
||
| 840 | } |
||
| 841 | } |
||
| 842 | |||
| 843 | // Rejoin some extra splits for conditions with multiple values |
||
| 844 | foreach($conditions as $condition_index => $condition) |
||
| 845 | { |
||
| 846 | if(((int)$condition || strpos($condition, lang('last')) === 0) && |
||
| 847 | substr_compare( $condition, lang('day'), -strlen( lang('day') ) ) === 0) |
||
| 848 | { |
||
| 849 | $time->setDate($time->format('Y'), $time->format('m'), (int)($condition) ? (int)$condition : $time->format('t')); |
||
| 850 | unset($conditions[$condition_index]); |
||
| 851 | continue; |
||
| 852 | } |
||
| 853 | if(!strpos($condition, ':') && strpos($conditions[$condition_index-1], ':')) |
||
| 854 | { |
||
| 855 | $conditions[$condition_index-1] .= ', ' . $condition; |
||
| 856 | unset($conditions[$condition_index]); |
||
| 857 | } |
||
| 858 | } |
||
| 859 | foreach($conditions as $condition_index => $condition) |
||
| 860 | { |
||
| 861 | list($condition_name, $value) = explode(': ', $condition); |
||
| 862 | if($condition_name == lang('days repeated')) |
||
| 863 | { |
||
| 864 | foreach (self::$days as $mask => $label) |
||
| 865 | { |
||
| 866 | if (strpos($value, $label) !== FALSE || strpos($value, lang($label)) !== FALSE) |
||
| 867 | { |
||
| 868 | $weekdays += $mask; |
||
| 869 | } |
||
| 870 | } |
||
| 871 | if(stripos($condition, lang('all')) !== false) |
||
| 872 | { |
||
| 873 | $weekdays = self::ALLDAYS; |
||
| 874 | } |
||
| 875 | } |
||
| 876 | else if ($condition_name == lang('interval')) |
||
| 877 | { |
||
| 878 | $interval = (int)$value; |
||
| 879 | } |
||
| 880 | else if ($condition_name == lang('ends')) |
||
| 881 | { |
||
| 882 | list(, $date) = explode(', ', $value); |
||
| 883 | $enddate = new DateTime($date); |
||
| 884 | } |
||
| 885 | } |
||
| 886 | |||
| 887 | return new calendar_rrule($time,$type_id,$interval,$enddate,$weekdays,$exceptions); |
||
| 888 | } |
||
| 889 | |||
| 890 | /** |
||
| 891 | * Get recurrence data (keys 'recur_*') to merge into an event |
||
| 892 | * |
||
| 893 | * @return array |
||
| 894 | */ |
||
| 895 | public function rrule2event() |
||
| 896 | { |
||
| 897 | return array( |
||
| 898 | 'recur_type' => $this->type, |
||
| 899 | 'recur_interval' => $this->interval, |
||
| 900 | 'recur_enddate' => $this->enddate ? $this->enddate->format('ts') : null, |
||
| 901 | 'recur_data' => $this->weekdays, |
||
| 902 | 'recur_exception' => $this->exceptions, |
||
| 903 | ); |
||
| 904 | } |
||
| 905 | |||
| 906 | /** |
||
| 907 | * Shift a recurrence rule to a new timezone |
||
| 908 | * |
||
| 909 | * @param array $event recurring event |
||
| 910 | * @param DateTime/string starttime of the event (in servertime) |
||
| 911 | * @param string $to_tz new timezone |
||
| 912 | */ |
||
| 913 | public static function rrule2tz(array &$event,$starttime,$to_tz) |
||
| 960 | } |
||
| 961 | } |
||
| 962 | } |
||
| 963 | |||
| 964 | /** |
||
| 965 | * Parses a DateTime field and returns a unix timestamp. If the |
||
| 966 | * field cannot be parsed then the original text is returned |
||
| 967 | * unmodified. |
||
| 968 | * |
||
| 969 | * @param string $text The Icalendar datetime field value. |
||
| 970 | * @param string $tzid =null A timezone identifier. |
||
| 971 | * |
||
| 972 | * @return integer A unix timestamp. |
||
| 973 | */ |
||
| 974 | private static function parseIcalDateTime($text, $tzid=null) |
||
| 980 | } |
||
| 981 | |||
| 982 | /** |
||
| 983 | * Parse an iCal recurrence-rule string |
||
| 984 | * |
||
| 985 | * @param type $recurence |
||
| 986 | * @param bool $support_below_daily =false true: support FREQ=HOURLY|MINUTELY |
||
| 987 | * @return type |
||
| 988 | */ |
||
| 989 | public static function parseRrule($recurence, $support_below_daily=false) |
||
| 1145 | } |
||
| 1146 | } |
||
| 1147 | |||
| 1148 | if (isset($_SERVER['SCRIPT_FILENAME']) && $_SERVER['SCRIPT_FILENAME'] == __FILE__) // some tests |
||
| 1149 | { |
||
| 1195 | } |
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..