diff --git a/pandora_console/godmode/alerts/alert_special_days.php b/pandora_console/godmode/alerts/alert_special_days.php index 281610e7d1..f5ad84a200 100644 --- a/pandora_console/godmode/alerts/alert_special_days.php +++ b/pandora_console/godmode/alerts/alert_special_days.php @@ -3,7 +3,7 @@ // Pandora FMS - http://pandorafms.com // ================================================== // Copyright (c) 2005-2010 Artica Soluciones Tecnologicas -// Copyright (c) 2012-2013 Junichi Satoh +// Copyright (c) 2012-2016 Junichi Satoh // Please see http://pandorafms.org for full contribution list // This program is free software; you can redistribute it and/or @@ -19,6 +19,7 @@ global $config; require_once ("include/functions_alerts.php"); +require_once ("include/ics-parser/class.iCalReader.php"); check_login (); @@ -45,6 +46,65 @@ ui_print_page_header (__('Alerts').' » '.__('Special days list'), "images/ $update_special_day = (bool) get_parameter ('update_special_day'); $create_special_day = (bool) get_parameter ('create_special_day'); $delete_special_day = (bool) get_parameter ('delete_special_day'); +$upload_ical = (bool)get_parameter('upload_ical', 0); +$display_range = (int) get_parameter ('display_range'); + +if ($upload_ical) { + $same_day = (string) get_parameter ('same_day'); + $overwrite = (bool) get_parameter ('overwrite', 0); + $values = array(); + $values['id_group'] = (string) get_parameter ('id_group'); + $values['same_day'] = $same_day; + + $error = $_FILES['ical_file']['error']; + $extension = substr($_FILES['ical_file']['name'], -3); + + if ($error == 0 && strcasecmp($extension, "ics") == 0) { + $skipped_dates = ''; + #$today = date ('Ymd'); + $this_month = date ('Ym'); + $ical = new ICal($_FILES['ical_file']['tmp_name']); + $events = $ical->events(); + foreach ($events as $event) { + $event_date = substr($event['DTSTART'], 0, 8); + $event_month = substr($event['DTSTART'], 0, 6); + if ($event_month >= $this_month) { + $values['description'] = @$event['SUMMARY']; + $values['date'] = $event_date; + $date = date ('Y-m-d', strtotime($event_date)); + $date_check = ''; + $filter['id_group'] = $values['id_group']; + $filter['date'] = $date; + $date_check = db_get_value_filter ('date', 'talert_special_days', $filter); + if ($date_check == $date) { + if ($overwrite) { + $id_special_day = db_get_value_filter ('id', 'talert_special_days', $filter); + alerts_update_alert_special_day ($id_special_day, $values); + } + else { + if ($skipped_dates == '') { + $skipped_dates = __('Skipped dates: '); + } + $skipped_dates .= $date . " "; + } + } + else { + alerts_create_alert_special_day ($date, $same_day, $values); + } + } + } + $result = true; + } + else { + $result = false; + } + + if ($result) { + db_pandora_audit ("Special days list", "Upload iCalendar " . $_FILES['ical_file']['name']); + } + + ui_print_result_message ($result, __('Success to upload iCalendar') . "
" . $skipped_dates, __('Fail to upload iCalendar')); +} if ($create_special_day) { $date = (string) get_parameter ('date'); @@ -65,14 +125,9 @@ if ($create_special_day) { } else { $date_check = ''; - switch ($config['dbtype']) { - case "mysql": - $date_check = db_get_value ('date', 'talert_special_days', 'date', $date); - break; - case "oracle": - $date_check = db_get_value ('"date"', 'talert_special_days', '"date"', $date); - break; - } + $filter['id_group'] = $values['id_group']; + $filter['date'] = $date; + $date_check = db_get_value_filter ('date', 'talert_special_days', $filter); if ($date_check == $date) { $result = ''; } @@ -98,9 +153,11 @@ if ($update_special_day) { $id = (int) get_parameter ('id'); $alert = alerts_get_alert_special_day ($id); $date = (string) get_parameter ('date'); + $date_orig = (string) get_parameter ('date_orig'); $same_day = (string) get_parameter ('same_day'); $description = (string) get_parameter ('description'); $id_group = (string) get_parameter ('id_group'); + $id_group_orig = (string) get_parameter ('id_group_orig'); list($year, $month, $day) = explode("-", $date); if ($year == '*') { @@ -119,8 +176,23 @@ if ($update_special_day) { $result = ''; } else { - $result = alerts_update_alert_special_day ($id, $values); - $info = 'Date: ' . $date . ' Same day of the week: ' . $same_day . ' Description: ' . $description; + if ($id_group != $id_group_orig || $date != $date_orig) { + $date_check = ''; + $filter['id_group'] = $id_group; + $filter['date'] = $date; + $date_check = db_get_value_filter ('date', 'talert_special_days', $filter); + if ($date_check == $date) { + $result = ''; + } + else { + $result = alerts_update_alert_special_day ($id, $values); + $info = 'Date: ' . $date . ' Same day of the week: ' . $same_day . ' Description: ' . $description; + } + } + else { + $result = alerts_update_alert_special_day ($id, $values); + $info = 'Date: ' . $date . ' Same day of the week: ' . $same_day . ' Description: ' . $description; + } } if ($result) { @@ -152,82 +224,257 @@ if ($delete_special_day) { __('Could not be deleted')); } -$table = new stdClass(); -$table->width = '100%'; -$table->class = 'databox data'; -$table->data = array (); -$table->head = array (); -$table->head[0] = __('Date'); -$table->head[1] = __('Same day of the week'); -$table->head[2] = __('Description'); -$table->head[3] = __('Group'); -$table->head[4] = __('Delete'); -$table->style = array (); -$table->style[0] = 'font-weight: bold'; -$table->size = array (); -$table->size[0] = '20%'; -$table->size[1] = '15%'; -$table->size[2] = '55%'; -$table->size[3] = '5%'; -$table->size[4] = '5%'; -$table->align = array (); -$table->align[3] = 'left'; -$table->align[4] = 'left'; +echo ""; +echo ""; +echo ""; +echo "
"; +echo __('iCalendar(.ics) file') . ' '; +html_print_input_file ('ical_file', false, false); +echo ""; +echo __('Same day of the week'); +$days = array (); +$days["monday"] = __('Monday'); +$days["tuesday"] = __('Tuesday'); +$days["wednesday"] = __('Wednesday'); +$days["thursday"] = __('Thursday'); +$days["friday"] = __('Friday'); +$days["saturday"] = __('Saturday'); +$days["sunday"] = __('Sunday'); +html_print_select ($days, "same_day", $same_day, '', '', 0, false, false, false); +echo ""; +echo __('Group') . ' '; +$own_info = get_user_info($config['id_user']); +if (!users_can_manage_group_all(0, "LM")) + $can_manage_group_all = false; +else + $can_manage_group_all = true; +html_print_select_groups(false, "LM", $can_manage_group_all, "id_group", $id_group, false, '', 0, false, false, true, '', false, 'width:100px;'); +echo ""; +echo __('Overwrite'); +ui_print_help_tip(__('Check this box, if you want to overwrite existing same days.'), false); +echo " "; +html_print_checkbox ("overwrite", 1, $overwrite, false, false, false, true); +echo ""; +html_print_input_hidden('upload_ical', 1); +echo ""; +echo "
"; + + +$this_year = date('Y'); +$this_month = date('m'); $filter = array(); if (!is_user_admin($config['id_user'])) $filter['id_group'] = array_keys(users_get_groups(false, "LM")); -$special_days = db_get_all_rows_filter ('talert_special_days', $filter); -if ($special_days === false) - $special_days = array (); - -foreach ($special_days as $special_day) { - $data = array (); - - $data[0] = ''; - # '0001' means every year. - $data[0] .= ''. - str_replace('0001', '*', $special_day['date']) . ''; - $data[0] .= ''; - switch ($special_day['same_day']) { - case 'monday': - $data[1] = __('Monday'); - break; - case 'tuesday': - $data[1] = __('Tuesday'); - break; - case 'wednesday': - $data[1] = __('Wednesday'); - break; - case 'thursday': - $data[1] = __('Thursday'); - break; - case 'friday': - $data[1] = __('Friday'); - break; - case 'saturday': - $data[1] = __('Saturday'); - break; - case 'sunday': - $data[1] = __('Sunday'); - break; - } - $data[2] = $special_day['description']; - $data[3] = ui_print_group_icon ($special_day["id_group"], true); - $data[4] = ''. - html_print_image("images/cross.png", true) . ''; - - array_push ($table->data, $data); -} - -if(isset($data)) { - html_print_table ($table); +// Show display range. +$html = ""; +echo $html; + +// Show calendar +for ($month = 1; $month <= 12; $month++) { + + if ($display_range) { + $display_month = $month; + $display_year = $display_range; + } + else { + $display_month = $this_month + $month - 1; + $display_year = $this_year; + } + + if ($display_month > 12) { + $display_month -= 12; + $display_year += 1; + } + + $cal_table = new stdClass(); + $cal_table->width = '100%'; + $cal_table->class = 'databox data'; + + $cal_table->data = array (); + $cal_table->head = array (); + $cal_table->head[0] = __('Sun'); + $cal_table->head[1] = __('Mon'); + $cal_table->head[2] = __('Tue'); + $cal_table->head[3] = __('Wed'); + $cal_table->head[4] = __('Thu'); + $cal_table->head[5] = __('Fri'); + $cal_table->head[6] = __('Sat'); + $cal_table->cellstyle = array (); + $cal_table->size = array (); + $cal_table->size[0] = '14%'; + $cal_table->size[1] = '14%'; + $cal_table->size[2] = '14%'; + $cal_table->size[3] = '14%'; + $cal_table->size[4] = '14%'; + $cal_table->size[5] = '14%'; + $cal_table->size[6] = '14%'; + $cal_table->align = array (); + $cal_table->border = '1'; + $cal_table->titlestyle = 'text-align:center; font-weight: bold;'; + switch ($display_month) { + case 1: + $cal_table->title = __('January'); + break; + case 2: + $cal_table->title = __('February'); + break; + case 3: + $cal_table->title = __('March'); + break; + case 4: + $cal_table->title = __('April'); + break; + case 5: + $cal_table->title = __('May'); + break; + case 6: + $cal_table->title = __('June'); + break; + case 7: + $cal_table->title = __('July'); + break; + case 8: + $cal_table->title = __('August'); + break; + case 9: + $cal_table->title = __('September'); + break; + case 10: + $cal_table->title = __('October'); + break; + case 11: + $cal_table->title = __('November'); + break; + case 12: + $cal_table->title = __('December'); + break; + } + $cal_table->title .= ' / ' . $display_year; + + $last_day = date('j', mktime(0, 0, 0, $display_month + 1, 0, $display_year)); + $cal_line = 0; + + for ($day = 1; $day < $last_day + 1; $day++) { + $week = date('w', mktime(0, 0, 0, $display_month, $day, $display_year)); + if ($cal_line == 0 && $week != 0 && $day == 1) { + for ($i = 0; $i < $week; $i++) { + $cal_table->cellstyle[$cal_line][$i] = 'font-size: 18px;'; + $cal_table->data[$cal_line][$i] = '-'; + } + } + if ($week == 0 || $week == 6) { + $cal_table->cellstyle[$cal_line][$week] = 'color: red;'; + } + + $date = sprintf("%04d-%02d-%02d", $display_year, $display_month, $day); + $date_wildcard = sprintf("0001-%02d-%02d", $display_month, $day); + $special_days = ''; + $filter['date'] = array($date, $date_wildcard); + $filter['order']['field'] = 'date'; + $filter['order']['order'] = 'DESC'; + $special_days = db_get_all_rows_filter ('talert_special_days', $filter); + + if ($special_days != '') { + foreach ($special_days as $special_day) { + $cal_table->data[$cal_line][$week] .= '
data[$cal_line][$week] .= 'color: red;'; + } + $cal_table->data[$cal_line][$week] .= '">'; + $cal_table->data[$cal_line][$week] .= $day; + $cal_table->data[$cal_line][$week] .= '
'; + $cal_table->data[$cal_line][$week] .= ui_print_group_icon ($special_day["id_group"], true); + + if ($special_day["date"] == $date_wildcard) { + $cal_table->data[$cal_line][$week] .= '(' . ui_print_help_tip('This is valid every year. However, this will be ignored if indivisual setting for the same group is available.', true) . ') '; + } + $cal_table->data[$cal_line][$week] .= __('Same as '); + switch ($special_day['same_day']) { + case 'monday': + $cal_table->data[$cal_line][$week] .= __('Monday'); + break; + case 'tuesday': + $cal_table->data[$cal_line][$week] .= __('Tuesday'); + break; + case 'wednesday': + $cal_table->data[$cal_line][$week] .= __('Wednesday'); + break; + case 'thursday': + $cal_table->data[$cal_line][$week] .= __('Thursday'); + break; + case 'friday': + $cal_table->data[$cal_line][$week] .= __('Friday'); + break; + case 'saturday': + $cal_table->data[$cal_line][$week] .= __('Saturday'); + break; + case 'sunday': + $cal_table->data[$cal_line][$week] .= __('Sunday'); + break; + } + $cal_table->data[$cal_line][$week] .= ui_print_help_tip($special_day['description'], true); + if ($special_day["id_group"] || ($can_manage_group_all && $special_day["id_group"] == 0)) { + $cal_table->data[$cal_line][$week] .= 'data[$cal_line][$week] .= '>' . html_print_image("images/wrench_orange.png", true) . '  '; + $cal_table->data[$cal_line][$week] .= 'data[$cal_line][$week] .= '>'. html_print_image("images/cross.png", true) . '';; + } + $cal_table->data[$cal_line][$week] .= '
'; + $cal_table->cellstyle[$cal_line][$week] = 'font-weight: bold;'; + } + } + else { + $cal_table->cellstyle[$cal_line][$week] .= 'font-size: 18px;'; + $cal_table->data[$cal_line][$week] = $day . ' '; + } + $cal_table->data[$cal_line][$week] .= 'data[$cal_line][$week] .= '>' . html_print_image("images/plus.png", true) . ''; + + if ($week == 6) { + $cal_line++; + } + } + for ($padding = $week + 1; $padding <= 6; $padding++) { + $cal_table->cellstyle[$cal_line][$padding] = 'font-size: 18px;'; + $cal_table->data[$cal_line][$padding] = '-'; + } + + html_print_table ($cal_table); + } echo '
'; diff --git a/pandora_console/godmode/alerts/configure_alert_special_days.php b/pandora_console/godmode/alerts/configure_alert_special_days.php index bc1421ec5e..9de750bd10 100644 --- a/pandora_console/godmode/alerts/configure_alert_special_days.php +++ b/pandora_console/godmode/alerts/configure_alert_special_days.php @@ -30,11 +30,11 @@ if (! check_acl ($config['id_user'], 0, "LM")) { ui_require_javascript_file ('calendar'); $id = (int) get_parameter ('id'); +$date = (string) get_parameter ('date'); $name = ''; $command = ''; $description = ''; -$date = ''; $same_day = ''; $id_group = 0; if ($id) { diff --git a/pandora_console/include/ics-parser/class.iCalReader.php b/pandora_console/include/ics-parser/class.iCalReader.php new file mode 100644 index 0000000000..792882c6fa --- /dev/null +++ b/pandora_console/include/ics-parser/class.iCalReader.php @@ -0,0 +1,742 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @link https://github.com/MartinThoma/ics-parser/ + * @version 1.0.3 + */ + +/** + * This is the ICal class + * + * @param {string} filename The name of the file which should be parsed + * @constructor + */ +class ICal +{ + /* How many ToDos are in this iCal? */ + public /** @type {int} */ $todo_count = 0; + + /* How many events are in this iCal? */ + public /** @type {int} */ $event_count = 0; + + /* How many freebusy are in this iCal? */ + public /** @type {int} */ $freebusy_count = 0; + + /* The parsed calendar */ + public /** @type {Array} */ $cal; + + /* Which keyword has been added to cal at last? */ + private /** @type {string} */ $last_keyword; + + /* The value in years to use for indefinite, recurring events */ + public /** @type {int} */ $default_span = 2; + + /** + * Creates the iCal Object + * + * @param {mixed} $filename The path to the iCal-file or an array of lines from an iCal file + * + * @return Object The iCal Object + */ + public function __construct($filename=false) + { + if (!$filename) { + return false; + } + + if (is_array($filename)) { + $lines = $filename; + } else { + $lines = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + } + + return $this->initLines($lines); + } + + + /** + * Initializes lines from a URL + * + * @url {string} $url The url of the ical file to download and initialize. Unless you know what you're doing, it should begin with "http://" + * + * @return Object The iCal Object + */ + public function initURL($url) + { + $contents = file_get_contents($url); + + $lines = explode("\n", $contents); + + return $this->initLines($lines); + } + + + /** + * Initializes lines from a string + * + * @param {string} $contents The contents of the ical file to initialize + * + * @return Object The iCal Object + */ + public function initString($contents) + { + $lines = explode("\n", $contents); + + return $this->initLines($lines); + } + + + /** + * Initializes lines from file + * + * @param {array} $lines The lines to initialize + * + * @return Object The iCal Object + */ + public function initLines($lines) + { + if (stristr($lines[0], 'BEGIN:VCALENDAR') === false) { + return false; + } else { + foreach ($lines as $line) { + $line = rtrim($line); // Trim trailing whitespace + $add = $this->keyValueFromString($line); + + if ($add === false) { + $this->addCalendarComponentWithKeyAndValue($component, false, $line); + continue; + } + + $keyword = $add[0]; + $values = $add[1]; // Could be an array containing multiple values + + if (!is_array($values)) { + if (!empty($values)) { + $values = array($values); // Make an array as not already + $blank_array = array(); // Empty placeholder array + array_push($values, $blank_array); + } else { + $values = array(); // Use blank array to ignore this line + } + } else if(empty($values[0])) { + $values = array(); // Use blank array to ignore this line + } + + $values = array_reverse($values); // Reverse so that our array of properties is processed first + + foreach ($values as $value) { + switch ($line) { + // http://www.kanzaki.com/docs/ical/vtodo.html + case 'BEGIN:VTODO': + $this->todo_count++; + $component = 'VTODO'; + break; + + // http://www.kanzaki.com/docs/ical/vevent.html + case 'BEGIN:VEVENT': + if (!is_array($value)) { + $this->event_count++; + } + $component = 'VEVENT'; + break; + + // http://www.kanzaki.com/docs/ical/vfreebusy.html + case 'BEGIN:VFREEBUSY': + $this->freebusy_count++; + $component = 'VFREEBUSY'; + break; + + // All other special strings + case 'BEGIN:VCALENDAR': + case 'BEGIN:DAYLIGHT': + // http://www.kanzaki.com/docs/ical/vtimezone.html + case 'BEGIN:VTIMEZONE': + case 'BEGIN:STANDARD': + case 'BEGIN:VALARM': + $component = $value; + break; + case 'END:VALARM': + case 'END:VTODO': // End special text - goto VCALENDAR key + case 'END:VEVENT': + case 'END:VFREEBUSY': + case 'END:VCALENDAR': + case 'END:DAYLIGHT': + case 'END:VTIMEZONE': + case 'END:STANDARD': + $component = 'VCALENDAR'; + break; + default: + $this->addCalendarComponentWithKeyAndValue($component, $keyword, $value); + break; + } + } + } + $this->process_recurrences(); + return $this->cal; + } + } + + /** + * Add to $this->ical array one value and key. + * + * @param {string} $component This could be VTODO, VEVENT, VCALENDAR, ... + * @param {string} $keyword The keyword, for example DTSTART + * @param {string} $value The value, for example 20110105T090000Z + * + * @return {None} + */ + public function addCalendarComponentWithKeyAndValue($component, $keyword, $value) + { + if ($keyword == false) { + $keyword = $this->last_keyword; + } + + switch ($component) { + case 'VTODO': + $this->cal[$component][$this->todo_count - 1][$keyword] = $value; + break; + case 'VEVENT': + if (!isset($this->cal[$component][$this->event_count - 1][$keyword . '_array'])) { + $this->cal[$component][$this->event_count - 1][$keyword . '_array'] = array(); // Create array() + } + + if (is_array($value)) { + array_push($this->cal[$component][$this->event_count - 1][$keyword . '_array'], $value); // Add array of properties to the end + } else { + if (!isset($this->cal[$component][$this->event_count - 1][$keyword])) { + $this->cal[$component][$this->event_count - 1][$keyword] = $value; + } + + $this->cal[$component][$this->event_count - 1][$keyword . '_array'][] = $value; + + // Glue back together for multi-line content + if ($this->cal[$component][$this->event_count - 1][$keyword] != $value) { + $ord = (isset($value[0])) ? ord($value[0]) : NULL; // First char + + if(in_array($ord, array(9, 32))){ // Is space or tab? + $value = substr($value, 1); // Only trim the first character + } + + if(is_array($this->cal[$component][$this->event_count - 1][$keyword . '_array'][1])){ // Account for multiple definitions of current keyword (e.g. ATTENDEE) + $this->cal[$component][$this->event_count - 1][$keyword] .= ';' . $value; // Concat value *with separator* as content spans multiple lines + } else { + $this->cal[$component][$this->event_count - 1][$keyword] .= $value; // Concat value as content spans multiple lines + } + } + } + break; + case 'VFREEBUSY': + $this->cal[$component][$this->freebusy_count - 1][$keyword] = $value; + break; + default: + $this->cal[$component][$keyword] = $value; + break; + } + $this->last_keyword = $keyword; + } + + /** + * Get a key-value pair of a string. + * + * @param {string} $text which is like "VCALENDAR:Begin" or "LOCATION:" + * + * @return {array} array("VCALENDAR", "Begin") + */ + public function keyValueFromString($text) + { + // Match colon separator outside of quoted substrings + // Fallback to nearest semicolon outside of quoted substrings, if colon cannot be found + // Do not try and match within the value paired with the keyword + preg_match('/(.*?)(?::(?=(?:[^"]*"[^"]*")*[^"]*$)|;(?=[^:]*$))([\w\W]*)/', htmlspecialchars($text, ENT_QUOTES, 'UTF-8'), $matches); + + if (count($matches) == 0) { + return false; + } + + if (preg_match('/^([A-Z-]+)([;][\w\W]*)?$/', $matches[1])) { + $matches = array_splice($matches, 1, 2); // Remove first match and re-align ordering + + // Process properties + if (preg_match('/([A-Z-]+)[;]([\w\W]*)/', $matches[0], $properties)) { + array_shift($properties); // Remove first match + $matches[0] = $properties[0]; // Fix to ignore everything in keyword after a ; (e.g. Language, TZID, etc.) + array_shift($properties); // Repeat removing first match + + $formatted = array(); + foreach ($properties as $property) { + preg_match_all('~[^\r\n";]+(?:"[^"\\\]*(?:\\\.[^"\\\]*)*"[^\r\n";]*)*~', $property, $attributes); // Match semicolon separator outside of quoted substrings + $attributes = (sizeof($attributes) == 0) ? array($property) : reset($attributes); // Remove multi-dimensional array and use the first key + + foreach ($attributes as $attribute) { + preg_match_all('~[^\r\n"=]+(?:"[^"\\\]*(?:\\\.[^"\\\]*)*"[^\r\n"=]*)*~', $attribute, $values); // Match equals sign separator outside of quoted substrings + $value = (sizeof($values) == 0) ? NULL : reset($values); // Remove multi-dimensional array and use the first key + + if (is_array($value) && isset($value[1])) { + $formatted[$value[0]] = trim($value[1], '"'); // Remove double quotes from beginning and end only + } + } + } + + $properties[0] = $formatted; // Assign the keyword property information + + array_unshift($properties, $matches[1]); // Add match to beginning of array + $matches[1] = $properties; + } + + return $matches; + } else { + return false; // Ignore this match + } + } + + /** + * Return Unix timestamp from iCal date time format + * + * @param {string} $icalDate A Date in the format YYYYMMDD[T]HHMMSS[Z] or + * YYYYMMDD[T]HHMMSS + * + * @return {int} + */ + public function iCalDateToUnixTimestamp($icalDate) + { + $icalDate = str_replace('T', '', $icalDate); + $icalDate = str_replace('Z', '', $icalDate); + + $pattern = '/([0-9]{4})'; // 1: YYYY + $pattern .= '([0-9]{2})'; // 2: MM + $pattern .= '([0-9]{2})'; // 3: DD + $pattern .= '([0-9]{0,2})'; // 4: HH + $pattern .= '([0-9]{0,2})'; // 5: MM + $pattern .= '([0-9]{0,2})/'; // 6: SS + preg_match($pattern, $icalDate, $date); + + // Unix timestamp can't represent dates before 1970 + if ($date[1] <= 1970) { + return false; + } + // Unix timestamps after 03:14:07 UTC 2038-01-19 might cause an overflow + // if 32 bit integers are used. + $timestamp = mktime((int)$date[4], (int)$date[5], (int)$date[6], (int)$date[2], (int)$date[3], (int)$date[1]); + return $timestamp; + } + + /** + * Processes recurrences + * + * @author John Grogg + * @return {array} + */ + public function process_recurrences() + { + $array = $this->cal; + $events = $array['VEVENT']; + if (empty($events)) + return false; + foreach ($array['VEVENT'] as $anEvent) { + if (isset($anEvent['RRULE']) && $anEvent['RRULE'] != '') { + // Recurring event, parse RRULE and add appropriate duplicate events + $rrules = array(); + $rrule_strings = explode(';', $anEvent['RRULE']); + foreach ($rrule_strings as $s) { + list($k, $v) = explode('=', $s); + $rrules[$k] = $v; + } + // Get frequency + $frequency = $rrules['FREQ']; + // Get Start timestamp + $start_timestamp = $this->iCalDateToUnixTimestamp($anEvent['DTSTART']); + $end_timestamp = $this->iCalDateToUnixTimestamp($anEvent['DTEND']); + $event_timestamp_offset = $end_timestamp - $start_timestamp; + // Get Interval + $interval = (isset($rrules['INTERVAL']) && $rrules['INTERVAL'] != '') ? $rrules['INTERVAL'] : 1; + + if (in_array($frequency, array('MONTHLY', 'YEARLY')) && isset($rrules['BYDAY']) && $rrules['BYDAY'] != '') { + // Deal with BYDAY + $day_number = intval($rrules['BYDAY']); + if (empty($day_number)) { // Returns 0 when no number defined in BYDAY + if (!isset($rrules['BYSETPOS'])) { + $day_number = 1; // Set first as default + } else if (is_numeric($rrules['BYSETPOS'])) { + $day_number = $rrules['BYSETPOS']; + } + } + $day_number = ($day_number == -1) ? 6 : $day_number; // Override for our custom key (6 => 'last') + $week_day = substr($rrules['BYDAY'], -2); + $day_ordinals = array(1 => 'first', 2 => 'second', 3 => 'third', 4 => 'fourth', 5 => 'fifth', 6 => 'last'); + $weekdays = array('SU' => 'sunday', 'MO' => 'monday', 'TU' => 'tuesday', 'WE' => 'wednesday', 'TH' => 'thursday', 'FR' => 'friday', 'SA' => 'saturday'); + } + + $until_default = date_create('now'); + $until_default->modify($this->default_span . ' year'); + $until_default->setTime(23, 59, 59); // End of the day + $until_default = date_format($until_default, 'Ymd\THis'); + + if (isset($rrules['UNTIL'])) { + // Get Until + $until = $this->iCalDateToUnixTimestamp($rrules['UNTIL']); + } else if (isset($rrules['COUNT'])) { + $frequency_conversion = array('DAILY' => 'day', 'WEEKLY' => 'week', 'MONTHLY' => 'month', 'YEARLY' => 'year'); + $count_orig = (is_numeric($rrules['COUNT']) && $rrules['COUNT'] > 1) ? $rrules['COUNT'] : 0; + $count = ($count_orig - 1); // Remove one to exclude the occurrence that initialises the rule + $count += ($count > 0) ? $count * ($interval - 1) : 0; + $offset = "+$count " . $frequency_conversion[$frequency]; + $until = strtotime($offset, $start_timestamp); + + if (in_array($frequency, array('MONTHLY', 'YEARLY')) && isset($rrules['BYDAY']) && $rrules['BYDAY'] != '') { + $dtstart = date_create($anEvent['DTSTART']); + for ($i = 1; $i <= $count; $i++) { + $dtstart_clone = clone $dtstart; + $dtstart_clone->modify('next ' . $frequency_conversion[$frequency]); + $offset = "{$day_ordinals[$day_number]} {$weekdays[$week_day]} of " . $dtstart_clone->format('F Y H:i:01'); + $dtstart->modify($offset); + } + + // Jumping X months forwards doesn't mean the end date will fall on the same day defined in BYDAY + // Use the largest of these to ensure we are going far enough in the future to capture our final end day + $until = max($until, $dtstart->format('U')); + } + + unset($offset); + } else { + $until = $this->iCalDateToUnixTimestamp($until_default); + } + + if(!isset($anEvent['EXDATE_array'])){ + $anEvent['EXDATE_array'] = array(); + } + + // Decide how often to add events and do so + switch ($frequency) { + case 'DAILY': + // Simply add a new event each interval of days until UNTIL is reached + $offset = "+$interval day"; + $recurring_timestamp = strtotime($offset, $start_timestamp); + + while ($recurring_timestamp <= $until) { + // Add event + $anEvent['DTSTART'] = date('Ymd\THis', $recurring_timestamp); + $anEvent['DTEND'] = date('Ymd\THis', $recurring_timestamp + $event_timestamp_offset); + + $search_date = $anEvent['DTSTART']; + $is_excluded = array_filter($anEvent['EXDATE_array'], function($val) use ($search_date) { + return is_string($val) && strpos($search_date, $val) === 0; + }); + + if (!$is_excluded) { + $events[] = $anEvent; + } + + // Move forwards + $recurring_timestamp = strtotime($offset, $recurring_timestamp); + } + break; + case 'WEEKLY': + // Create offset + $offset = "+$interval week"; + // Build list of days of week to add events + $weekdays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); + + if (isset($rrules['BYDAY']) && $rrules['BYDAY'] != '') { + $bydays = explode(',', $rrules['BYDAY']); + } else { + $weekTemp = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); + $findDay = $weekTemp[date('w', $start_timestamp)]; + $bydays = array($findDay); + } + + // Get timestamp of first day of start week + $week_recurring_timestamp = (date('w', $start_timestamp) == 0) ? $start_timestamp : strtotime('last Sunday ' . date('H:i:s', $start_timestamp), $start_timestamp); + + // Step through weeks + while ($week_recurring_timestamp <= $until) { + // Add events for bydays + $day_recurring_timestamp = $week_recurring_timestamp; + + foreach ($weekdays as $day) { + // Check if day should be added + + if (in_array($day, $bydays) && $day_recurring_timestamp > $start_timestamp && $day_recurring_timestamp <= $until) { + // Add event to day + $anEvent['DTSTART'] = date('Ymd\THis', $day_recurring_timestamp); + $anEvent['DTEND'] = date('Ymd\THis', $day_recurring_timestamp + $event_timestamp_offset); + + $search_date = $anEvent['DTSTART']; + $is_excluded = array_filter($anEvent['EXDATE_array'], function($val) use ($search_date) { return is_string($val) && strpos($search_date, $val) === 0; }); + + if (!$is_excluded) { + $events[] = $anEvent; + } + } + + // Move forwards a day + $day_recurring_timestamp = strtotime('+1 day', $day_recurring_timestamp); + } + + // Move forwards $interval weeks + $week_recurring_timestamp = strtotime($offset, $week_recurring_timestamp); + } + break; + case 'MONTHLY': + // Create offset + $offset = "+$interval month"; + $recurring_timestamp = strtotime($offset, $start_timestamp); + + if (isset($rrules['BYMONTHDAY']) && $rrules['BYMONTHDAY'] != '') { + // Deal with BYMONTHDAY + $monthdays = explode(',', $rrules['BYMONTHDAY']); + + while ($recurring_timestamp <= $until) { + foreach ($monthdays as $monthday) { + // Add event + $anEvent['DTSTART'] = date('Ym' . sprintf('%02d', $monthday) . '\THis', $recurring_timestamp); + $anEvent['DTEND'] = date('Ymd\THis', $this->iCalDateToUnixTimestamp($anEvent['DTSTART']) + $event_timestamp_offset); + + $search_date = $anEvent['DTSTART']; + $is_excluded = array_filter($anEvent['EXDATE_array'], function($val) use ($search_date) { return is_string($val) && strpos($search_date, $val) === 0; }); + + if (!$is_excluded) { + $events[] = $anEvent; + } + } + + // Move forwards + $recurring_timestamp = strtotime($offset, $recurring_timestamp); + } + } else if (isset($rrules['BYDAY']) && $rrules['BYDAY'] != '') { + $start_time = date('His', $start_timestamp); + + while ($recurring_timestamp <= $until) { + $event_start_desc = "{$day_ordinals[$day_number]} {$weekdays[$week_day]} of " . date('F Y H:i:s', $recurring_timestamp); + $event_start_timestamp = strtotime($event_start_desc); + + if ($event_start_timestamp > $start_timestamp && $event_start_timestamp < $until) { + $anEvent['DTSTART'] = date('Ymd\T', $event_start_timestamp) . $start_time; + $anEvent['DTEND'] = date('Ymd\THis', $this->iCalDateToUnixTimestamp($anEvent['DTSTART']) + $event_timestamp_offset); + + $search_date = $anEvent['DTSTART']; + $is_excluded = array_filter($anEvent['EXDATE_array'], function($val) use ($search_date) { return is_string($val) && strpos($search_date, $val) === 0; }); + + if (!$is_excluded) { + $events[] = $anEvent; + } + } + + // Move forwards + $recurring_timestamp = strtotime($offset, $recurring_timestamp); + } + } + break; + case 'YEARLY': + // Create offset + $offset = "+$interval year"; + $recurring_timestamp = strtotime($offset, $start_timestamp); + $month_names = array(1 => 'January', 2 => 'February', 3 => 'March', 4 => 'April', 5 => 'May', 6 => 'June', 7 => 'July', 8 => 'August', 9 => 'September', 10 => 'October', 11 => 'November', 12 => 'December'); + + // Check if BYDAY rule exists + if (isset($rrules['BYDAY']) && $rrules['BYDAY'] != '') { + $start_time = date('His', $start_timestamp); + + while ($recurring_timestamp <= $until) { + $event_start_desc = "{$day_ordinals[$day_number]} {$weekdays[$week_day]} of {$month_names[$rrules['BYMONTH']]} " . date('Y H:i:s', $recurring_timestamp); + $event_start_timestamp = strtotime($event_start_desc); + + if ($event_start_timestamp > $start_timestamp && $event_start_timestamp < $until) { + $anEvent['DTSTART'] = date('Ymd\T', $event_start_timestamp) . $start_time; + $anEvent['DTEND'] = date('Ymd\THis', $this->iCalDateToUnixTimestamp($anEvent['DTSTART']) + $event_timestamp_offset); + + $search_date = $anEvent['DTSTART']; + $is_excluded = array_filter($anEvent['EXDATE_array'], function($val) use ($search_date) { return is_string($val) && strpos($search_date, $val) === 0; }); + + if (!$is_excluded) { + $events[] = $anEvent; + } + } + + // Move forwards + $recurring_timestamp = strtotime($offset, $recurring_timestamp); + } + } else { + $day = date('d', $start_timestamp); + $start_time = date('His', $start_timestamp); + + // Step through years + while ($recurring_timestamp <= $until) { + // Add specific month dates + if (isset($rrules['BYMONTH']) && $rrules['BYMONTH'] != '') { + $event_start_desc = "$day {$month_names[$rrules['BYMONTH']]} " . date('Y H:i:s', $recurring_timestamp); + } else { + $event_start_desc = $day . date('F Y H:i:s', $recurring_timestamp); + } + + $event_start_timestamp = strtotime($event_start_desc); + + if ($event_start_timestamp > $start_timestamp && $event_start_timestamp < $until) { + $anEvent['DTSTART'] = date('Ymd\T', $event_start_timestamp) . $start_time; + $anEvent['DTEND'] = date('Ymd\THis', $this->iCalDateToUnixTimestamp($anEvent['DTSTART']) + $event_timestamp_offset); + + $search_date = $anEvent['DTSTART']; + $is_excluded = array_filter($anEvent['EXDATE_array'], function($val) use ($search_date) { return is_string($val) && strpos($search_date, $val) === 0; }); + + if (!$is_excluded) { + $events[] = $anEvent; + } + } + + // Move forwards + $recurring_timestamp = strtotime($offset, $recurring_timestamp); + } + } + break; + + $events = (isset($count_orig) && sizeof($events) > $count_orig) ? array_slice($events, 0, $count_orig) : $events; // Ensure we abide by COUNT if defined + } + } + } + $this->cal['VEVENT'] = $events; + } + + /** + * Returns an array of arrays with all events. Every event is an associative + * array and each property is an element it. + * + * @return {array} + */ + public function events() + { + $array = $this->cal; + return $array['VEVENT']; + } + + /** + * Returns the calendar name + * + * @return {calendar name} + */ + public function calendarName() + { + return $this->cal['VCALENDAR']['X-WR-CALNAME']; + } + + /** + * Returns the calendar description + * + * @return {calendar description} + */ + public function calendarDescription() + { + return $this->cal['VCALENDAR']['X-WR-CALDESC']; + } + + /** + * Returns an array of arrays with all free/busy events. Every event is + * an associative array and each property is an element it. + * + * @return {array} + */ + public function freeBusyEvents() + { + $array = $this->cal; + return $array['VFREEBUSY']; + } + + /** + * Returns a boolean value whether the current calendar has events or not + * + * @return {boolean} + */ + public function hasEvents() + { + return (count($this->events()) > 0) ? true : false; + } + + /** + * Returns false when the current calendar has no events in range, else the + * events. + * + * Note that this function makes use of a UNIX timestamp. This might be a + * problem on January the 29th, 2038. + * See http://en.wikipedia.org/wiki/Unix_time#Representing_the_number + * + * @param {boolean} $rangeStart Either true or false + * @param {boolean} $rangeEnd Either true or false + * + * @return {mixed} + */ + public function eventsFromRange($rangeStart = false, $rangeEnd = false) + { + $events = $this->sortEventsWithOrder($this->events(), SORT_ASC); + + if (!$events) { + return false; + } + + $extendedEvents = array(); + + if ($rangeStart === false) { + $rangeStart = new DateTime(); + } else { + $rangeStart = new DateTime($rangeStart); + } + + if ($rangeEnd === false or $rangeEnd <= 0) { + $rangeEnd = new DateTime('2038/01/18'); + } else { + $rangeEnd = new DateTime($rangeEnd); + } + + $rangeStart = $rangeStart->format('U'); + $rangeEnd = $rangeEnd->format('U'); + + // Loop through all events by adding two new elements + foreach ($events as $anEvent) { + $timestamp = $this->iCalDateToUnixTimestamp($anEvent['DTSTART']); + if ($timestamp >= $rangeStart && $timestamp <= $rangeEnd) { + $extendedEvents[] = $anEvent; + } + } + + return $extendedEvents; + } + + /** + * Returns a boolean value whether the current calendar has events or not + * + * @param {array} $events An array with events. + * @param {array} $sortOrder Either SORT_ASC, SORT_DESC, SORT_REGULAR, + * SORT_NUMERIC, SORT_STRING + * + * @return {boolean} + */ + public function sortEventsWithOrder($events, $sortOrder = SORT_ASC) + { + $extendedEvents = array(); + + // Loop through all events by adding two new elements + foreach ($events as $anEvent) { + if (!array_key_exists('UNIX_TIMESTAMP', $anEvent)) { + $anEvent['UNIX_TIMESTAMP'] = $this->iCalDateToUnixTimestamp($anEvent['DTSTART']); + } + + if (!array_key_exists('REAL_DATETIME', $anEvent)) { + $anEvent['REAL_DATETIME'] = date('d.m.Y', $anEvent['UNIX_TIMESTAMP']); + } + + $extendedEvents[] = $anEvent; + } + + foreach ($extendedEvents as $key => $value) { + $timestamp[$key] = $value['UNIX_TIMESTAMP']; + } + array_multisort($timestamp, $sortOrder, $extendedEvents); + + return $extendedEvents; + } +}
" . __('Display range: '); +if ($display_range) { + $html .= '[' . __('Default') . ']  '; + if ($display_range > 1970) { + $html .= '<< '; + } + $html .= '[' . $display_range . ']'; + $html .= ' >>'; } else { - ui_print_info_message ( array('no_close'=>true, 'message'=> __('No special days configured') ) ); + $html .= '[' . __('Default') . ']  '; + $html .= '<< '; + $html .= '['; + $html .= $this_year; + $html .= ']'; + $html .= ' >>'; +} +$html .= "