diff --git a/action/ajax.php b/action/ajax.php --- a/action/ajax.php +++ b/action/ajax.php @@ -1,128 +1,129 @@ hlp =& plugin_load('helper','davcal'); } function register(Doku_Event_Handler $controller) { $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax_call_unknown'); } function handle_ajax_call_unknown(&$event, $param) { if($event->data != 'plugin_davcal') return; $event->preventDefault(); $event->stopPropagation(); global $INPUT; $action = trim($INPUT->post->str('action')); $id = trim($INPUT->post->str('id')); $params = $INPUT->post->arr('params'); $user = $_SERVER['REMOTE_USER']; $write = false; $data = array(); $data['result'] = false; $data['html'] = $this->getLang('unknown_error'); $acl = auth_quickaclcheck($id); if($acl > AUTH_READ) { $write = true; } switch($action) { case 'newEvent': if($write) { $data['result'] = true; $data['html'] = $this->getLang('event_added'); $this->hlp->addCalendarEntryToCalendarForPage($id, $user, $params); } else { $data['result'] = false; $data['html'] = $this->getLang('no_permission'); } break; case 'getEvents': $startDate = $INPUT->post->str('start'); $endDate = $INPUT->post->str('end'); $data = $this->hlp->getEventsWithinDateRange($id, $user, $startDate, $endDate); break; case 'editEvent': if($write) { $data['result'] = true; $data['html'] = $this->getLang('event_edited'); $this->hlp->editCalendarEntryForPage($id, $user, $params); } else { $data['result'] = false; $data['html'] = $this->getLang('no_permission'); } break; case 'deleteEvent': if($write) { $data['result'] = true; $data['html'] = $this->getLang('event_deleted'); $this->hlp->deleteCalendarEntryForPage($id, $params); } else { $data['result'] = false; $data['html'] = $this->getLang('no_permission'); } break; case 'getSettings': $data['result'] = true; $data['settings'] = $this->hlp->getPersonalSettings($user); $data['settings']['syncurl'] = $this->hlp->getSyncUrlForPage($id, $user); + $data['settings']['privateurl'] = $this->hlp->getPrivateURLForPage($id); break; case 'saveSettings': $settings = array(); $settings['weeknumbers'] = $params['weeknumbers']; $settings['timezone'] = $params['timezone']; $settings['workweek'] = $params['workweek']; if($this->hlp->savePersonalSettings($settings, $user)) { $data['result'] = true; $data['html'] = $this->getLang('settings_saved'); } else { $data['result'] = false; $data['html'] = $this->getLang('error_saving'); } break; } // If we are still here, JSON output is requested //json library of DokuWiki require_once DOKU_INC . 'inc/JSON.php'; $json = new JSON(); //set content type header('Content-Type: application/json'); echo $json->encode($data); } } \ No newline at end of file diff --git a/db/latest.version b/db/latest.version --- a/db/latest.version +++ b/db/latest.version @@ -1,1 +1,1 @@ -3 +4 diff --git a/db/update0004.sql b/db/update0004.sql new file mode 100644 --- /dev/null +++ b/db/update0004.sql @@ -0,0 +1,5 @@ +CREATE TABLE calendartoprivateurlmapping ( + id integer primary key asc, + url text, + calid integer +); diff --git a/helper.php b/helper.php --- a/helper.php +++ b/helper.php @@ -1,481 +1,554 @@ sqlite =& plugin_load('helper', 'sqlite'); if(!$this->sqlite) { msg('This plugin requires the sqlite plugin. Please install it.'); return; } if(!$this->sqlite->init('davcal', DOKU_PLUGIN.'davcal/db/')) { return; } } public function setCalendarNameForPage($name, $description, $id = null, $userid = null) { if(is_null($id)) { global $ID; $id = $ID; } if(is_null($userid)) $userid = $_SERVER['REMOTE_USER']; $calid = $this->getCalendarIdForPage($id); if($calid === false) return $this->createCalendarForPage($name, $description, $id, $userid); $query = "UPDATE calendars SET displayname=".$this->sqlite->quote_string($name).", ". "description=".$this->sqlite->quote_string($description)." WHERE ". "id=".$this->sqlite->quote_string($calid); $res = $this->sqlite->query($query); if($res !== false) return true; return false; } public function savePersonalSettings($settings, $userid = null) { if(is_null($userid)) $userid = $_SERVER['REMOTE_USER']; $this->sqlite->query("BEGIN TRANSACTION"); $query = "DELETE FROM calendarsettings WHERE userid=".$this->sqlite->quote_string($userid); $this->sqlite->query($query); foreach($settings as $key => $value) { $query = "INSERT INTO calendarsettings (userid, key, value) VALUES (". $this->sqlite->quote_string($userid).", ". $this->sqlite->quote_string($key).", ". $this->sqlite->quote_string($value).")"; $res = $this->sqlite->query($query); if($res === false) return false; } $this->sqlite->query("COMMIT TRANSACTION"); return true; } public function getPersonalSettings($userid = null) { if(is_null($userid)) $userid = $_SERVER['REMOTE_USER']; // Some sane default settings $settings = array( 'timezone' => 'local', 'weeknumbers' => '0', 'workweek' => '0' ); $query = "SELECT key, value FROM calendarsettings WHERE userid=".$this->sqlite->quote_string($userid); $res = $this->sqlite->query($query); $arr = $this->sqlite->res2arr($res); foreach($arr as $row) { $settings[$row['key']] = $row['value']; } return $settings; } public function getCalendarIdForPage($id = null) { if(is_null($id)) { global $ID; $id = $ID; } $query = "SELECT calid FROM pagetocalendarmapping WHERE page=".$this->sqlite->quote_string($id); $res = $this->sqlite->query($query); $row = $this->sqlite->res2row($res); if(isset($row['calid'])) return $row['calid']; else return false; } public function getCalendarIdToPageMapping() { $query = "SELECT calid, page FROM pagetocalendarmapping"; $res = $this->sqlite->query($query); $arr = $this->sqlite->res2arr($res); return $arr; } public function getCalendarIdsForUser($principalUri) { $user = explode('/', $principalUri); $user = end($user); $mapping = $this->getCalendarIdToPageMapping(); $calids = array(); foreach($mapping as $row) { $id = $row['calid']; $page = $row['page']; $acl = auth_quickaclcheck($page); if($acl >= AUTH_READ) { $write = $acl > AUTH_READ; $calids[$id] = array('readonly' => !$write); } } return $calids; } public function createCalendarForPage($name, $description, $id = null, $userid = null) { if(is_null($id)) { global $ID; $id = $ID; } if(is_null($userid)) $userid = $_SERVER['REMOTE_USER']; $values = array('principals/'.$userid, $name, str_replace(array('/', ' ', ':'), '_', $id), $description, 'VEVENT,VTODO', 0, 1); $query = "INSERT INTO calendars (principaluri, displayname, uri, description, components, transparent, synctoken) VALUES (".$this->sqlite->quote_and_join($values, ',').");"; $res = $this->sqlite->query($query); if($res === false) return false; $query = "SELECT id FROM calendars WHERE principaluri=".$this->sqlite->quote_string($values[0])." AND ". "displayname=".$this->sqlite->quote_string($values[1])." AND ". "uri=".$this->sqlite->quote_string($values[2])." AND ". "description=".$this->sqlite->quote_string($values[3]); $res = $this->sqlite->query($query); $row = $this->sqlite->res2row($res); if(isset($row['id'])) { $values = array($id, $row['id']); $query = "INSERT INTO pagetocalendarmapping (page, calid) VALUES (".$this->sqlite->quote_and_join($values, ',').")"; $res = $this->sqlite->query($query); return ($res !== false); } return false; } public function addCalendarEntryToCalendarForPage($id, $user, $params) { $settings = $this->getPersonalSettings($user); if($settings['timezone'] !== '' && $settings['timezone'] !== 'local') $timezone = new \DateTimeZone($settings['timezone']); elseif($settings['timezone'] === 'local') $timezone = new \DateTimeZone($params['detectedtz']); else $timezone = new \DateTimeZone('UTC'); $startDate = explode('-', $params['eventfrom']); $startTime = explode(':', $params['eventfromtime']); $endDate = explode('-', $params['eventto']); $endTime = explode(':', $params['eventtotime']); require_once('vendor/autoload.php'); $vcalendar = new \Sabre\VObject\Component\VCalendar(); $event = $vcalendar->add('VEVENT'); $uuid = \Sabre\VObject\UUIDUtil::getUUID(); $event->add('UID', $uuid); $event->summary = $params['eventname']; $description = $params['eventdescription']; if($description !== '') $event->add('DESCRIPTION', $description); $dtStamp = new \DateTime(null, new \DateTimeZone('UTC')); $event->add('DTSTAMP', $dtStamp); $event->add('CREATED', $dtStamp); $event->add('LAST-MODIFIED', $dtStamp); $dtStart = new \DateTime(); $dtStart->setTimezone($timezone); $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2])); if($params['allday'] != '1') $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0); $dtEnd = new \DateTime(); $dtEnd->setTimezone($timezone); $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2])); if($params['allday'] != '1') $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0); // According to the VCal spec, we need to add a whole day here if($params['allday'] == '1') $dtEnd->add(new \DateInterval('P1D')); $dtStartEv = $event->add('DTSTART', $dtStart); $dtEndEv = $event->add('DTEND', $dtEnd); if($params['allday'] == '1') { $dtStartEv['VALUE'] = 'DATE'; $dtEndEv['VALUE'] = 'DATE'; } $calid = $this->getCalendarIdForPage($id); $uri = uniqid('dokuwiki-').'.ics'; $now = new DateTime(); $eventStr = $vcalendar->serialize(); $values = array($calid, $uri, $eventStr, $now->getTimestamp(), 'VEVENT', $event->DTSTART->getDateTime()->getTimeStamp(), $event->DTEND->getDateTime()->getTimeStamp(), strlen($eventStr), md5($eventStr), uniqid() ); $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, componenttype, firstoccurence, lastoccurence, size, etag, uid) VALUES (".$this->sqlite->quote_and_join($values, ',').")"; $res = $this->sqlite->query($query); if($res !== false) { $this->updateSyncTokenLog($calid, $uri, 'added'); return true; } return false; } public function getCalendarSettings($calid) { $query = "SELECT principaluri, displayname, uri, description, components, transparent, synctoken FROM calendars WHERE id=".$this->sqlite->quote_string($calid); $res = $this->sqlite->query($query); $row = $this->sqlite->res2row($res); return $row; } public function getEventsWithinDateRange($id, $user, $startDate, $endDate) { $settings = $this->getPersonalSettings($user); if($settings['timezone'] !== '' && $settings['timezone'] !== 'local') $timezone = new \DateTimeZone($settings['timezone']); else $timezone = new \DateTimeZone('UTC'); $data = array(); require_once('vendor/autoload.php'); $calid = $this->getCalendarIdForPage($id); $startTs = new \DateTime($startDate); $endTs = new \DateTime($endDate); $query = "SELECT calendardata, componenttype, uid FROM calendarobjects WHERE (calendarid=". $this->sqlite->quote_string($calid)." AND firstoccurence > ". $this->sqlite->quote_string($startTs->getTimestamp())." AND firstoccurence < ". $this->sqlite->quote_string($endTs->getTimestamp()).") OR (calendarid=". $this->sqlite->quote_string($calid)." AND lastoccurence > ". $this->sqlite->quote_string($startTs->getTimestamp())." AND lastoccurence < ". $this->sqlite->quote_string($endTs->getTimestamp()).")"; $res = $this->sqlite->query($query); $arr = $this->sqlite->res2arr($res); foreach($arr as $row) { if(isset($row['calendardata'])) { $entry = array(); $vcal = \Sabre\VObject\Reader::read($row['calendardata']); $start = $vcal->VEVENT->DTSTART; if($start !== null) { $dtStart = $start->getDateTime(); $dtStart->setTimezone($timezone); $entry['start'] = $dtStart->format(\DateTime::ATOM); if($start['VALUE'] == 'DATE') $entry['allDay'] = true; else $entry['allDay'] = false; } $end = $vcal->VEVENT->DTEND; if($end !== null) { $dtEnd = $end->getDateTime(); $dtEnd->setTimezone($timezone); $entry['end'] = $dtEnd->format(\DateTime::ATOM); } $description = $vcal->VEVENT->DESCRIPTION; if($description !== null) $entry['description'] = (string)$description; else $entry['description'] = ''; $entry['title'] = (string)$vcal->VEVENT->summary; $entry['id'] = $row['uid']; $data[] = $entry; } } return $data; } public function getEventWithUid($uid) { $query = "SELECT calendardata, calendarid, componenttype, uri FROM calendarobjects WHERE uid=". $this->sqlite->quote_string($uid); $res = $this->sqlite->query($query); $row = $this->sqlite->res2row($res); return $row; } + public function getAllCalendarEvents($calid) + { + $query = "SELECT calendardata, uid, componenttype, uri FROM calendarobjects WHERE calendarid=". + $this->sqlite->quote_string($calid); + $res = $this->sqlite->query($query); + $arr = $this->sqlite->res2arr($res); + return $arr; + } + public function editCalendarEntryForPage($id, $user, $params) { $settings = $this->getPersonalSettings($user); if($settings['timezone'] !== '' && $settings['timezone'] !== 'local') $timezone = new \DateTimeZone($settings['timezone']); elseif($settings['timezone'] === 'local') $timezone = new \DateTimeZone($params['detectedtz']); else $timezone = new \DateTimeZone('UTC'); $startDate = explode('-', $params['eventfrom']); $startTime = explode(':', $params['eventfromtime']); $endDate = explode('-', $params['eventto']); $endTime = explode(':', $params['eventtotime']); $uid = $params['uid']; $event = $this->getEventWithUid($uid); require_once('vendor/autoload.php'); if(!isset($event['calendardata'])) return false; $uri = $event['uri']; $calid = $event['calendarid']; $vcal = \Sabre\VObject\Reader::read($event['calendardata']); $vevent = $vcal->VEVENT; $vevent->summary = $params['eventname']; $dtStamp = new \DateTime(null, new \DateTimeZone('UTC')); $description = $params['eventdescription']; $vevent->remove('DESCRIPTION'); $vevent->remove('DTSTAMP'); $vevent->remove('LAST-MODIFIED'); $vevent->add('DTSTAMP', $dtStamp); $vevent->add('LAST-MODIFIED', $dtStamp); if($description !== '') $vevent->add('DESCRIPTION', $description); $dtStart = new \DateTime(); $dtStart->setTimezone($timezone); $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2])); if($params['allday'] != '1') $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0); $dtEnd = new \DateTime(); $dtEnd->setTimezone($timezone); $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2])); if($params['allday'] != '1') $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0); // According to the VCal spec, we need to add a whole day here if($params['allday'] == '1') $dtEnd->add(new \DateInterval('P1D')); $vevent->remove('DTSTART'); $vevent->remove('DTEND'); $dtStartEv = $vevent->add('DTSTART', $dtStart); $dtEndEv = $vevent->add('DTEND', $dtEnd); if($params['allday'] == '1') { $dtStartEv['VALUE'] = 'DATE'; $dtEndEv['VALUE'] = 'DATE'; } $now = new DateTime(); $eventStr = $vcal->serialize(); $query = "UPDATE calendarobjects SET calendardata=".$this->sqlite->quote_string($eventStr). ", lastmodified=".$this->sqlite->quote_string($now->getTimestamp()). ", firstoccurence=".$this->sqlite->quote_string($dtStart->getTimestamp()). ", lastoccurence=".$this->sqlite->quote_string($dtEnd->getTimestamp()). ", size=".strlen($eventStr). ", etag=".$this->sqlite->quote_string(md5($eventStr)). " WHERE uid=".$this->sqlite->quote_string($uid); $res = $this->sqlite->query($query); if($res !== false) { $this->updateSyncTokenLog($calid, $uri, 'modified'); return true; } return false; } public function deleteCalendarEntryForPage($id, $params) { $uid = $params['uid']; $event = $this->getEventWithUid($uid); $calid = $event['calendarid']; $uri = $event['uri']; $query = "DELETE FROM calendarobjects WHERE uid=".$this->sqlite->quote_string($uid); $res = $this->sqlite->query($query); if($res !== false) { $this->updateSyncTokenLog($calid, $uri, 'deleted'); } return true; } public function getSyncTokenForCalendar($calid) { $row = $this->getCalendarSettings($calid); if(isset($row['synctoken'])) return $row['synctoken']; return false; } public function operationNameToOperation($operationName) { switch($operationName) { case 'added': return 1; break; case 'modified': return 2; break; case 'deleted': return 3; break; } return false; } private function updateSyncTokenLog($calid, $uri, $operation) { $currentToken = $this->getSyncTokenForCalendar($calid); $operationCode = $this->operationNameToOperation($operation); if(($operationCode === false) || ($currentToken === false)) return false; $values = array($uri, $currentToken, $calid, $operationCode ); $query = "INSERT INTO calendarchanges (uri, synctoken, calendarid, operation) VALUES(". $this->sqlite->quote_and_join($values, ',').")"; $res = $this->sqlite->query($query); if($res === false) return false; $currentToken++; $query = "UPDATE calendars SET synctoken=".$this->sqlite->quote_string($currentToken)." WHERE id=". $this->sqlite->quote_string($calid); $res = $this->sqlite->query($query); return ($res !== false); } public function getSyncUrlForPage($id, $user = null) { if(is_null($user)) $user = $_SERVER['REMOTE_USER']; $calid = $this->getCalendarIdForPage($id); if($calid === false) return false; $calsettings = $this->getCalendarSettings($calid); if(!isset($calsettings['uri'])) return false; $syncurl = DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$user.'/'.$calsettings['uri']; return $syncurl; } + public function getPrivateURLForPage($id) + { + $calid = $this->getCalendarIdForPage($id); + if($calid === false) + return false; + + return $this->getPrivateURLForCalendar($calid); + } + + public function getPrivateURLForCalendar($calid) + { + $query = "SELECT url FROM calendartoprivateurlmapping WHERE calid=".$this->sqlite->quote_string($calid); + $res = $this->sqlite->query($query); + $row = $this->sqlite->res2row($res); + if(!isset($row['url'])) + { + $url = uniqid("dokuwiki-").".ics"; + $values = array( + $url, + $calid + ); + $query = "INSERT INTO calendartoprivateurlmapping (url, calid) VALUES(". + $this->sqlite->quote_and_join($values, ", ").")"; + $res = $this->sqlite->query($query); + if($res === false) + return false; + } + else + { + $url = $row['url']; + } + return DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$url; + } + + public function getCalendarForPrivateURL($url) + { + $query = "SELECT calid FROM calendartoprivateurlmapping WHERE url=".$this->sqlite->quote_string($url); + $res = $this->sqlite->query($query); + $row = $this->sqlite->res2row($res); + if(!isset($row['calid'])) + return false; + return $row['calid']; + } + + public function getCalendarAsICSFeed($calid) + { + $calSettings = $this->getCalendarSettings($calid); + if($calSettings === false) + return false; + $events = $this->getAllCalendarEvents($calid); + if($events === false) + return false; + + $out = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//DAVCal//DAVCal for DokuWiki//EN\nCALSCALE:GREGORIAN\nX-WR-CALNAME:"; + $out .= $calSettings['displayname']."\n"; + foreach($events as $event) + { + $out .= rtrim($event['calendardata']); + $out .= "\n"; + } + $out .= "END:VCALENDAR\n"; + return $out; + } + } diff --git a/ics.php b/ics.php new file mode 100644 --- /dev/null +++ b/ics.php @@ -0,0 +1,22 @@ +getCalendarForPrivateURL($icsFile); + +if($calid === false) + die("No calendar with this name known."); + +$stream = $hlp->getCalendarAsICSFeed($calid); +header("Content-Type: text/calendar"); +header("Content-Transfer-Encoding: Binary"); +header("Content-disposition: attachment; filename=\"calendar.ics\""); +echo $stream; \ No newline at end of file diff --git a/jstz.js b/jstz.js new file mode 100644 --- /dev/null +++ b/jstz.js @@ -0,0 +1,1313 @@ +(function (root) {/*global exports, Intl*/ +/** + * This script gives you the zone info key representing your device's time zone setting. + * + * @name jsTimezoneDetect + * @version 1.0.5 + * @author Jon Nylander + * @license MIT License - https://bitbucket.org/pellepim/jstimezonedetect/src/default/LICENCE.txt + * + * For usage and examples, visit: + * http://pellepim.bitbucket.org/jstz/ + * + * Copyright (c) Jon Nylander + */ + + +/** + * Namespace to hold all the code for timezone detection. + */ +var jstz = (function () { + 'use strict'; + var HEMISPHERE_SOUTH = 's', + + consts = { + DAY: 86400000, + HOUR: 3600000, + MINUTE: 60000, + SECOND: 1000, + BASELINE_YEAR: 2014, + MAX_SCORE: 864000000, // 10 days + AMBIGUITIES: { + 'America/Denver': ['America/Mazatlan'], + 'America/Chicago': ['America/Mexico_City'], + 'America/Santiago': ['America/Asuncion', 'America/Campo_Grande'], + 'America/Montevideo': ['America/Sao_Paulo'], + // Europe/Minsk should not be in this list... but Windows. + 'Asia/Beirut': ['Asia/Amman', 'Asia/Jerusalem', 'Europe/Helsinki', 'Asia/Damascus', 'Africa/Cairo', 'Asia/Gaza', 'Europe/Minsk'], + 'Pacific/Auckland': ['Pacific/Fiji'], + 'America/Los_Angeles': ['America/Santa_Isabel'], + 'America/New_York': ['America/Havana'], + 'America/Halifax': ['America/Goose_Bay'], + 'America/Godthab': ['America/Miquelon'], + 'Asia/Dubai': ['Asia/Yerevan'], + 'Asia/Jakarta': ['Asia/Krasnoyarsk'], + 'Asia/Shanghai': ['Asia/Irkutsk', 'Australia/Perth'], + 'Australia/Sydney': ['Australia/Lord_Howe'], + 'Asia/Tokyo': ['Asia/Yakutsk'], + 'Asia/Dhaka': ['Asia/Omsk'], + // In the real world Yerevan is not ambigous for Baku... but Windows. + 'Asia/Baku': ['Asia/Yerevan'], + 'Australia/Brisbane': ['Asia/Vladivostok'], + 'Pacific/Noumea': ['Asia/Vladivostok'], + 'Pacific/Majuro': ['Asia/Kamchatka', 'Pacific/Fiji'], + 'Pacific/Tongatapu': ['Pacific/Apia'], + 'Asia/Baghdad': ['Europe/Minsk', 'Europe/Moscow'], + 'Asia/Karachi': ['Asia/Yekaterinburg'], + 'Africa/Johannesburg': ['Asia/Gaza', 'Africa/Cairo'] + } + }, + + /** + * Gets the offset in minutes from UTC for a certain date. + * @param {Date} date + * @returns {Number} + */ + get_date_offset = function get_date_offset(date) { + var offset = -date.getTimezoneOffset(); + return (offset !== null ? offset : 0); + }, + + /** + * This function does some basic calculations to create information about + * the user's timezone. It uses REFERENCE_YEAR as a solid year for which + * the script has been tested rather than depend on the year set by the + * client device. + * + * Returns a key that can be used to do lookups in jstz.olson.timezones. + * eg: "720,1,2". + * + * @returns {String} + */ + lookup_key = function lookup_key() { + var january_offset = get_date_offset(new Date(consts.BASELINE_YEAR, 0, 2)), + june_offset = get_date_offset(new Date(consts.BASELINE_YEAR, 5, 2)), + diff = january_offset - june_offset; + + if (diff < 0) { + return january_offset + ",1"; + } else if (diff > 0) { + return june_offset + ",1," + HEMISPHERE_SOUTH; + } + + return january_offset + ",0"; + }, + + + /** + * Tries to get the time zone key directly from the operating system for those + * environments that support the ECMAScript Internationalization API. + */ + get_from_internationalization_api = function get_from_internationalization_api() { + if (typeof Intl === "undefined" || typeof Intl.DateTimeFormat === "undefined") { + return; + } + var format = Intl.DateTimeFormat(); + if (typeof format === "undefined" || typeof format.resolvedOptions === "undefined") { + return; + } + return format.resolvedOptions().timeZone; + }, + + /** + * Starting point for getting all the DST rules for a specific year + * for the current timezone (as described by the client system). + * + * Returns an object with start and end attributes, or false if no + * DST rules were found for the year. + * + * @param year + * @returns {Object} || {Boolean} + */ + dst_dates = function dst_dates(year) { + var yearstart = new Date(year, 0, 1, 0, 0, 1, 0).getTime(); + var yearend = new Date(year, 12, 31, 23, 59, 59).getTime(); + var current = yearstart; + var offset = (new Date(current)).getTimezoneOffset(); + var dst_start = null; + var dst_end = null; + + while (current < yearend - 86400000) { + var dateToCheck = new Date(current); + var dateToCheckOffset = dateToCheck.getTimezoneOffset(); + + if (dateToCheckOffset !== offset) { + if (dateToCheckOffset < offset) { + dst_start = dateToCheck; + } + if (dateToCheckOffset > offset) { + dst_end = dateToCheck; + } + offset = dateToCheckOffset; + } + + current += 86400000; + } + + if (dst_start && dst_end) { + return { + s: find_dst_fold(dst_start).getTime(), + e: find_dst_fold(dst_end).getTime() + }; + } + + return false; + }, + + /** + * Probably completely unnecessary function that recursively finds the + * exact (to the second) time when a DST rule was changed. + * + * @param a_date - The candidate Date. + * @param padding - integer specifying the padding to allow around the candidate + * date for finding the fold. + * @param iterator - integer specifying how many milliseconds to iterate while + * searching for the fold. + * + * @returns {Date} + */ + find_dst_fold = function find_dst_fold(a_date, padding, iterator) { + if (typeof padding === 'undefined') { + padding = consts.DAY; + iterator = consts.HOUR; + } + + var date_start = new Date(a_date.getTime() - padding).getTime(); + var date_end = a_date.getTime() + padding; + var offset = new Date(date_start).getTimezoneOffset(); + + var current = date_start; + + var dst_change = null; + while (current < date_end - iterator) { + var dateToCheck = new Date(current); + var dateToCheckOffset = dateToCheck.getTimezoneOffset(); + + if (dateToCheckOffset !== offset) { + dst_change = dateToCheck; + break; + } + current += iterator; + } + + if (padding === consts.DAY) { + return find_dst_fold(dst_change, consts.HOUR, consts.MINUTE); + } + + if (padding === consts.HOUR) { + return find_dst_fold(dst_change, consts.MINUTE, consts.SECOND); + } + + return dst_change; + }, + + windows7_adaptations = function windows7_adaptions(rule_list, preliminary_timezone, score, sample) { + if (score !== 'N/A') { + return score; + } + if (preliminary_timezone === 'Asia/Beirut') { + if (sample.name === 'Africa/Cairo') { + if (rule_list[6].s === 1398376800000 && rule_list[6].e === 1411678800000) { + return 0; + } + } + if (sample.name === 'Asia/Jerusalem') { + if (rule_list[6].s === 1395964800000 && rule_list[6].e === 1411858800000) { + return 0; + } + } + } else if (preliminary_timezone === 'America/Santiago') { + if (sample.name === 'America/Asuncion') { + if (rule_list[6].s === 1412481600000 && rule_list[6].e === 1397358000000) { + return 0; + } + } + if (sample.name === 'America/Campo_Grande') { + if (rule_list[6].s === 1413691200000 && rule_list[6].e === 1392519600000) { + return 0; + } + } + } else if (preliminary_timezone === 'America/Montevideo') { + if (sample.name === 'America/Sao_Paulo') { + if (rule_list[6].s === 1413687600000 && rule_list[6].e === 1392516000000) { + return 0; + } + } + } else if (preliminary_timezone === 'Pacific/Auckland') { + if (sample.name === 'Pacific/Fiji') { + if (rule_list[6].s === 1414245600000 && rule_list[6].e === 1396101600000) { + return 0; + } + } + } + + return score; + }, + + /** + * Takes the DST rules for the current timezone, and proceeds to find matches + * in the jstz.olson.dst_rules.zones array. + * + * Compares samples to the current timezone on a scoring basis. + * + * Candidates are ruled immediately if either the candidate or the current zone + * has a DST rule where the other does not. + * + * Candidates are ruled out immediately if the current zone has a rule that is + * outside the DST scope of the candidate. + * + * Candidates are included for scoring if the current zones rules fall within the + * span of the samples rules. + * + * Low score is best, the score is calculated by summing up the differences in DST + * rules and if the consts.MAX_SCORE is overreached the candidate is ruled out. + * + * Yah follow? :) + * + * @param rule_list + * @param preliminary_timezone + * @returns {*} + */ + best_dst_match = function best_dst_match(rule_list, preliminary_timezone) { + var score_sample = function score_sample(sample) { + var score = 0; + + for (var j = 0; j < rule_list.length; j++) { + + // Both sample and current time zone report DST during the year. + if (!!sample.rules[j] && !!rule_list[j]) { + + // The current time zone's DST rules are inside the sample's. Include. + if (rule_list[j].s >= sample.rules[j].s && rule_list[j].e <= sample.rules[j].e) { + score = 0; + score += Math.abs(rule_list[j].s - sample.rules[j].s); + score += Math.abs(sample.rules[j].e - rule_list[j].e); + + // The current time zone's DST rules are outside the sample's. Discard. + } else { + score = 'N/A'; + break; + } + + // The max score has been reached. Discard. + if (score > consts.MAX_SCORE) { + score = 'N/A'; + break; + } + } + } + + score = windows7_adaptations(rule_list, preliminary_timezone, score, sample); + + return score; + }; + var scoreboard = {}; + var dst_zones = jstz.olson.dst_rules.zones; + var dst_zones_length = dst_zones.length; + var ambiguities = consts.AMBIGUITIES[preliminary_timezone]; + + for (var i = 0; i < dst_zones_length; i++) { + var sample = dst_zones[i]; + var score = score_sample(dst_zones[i]); + + if (score !== 'N/A') { + scoreboard[sample.name] = score; + } + } + + for (var tz in scoreboard) { + if (scoreboard.hasOwnProperty(tz)) { + if (ambiguities.indexOf(tz) != -1) { + return tz; + } + } + } + + return preliminary_timezone; + }, + + /** + * Takes the preliminary_timezone as detected by lookup_key(). + * + * Builds up the current timezones DST rules for the years defined + * in the jstz.olson.dst_rules.years array. + * + * If there are no DST occurences for those years, immediately returns + * the preliminary timezone. Otherwise proceeds and tries to solve + * ambiguities. + * + * @param preliminary_timezone + * @returns {String} timezone_name + */ + get_by_dst = function get_by_dst(preliminary_timezone) { + var get_rules = function get_rules() { + var rule_list = []; + for (var i = 0; i < jstz.olson.dst_rules.years.length; i++) { + var year_rules = dst_dates(jstz.olson.dst_rules.years[i]); + rule_list.push(year_rules); + } + return rule_list; + }; + var check_has_dst = function check_has_dst(rules) { + for (var i = 0; i < rules.length; i++) { + if (rules[i] !== false) { + return true; + } + } + return false; + }; + var rules = get_rules(); + var has_dst = check_has_dst(rules); + + if (has_dst) { + return best_dst_match(rules, preliminary_timezone); + } + + return preliminary_timezone; + }, + + /** + * Uses get_timezone_info() to formulate a key to use in the olson.timezones dictionary. + * + * Returns an object with one function ".name()" + * + * @returns Object + */ + determine = function determine() { + var preliminary_tz = get_from_internationalization_api(); + + if (!preliminary_tz) { + preliminary_tz = jstz.olson.timezones[lookup_key()]; + + if (typeof consts.AMBIGUITIES[preliminary_tz] !== 'undefined') { + preliminary_tz = get_by_dst(preliminary_tz); + } + } + + return { + name: function () { + return preliminary_tz; + } + }; + }; + + return { + determine: determine + }; +}()); + + +jstz.olson = jstz.olson || {}; + +/** + * The keys in this dictionary are comma separated as such: + * + * First the offset compared to UTC time in minutes. + * + * Then a flag which is 0 if the timezone does not take daylight savings into account and 1 if it + * does. + * + * Thirdly an optional 's' signifies that the timezone is in the southern hemisphere, + * only interesting for timezones with DST. + * + * The mapped arrays is used for constructing the jstz.TimeZone object from within + * jstz.determine(); + */ +jstz.olson.timezones = { + '-720,0': 'Etc/GMT+12', + '-660,0': 'Pacific/Pago_Pago', + '-660,1,s': 'Pacific/Apia', // Why? Because windows... cry! + '-600,1': 'America/Adak', + '-600,0': 'Pacific/Honolulu', + '-570,0': 'Pacific/Marquesas', + '-540,0': 'Pacific/Gambier', + '-540,1': 'America/Anchorage', + '-480,1': 'America/Los_Angeles', + '-480,0': 'Pacific/Pitcairn', + '-420,0': 'America/Phoenix', + '-420,1': 'America/Denver', + '-360,0': 'America/Guatemala', + '-360,1': 'America/Chicago', + '-360,1,s': 'Pacific/Easter', + '-300,0': 'America/Bogota', + '-300,1': 'America/New_York', + '-270,0': 'America/Caracas', + '-240,1': 'America/Halifax', + '-240,0': 'America/Santo_Domingo', + '-240,1,s': 'America/Santiago', + '-210,1': 'America/St_Johns', + '-180,1': 'America/Godthab', + '-180,0': 'America/Argentina/Buenos_Aires', + '-180,1,s': 'America/Montevideo', + '-120,0': 'America/Noronha', + '-120,1': 'America/Noronha', + '-60,1': 'Atlantic/Azores', + '-60,0': 'Atlantic/Cape_Verde', + '0,0': 'UTC', + '0,1': 'Europe/London', + '60,1': 'Europe/Berlin', + '60,0': 'Africa/Lagos', + '60,1,s': 'Africa/Windhoek', + '120,1': 'Asia/Beirut', + '120,0': 'Africa/Johannesburg', + '180,0': 'Asia/Baghdad', + '180,1': 'Europe/Moscow', + '210,1': 'Asia/Tehran', + '240,0': 'Asia/Dubai', + '240,1': 'Asia/Baku', + '270,0': 'Asia/Kabul', + '300,1': 'Asia/Yekaterinburg', + '300,0': 'Asia/Karachi', + '330,0': 'Asia/Kolkata', + '345,0': 'Asia/Kathmandu', + '360,0': 'Asia/Dhaka', + '360,1': 'Asia/Omsk', + '390,0': 'Asia/Rangoon', + '420,1': 'Asia/Krasnoyarsk', + '420,0': 'Asia/Jakarta', + '480,0': 'Asia/Shanghai', + '480,1': 'Asia/Irkutsk', + '525,0': 'Australia/Eucla', + '525,1,s': 'Australia/Eucla', + '540,1': 'Asia/Yakutsk', + '540,0': 'Asia/Tokyo', + '570,0': 'Australia/Darwin', + '570,1,s': 'Australia/Adelaide', + '600,0': 'Australia/Brisbane', + '600,1': 'Asia/Vladivostok', + '600,1,s': 'Australia/Sydney', + '630,1,s': 'Australia/Lord_Howe', + '660,1': 'Asia/Kamchatka', + '660,0': 'Pacific/Noumea', + '690,0': 'Pacific/Norfolk', + '720,1,s': 'Pacific/Auckland', + '720,0': 'Pacific/Majuro', + '765,1,s': 'Pacific/Chatham', + '780,0': 'Pacific/Tongatapu', + '780,1,s': 'Pacific/Apia', + '840,0': 'Pacific/Kiritimati' +}; + +/* Build time: 2014-11-28 11:10:50Z Build by invoking python utilities/dst.py generate */ +jstz.olson.dst_rules = { + "years": [ + 2008, + 2009, + 2010, + 2011, + 2012, + 2013, + 2014 + ], + "zones": [ + { + "name": "Africa/Cairo", + "rules": [ + { + "e": 1219957200000, + "s": 1209074400000 + }, + { + "e": 1250802000000, + "s": 1240524000000 + }, + { + "e": 1285880400000, + "s": 1284069600000 + }, + false, + false, + false, + { + "e": 1411678800000, + "s": 1406844000000 + } + ] + }, + { + "name": "America/Asuncion", + "rules": [ + { + "e": 1205031600000, + "s": 1224388800000 + }, + { + "e": 1236481200000, + "s": 1255838400000 + }, + { + "e": 1270954800000, + "s": 1286078400000 + }, + { + "e": 1302404400000, + "s": 1317528000000 + }, + { + "e": 1333854000000, + "s": 1349582400000 + }, + { + "e": 1364094000000, + "s": 1381032000000 + }, + { + "e": 1395543600000, + "s": 1412481600000 + } + ] + }, + { + "name": "America/Campo_Grande", + "rules": [ + { + "e": 1203217200000, + "s": 1224388800000 + }, + { + "e": 1234666800000, + "s": 1255838400000 + }, + { + "e": 1266721200000, + "s": 1287288000000 + }, + { + "e": 1298170800000, + "s": 1318737600000 + }, + { + "e": 1330225200000, + "s": 1350792000000 + }, + { + "e": 1361070000000, + "s": 1382241600000 + }, + { + "e": 1392519600000, + "s": 1413691200000 + } + ] + }, + { + "name": "America/Goose_Bay", + "rules": [ + { + "e": 1225594860000, + "s": 1205035260000 + }, + { + "e": 1257044460000, + "s": 1236484860000 + }, + { + "e": 1289098860000, + "s": 1268539260000 + }, + { + "e": 1320555600000, + "s": 1299988860000 + }, + { + "e": 1352005200000, + "s": 1331445600000 + }, + { + "e": 1383454800000, + "s": 1362895200000 + }, + { + "e": 1414904400000, + "s": 1394344800000 + } + ] + }, + { + "name": "America/Havana", + "rules": [ + { + "e": 1224997200000, + "s": 1205643600000 + }, + { + "e": 1256446800000, + "s": 1236488400000 + }, + { + "e": 1288501200000, + "s": 1268542800000 + }, + { + "e": 1321160400000, + "s": 1300597200000 + }, + { + "e": 1352005200000, + "s": 1333256400000 + }, + { + "e": 1383454800000, + "s": 1362891600000 + }, + { + "e": 1414904400000, + "s": 1394341200000 + } + ] + }, + { + "name": "America/Mazatlan", + "rules": [ + { + "e": 1225008000000, + "s": 1207472400000 + }, + { + "e": 1256457600000, + "s": 1238922000000 + }, + { + "e": 1288512000000, + "s": 1270371600000 + }, + { + "e": 1319961600000, + "s": 1301821200000 + }, + { + "e": 1351411200000, + "s": 1333270800000 + }, + { + "e": 1382860800000, + "s": 1365325200000 + }, + { + "e": 1414310400000, + "s": 1396774800000 + } + ] + }, + { + "name": "America/Mexico_City", + "rules": [ + { + "e": 1225004400000, + "s": 1207468800000 + }, + { + "e": 1256454000000, + "s": 1238918400000 + }, + { + "e": 1288508400000, + "s": 1270368000000 + }, + { + "e": 1319958000000, + "s": 1301817600000 + }, + { + "e": 1351407600000, + "s": 1333267200000 + }, + { + "e": 1382857200000, + "s": 1365321600000 + }, + { + "e": 1414306800000, + "s": 1396771200000 + } + ] + }, + { + "name": "America/Miquelon", + "rules": [ + { + "e": 1225598400000, + "s": 1205038800000 + }, + { + "e": 1257048000000, + "s": 1236488400000 + }, + { + "e": 1289102400000, + "s": 1268542800000 + }, + { + "e": 1320552000000, + "s": 1299992400000 + }, + { + "e": 1352001600000, + "s": 1331442000000 + }, + { + "e": 1383451200000, + "s": 1362891600000 + }, + { + "e": 1414900800000, + "s": 1394341200000 + } + ] + }, + { + "name": "America/Santa_Isabel", + "rules": [ + { + "e": 1225011600000, + "s": 1207476000000 + }, + { + "e": 1256461200000, + "s": 1238925600000 + }, + { + "e": 1288515600000, + "s": 1270375200000 + }, + { + "e": 1319965200000, + "s": 1301824800000 + }, + { + "e": 1351414800000, + "s": 1333274400000 + }, + { + "e": 1382864400000, + "s": 1365328800000 + }, + { + "e": 1414314000000, + "s": 1396778400000 + } + ] + }, + { + "name": "America/Sao_Paulo", + "rules": [ + { + "e": 1203213600000, + "s": 1224385200000 + }, + { + "e": 1234663200000, + "s": 1255834800000 + }, + { + "e": 1266717600000, + "s": 1287284400000 + }, + { + "e": 1298167200000, + "s": 1318734000000 + }, + { + "e": 1330221600000, + "s": 1350788400000 + }, + { + "e": 1361066400000, + "s": 1382238000000 + }, + { + "e": 1392516000000, + "s": 1413687600000 + } + ] + }, + { + "name": "Asia/Amman", + "rules": [ + { + "e": 1225404000000, + "s": 1206655200000 + }, + { + "e": 1256853600000, + "s": 1238104800000 + }, + { + "e": 1288303200000, + "s": 1269554400000 + }, + { + "e": 1319752800000, + "s": 1301608800000 + }, + false, + false, + { + "e": 1414706400000, + "s": 1395957600000 + } + ] + }, + { + "name": "Asia/Damascus", + "rules": [ + { + "e": 1225486800000, + "s": 1207260000000 + }, + { + "e": 1256850000000, + "s": 1238104800000 + }, + { + "e": 1288299600000, + "s": 1270159200000 + }, + { + "e": 1319749200000, + "s": 1301608800000 + }, + { + "e": 1351198800000, + "s": 1333058400000 + }, + { + "e": 1382648400000, + "s": 1364508000000 + }, + { + "e": 1414702800000, + "s": 1395957600000 + } + ] + }, + { + "name": "Asia/Dubai", + "rules": [ + false, + false, + false, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Gaza", + "rules": [ + { + "e": 1219957200000, + "s": 1206655200000 + }, + { + "e": 1252015200000, + "s": 1238104800000 + }, + { + "e": 1281474000000, + "s": 1269640860000 + }, + { + "e": 1312146000000, + "s": 1301608860000 + }, + { + "e": 1348178400000, + "s": 1333058400000 + }, + { + "e": 1380229200000, + "s": 1364508000000 + }, + { + "e": 1411678800000, + "s": 1395957600000 + } + ] + }, + { + "name": "Asia/Irkutsk", + "rules": [ + { + "e": 1224957600000, + "s": 1206813600000 + }, + { + "e": 1256407200000, + "s": 1238263200000 + }, + { + "e": 1288461600000, + "s": 1269712800000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Jerusalem", + "rules": [ + { + "e": 1223161200000, + "s": 1206662400000 + }, + { + "e": 1254006000000, + "s": 1238112000000 + }, + { + "e": 1284246000000, + "s": 1269561600000 + }, + { + "e": 1317510000000, + "s": 1301616000000 + }, + { + "e": 1348354800000, + "s": 1333065600000 + }, + { + "e": 1382828400000, + "s": 1364515200000 + }, + { + "e": 1414278000000, + "s": 1395964800000 + } + ] + }, + { + "name": "Asia/Kamchatka", + "rules": [ + { + "e": 1224943200000, + "s": 1206799200000 + }, + { + "e": 1256392800000, + "s": 1238248800000 + }, + { + "e": 1288450800000, + "s": 1269698400000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Krasnoyarsk", + "rules": [ + { + "e": 1224961200000, + "s": 1206817200000 + }, + { + "e": 1256410800000, + "s": 1238266800000 + }, + { + "e": 1288465200000, + "s": 1269716400000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Omsk", + "rules": [ + { + "e": 1224964800000, + "s": 1206820800000 + }, + { + "e": 1256414400000, + "s": 1238270400000 + }, + { + "e": 1288468800000, + "s": 1269720000000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Vladivostok", + "rules": [ + { + "e": 1224950400000, + "s": 1206806400000 + }, + { + "e": 1256400000000, + "s": 1238256000000 + }, + { + "e": 1288454400000, + "s": 1269705600000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Yakutsk", + "rules": [ + { + "e": 1224954000000, + "s": 1206810000000 + }, + { + "e": 1256403600000, + "s": 1238259600000 + }, + { + "e": 1288458000000, + "s": 1269709200000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Yekaterinburg", + "rules": [ + { + "e": 1224968400000, + "s": 1206824400000 + }, + { + "e": 1256418000000, + "s": 1238274000000 + }, + { + "e": 1288472400000, + "s": 1269723600000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Asia/Yerevan", + "rules": [ + { + "e": 1224972000000, + "s": 1206828000000 + }, + { + "e": 1256421600000, + "s": 1238277600000 + }, + { + "e": 1288476000000, + "s": 1269727200000 + }, + { + "e": 1319925600000, + "s": 1301176800000 + }, + false, + false, + false + ] + }, + { + "name": "Australia/Lord_Howe", + "rules": [ + { + "e": 1207407600000, + "s": 1223134200000 + }, + { + "e": 1238857200000, + "s": 1254583800000 + }, + { + "e": 1270306800000, + "s": 1286033400000 + }, + { + "e": 1301756400000, + "s": 1317483000000 + }, + { + "e": 1333206000000, + "s": 1349537400000 + }, + { + "e": 1365260400000, + "s": 1380987000000 + }, + { + "e": 1396710000000, + "s": 1412436600000 + } + ] + }, + { + "name": "Australia/Perth", + "rules": [ + { + "e": 1206813600000, + "s": 1224957600000 + }, + false, + false, + false, + false, + false, + false + ] + }, + { + "name": "Europe/Helsinki", + "rules": [ + { + "e": 1224982800000, + "s": 1206838800000 + }, + { + "e": 1256432400000, + "s": 1238288400000 + }, + { + "e": 1288486800000, + "s": 1269738000000 + }, + { + "e": 1319936400000, + "s": 1301187600000 + }, + { + "e": 1351386000000, + "s": 1332637200000 + }, + { + "e": 1382835600000, + "s": 1364691600000 + }, + { + "e": 1414285200000, + "s": 1396141200000 + } + ] + }, + { + "name": "Europe/Minsk", + "rules": [ + { + "e": 1224979200000, + "s": 1206835200000 + }, + { + "e": 1256428800000, + "s": 1238284800000 + }, + { + "e": 1288483200000, + "s": 1269734400000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Europe/Moscow", + "rules": [ + { + "e": 1224975600000, + "s": 1206831600000 + }, + { + "e": 1256425200000, + "s": 1238281200000 + }, + { + "e": 1288479600000, + "s": 1269730800000 + }, + false, + false, + false, + false + ] + }, + { + "name": "Pacific/Apia", + "rules": [ + false, + false, + false, + { + "e": 1301752800000, + "s": 1316872800000 + }, + { + "e": 1333202400000, + "s": 1348927200000 + }, + { + "e": 1365256800000, + "s": 1380376800000 + }, + { + "e": 1396706400000, + "s": 1411826400000 + } + ] + }, + { + "name": "Pacific/Fiji", + "rules": [ + false, + false, + { + "e": 1269698400000, + "s": 1287842400000 + }, + { + "e": 1327154400000, + "s": 1319292000000 + }, + { + "e": 1358604000000, + "s": 1350741600000 + }, + { + "e": 1390050000000, + "s": 1382796000000 + }, + { + "e": 1421503200000, + "s": 1414850400000 + } + ] + } + ] +}; if (typeof exports !== 'undefined') { + exports.jstz = jstz; + } else { + root.jstz = jstz; + } +})(this); diff --git a/lang/en/lang.php b/lang/en/lang.php --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -1,39 +1,40 @@ '); jQuery.post( DOKU_BASE + 'lib/exe/ajax.php', { call: 'plugin_davcal', id: JSINFO.id, action: 'saveSettings', params: postArray }, function(data) { var result = data['result']; var html = data['html']; jQuery('#dw_davcal__ajaxsettings').html(html); if(result === true) { location.reload(); } } ); }; dialogButtons[LANG.plugins.davcal['cancel']] = function () { dw_davcal__modals.hideSettingsDialog(); }; dw_davcal__modals.$settingsDialog = jQuery(document.createElement('div')) .dialog({ autoOpen: false, draggable: true, title: LANG.plugins.davcal['settings'], resizable: true, buttons: dialogButtons, }) .html( '
' + //'' + '' + '' + '' + '' + + '' + '
' + LANG.plugins.davcal['use_lang_tz'] + '
' + LANG.plugins.davcal['timezone'] + '
' + LANG.plugins.davcal['weeknumbers'] + '
' + LANG.plugins.davcal['only_workweek'] + '
' + LANG.plugins.davcal['sync_url'] + '
' + LANG.plugins.davcal['private_url'] + '
' + '
' + '
' ) .parent() .attr('id','dw_davcal__settings') .show() .appendTo('.dokuwiki:first'); jQuery('#dw_davcal__settings').position({ my: "center", at: "center", of: window }); jQuery('#dw_davcal__settings_syncurl').on('click', function() { jQuery(this).select(); }); + jQuery('#dw_davcal__settings_privateurl').on('click', function() { + jQuery(this).select(); + }); + // attach event handlers jQuery('#dw_davcal__settings .ui-dialog-titlebar-close').click(function(){ dw_davcal__modals.hideSettingsDialog(); }); var $tzdropdown = jQuery('#dw_davcal__settings_timezone'); jQuery('#fullCalendarTimezoneList option').each(function() { jQuery('