Merge branch 'dev/kolab3'

Conflicts:
	plugins/calendar/drivers/kolab/kolab_driver.php
This commit is contained in:
Jeroen van Meeuwen (Kolab Systems) 2012-05-23 10:24:27 +01:00
commit 2cf2cbcca8
49 changed files with 8719 additions and 2141 deletions

View file

@ -4,4 +4,5 @@
*~
config.inc.php
skins/*
!skins/default
!skins/default
!skins/larry

18
plugins/calendar/README Normal file
View file

@ -0,0 +1,18 @@
A calendar module for Roundcube
-------------------------------
This plugin currently supports a local database as well as a Kolab groupware
server as backends for calendar and event storage. For both drivers, some
initialization of the local database is necessary. To do so, execute the
SQL commands in drivers/<yourchoice>/SQL/<yourdatabase>.sql
The client-side calendar UI relies on the 'fullcalenda'r project by Adam Arshaw
with extensions made for the use in Roundcube. All changes are published in
an official fork at https://github.com/roundcube/fullcalendar
For recurring event computation, some utility classes from the Horde project
are used. They are packaged in a slightly modified version with this plugin.
iCalendar parsing is done with the help of the Horde_iCalendar class. A copy
of that class with all its dependencies is part of this package. In order
to update it, execute lib/get_horde_icalendar.sh > lib/Horde_iCalendar.php

View file

@ -13,7 +13,7 @@
+ View: 3.3: Display modes (agenda / day / week / month)
+ Day / Week / Month
+ List (Agenda) view
- Add selection for date range
+ Add selection for date range
- Individual days selection
+ Show list of calendars in a (hideable) drawer
+ View: 3.1: Folder list
@ -25,13 +25,13 @@
+ View: 3.9: Alter event with drag/drop
+ Option: 4.12: Set default reminder time
+ Option: 3.23: Specify folder for new event (prefs)
- Option: Set date/time format in prefs
+ Option: Set date/time format in prefs
+ Receive: 1.20: Invitation handling
- Jump to calendar view from mail ("Show event")
- Allow to re-send invitations
- Implement iTIP delegation
- View: 3.4: Fish-Eye View For Busy Days
+ View: 3.4: Fish-Eye View For Busy Days
+ View: 3.8: Color according to calendar and category (similar to Kontact)
+ Support for multiple calendars (replace categories)
@ -39,9 +39,10 @@
+ Colors for calendars should be user-configurable
+ ICS parser/generator (http://code.google.com/p/qcal/)
- Script to send event alarms by email (in cronjob)
- Export *with* attachments
- Importing ICS files (upload, drag & drop)
- Remember last visited view
- Create/manage invdividual views
- Support for tasks/todos with task list view (ordered by date/time)
+ Importing ICS files (upload, drag & drop)

View file

@ -99,9 +99,9 @@ class calendar extends rcube_plugin
// set user's timezone
$this->timezone = new DateTimeZone($this->rc->config->get('timezone', 'GMT'));
$now = new DateTime('now', $this->timezone);
$this->timezone_offset = $now->format('Z') / 3600;
$this->dst_active = $now->format('I');
$this->gmt_offset = $now->getOffset();
$this->dst_active = $now->format('I');
$this->timezone_offset = $this->gmt_offset / 3600 - $this->dst_active;
require($this->home . '/lib/calendar_ui.php');
$this->ui = new calendar_ui($this);
@ -205,7 +205,7 @@ class calendar extends rcube_plugin
switch ($driver_name) {
case "kolab":
$this->require_plugin('kolab_core');
$this->require_plugin('libkolab');
default:
$this->driver = new $driver_class($this);
break;
@ -1069,6 +1069,11 @@ class calendar extends rcube_plugin
$settings['identity'] = array('name' => $identity['name'], 'email' => $identity['email'], 'emails' => ';' . join(';', $identity['emails']));
}
// define list of file types which can be displayed inline
// same as in program/steps/mail/show.inc
$mimetypes = $this->rc->config->get('client_mimetypes', 'text/plain,text/html,text/xml,image/jpeg,image/gif,image/png,application/x-javascript,application/pdf,application/x-shockwave-flash');
$settings['mimetypes'] = is_string($mimetypes) ? explode(',', $mimetypes) : (array)$mimetypes;
return $settings;
}
@ -1259,7 +1264,47 @@ class calendar extends rcube_plugin
return false;
}
/**
* Get the next alarm (time & action) for the given event
*
* @param array Event data
* @return array Hash array with alarm time/type or null if no alarms are configured
*/
public static function get_next_alarm($event)
{
if (!$event['alarms'])
return null;
// TODO: handle multiple alarms (currently not supported)
list($trigger, $action) = explode(':', $event['alarms'], 2);
$notify = self::parse_alaram_value($trigger);
if (!empty($notify[1])){ // offset
$mult = 1;
switch ($notify[1]) {
case '-S': $mult = -1; break;
case '+S': $mult = 1; break;
case '-M': $mult = -60; break;
case '+M': $mult = 60; break;
case '-H': $mult = -3600; break;
case '+H': $mult = 3600; break;
case '-D': $mult = -86400; break;
case '+D': $mult = 86400; break;
case '-W': $mult = -604800; break;
case '+W': $mult = 604800; break;
}
$offset = $notify[0] * $mult;
$refdate = $mult > 0 ? $event['end'] : $event['start'];
$notify_at = $refdate + $offset;
}
else { // absolute timestamp
$notify_at = $notify[0];
}
return array('time' => $notify_at, 'action' => $action ? strtoupper($action) : 'DISPLAY');
}
/**
* Convert the internal structured data into a vcalendar rrule 2.0 string
*/
@ -1278,7 +1323,7 @@ class calendar extends rcube_plugin
case 'EXDATE':
foreach ((array)$val as $i => $ex)
$val[$i] = gmdate('Ymd\THis', $ex);
$val = join(',', $val);
$val = join(',', (array)$val);
break;
}
$rrule .= $k . '=' . $val . ';';
@ -1534,12 +1579,8 @@ class calendar extends rcube_plugin
}
ob_end_clean();
send_nocacheing_headers();
if (isset($_SESSION['calendar_attachment']))
$attachment = $_SESSION['calendar_attachment'];
else
$attachment = $_SESSION['calendar_attachment'] = $this->driver->get_attachment($id, $event);
$attachment = $GLOBALS['calendar_attachment'] = $this->driver->get_attachment($id, $event);
// show part page
if (!empty($_GET['_frame'])) {
@ -1550,16 +1591,30 @@ class calendar extends rcube_plugin
exit;
}
$this->rc->session->remove('calendar_attachment');
if ($attachment) {
$mimetype = strtolower($attachment['mimetype']);
// allow post-processing of the attachment body
$part = new rcube_message_part;
$part->filename = $attachment['name'];
$part->size = $attachment['size'];
$part->mimetype = $attachment['mimetype'];
$plugin = $this->rc->plugins->exec_hook('message_part_get', array(
'body' => $this->driver->get_attachment_body($id, $event),
'mimetype' => strtolower($attachment['mimetype']),
'download' => !empty($_GET['_download']),
'part' => $part,
));
if ($plugin['abort'])
exit;
$mimetype = $plugin['mimetype'];
list($ctype_primary, $ctype_secondary) = explode('/', $mimetype);
$browser = $this->rc->output->browser;
// send download headers
if ($_GET['_download']) {
if ($plugin['download']) {
header("Content-Type: application/octet-stream");
if ($browser->ie)
header("Content-Type: application/force-download");
@ -1573,13 +1628,11 @@ class calendar extends rcube_plugin
header("Content-Transfer-Encoding: binary");
}
$body = $this->driver->get_attachment_body($id, $event);
// display page, @TODO: support text/plain (and maybe some other text formats)
if ($mimetype == 'text/html' && empty($_GET['_download'])) {
$OUTPUT = new rcube_html_page();
// @TODO: use washtml on $body
$OUTPUT->write($body);
$OUTPUT->write($plugin['body']);
}
else {
// don't kill the connection if download takes more than 30 sec.
@ -1598,7 +1651,7 @@ class calendar extends rcube_plugin
$disposition = !empty($_GET['_download']) ? 'attachment' : 'inline';
header("Content-Disposition: $disposition; filename=\"$filename\"");
echo $body;
echo $plugin['body'];
}
exit;
@ -1614,7 +1667,7 @@ class calendar extends rcube_plugin
*/
public function attachment_frame($attrib)
{
$attachment = $_SESSION['calendar_attachment'];
$attachment = $GLOBALS['calendar_attachment'];
$mimetype = strtolower($attachment['mimetype']);
list($ctype_primary, $ctype_secondary) = explode('/', $mimetype);
@ -2167,15 +2220,15 @@ class calendar extends rcube_plugin
$charset = RCMAIL_CHARSET;
// establish imap connection
$this->rc->imap_connect();
$this->rc->imap->set_mailbox($mbox);
$imap = $this->rc->get_storage();
$imap->set_mailbox($mbox);
if ($uid && $mime_id) {
list($mime_id, $index) = explode(':', $mime_id);
$part = $this->rc->imap->get_message_part($uid, $mime_id);
$part = $imap->get_message_part($uid, $mime_id);
if ($part->ctype_parameters['charset'])
$charset = $part->ctype_parameters['charset'];
$headers = $this->rc->imap->get_headers($uid);
$headers = $imap->get_message_headers($uid);
}
$events = $this->get_ical()->import($part, $charset);
@ -2312,8 +2365,8 @@ class calendar extends rcube_plugin
$event = array();
// establish imap connection
$this->rc->imap_connect();
$this->rc->imap->set_mailbox($mbox);
$imap = $this->rc->get_storage();
$imap->set_mailbox($mbox);
$message = new rcube_message($uid);
if ($message->headers) {
@ -2331,7 +2384,7 @@ class calendar extends rcube_plugin
foreach ((array)$message->attachments as $part) {
$attachment = array(
'data' => $this->rc->imap->get_message_part($uid, $part->mime_id, $part),
'data' => $imap->get_message_part($uid, $part->mime_id, $part),
'size' => $part->size,
'name' => $part->filename,
'mimetype' => $part->mimetype,

View file

@ -260,7 +260,7 @@ function rcube_calendar_ui(settings)
var qstring = '_id='+urlencode(att.id)+'&_event='+urlencode(event.recurrence_id||event.id)+'&_cal='+urlencode(event.calendar);
// open attachment in frame if it's of a supported mimetype
if (id && att.mimetype && $.inArray(att.mimetype, rcmail.mimetypes)>=0) {
if (id && att.mimetype && $.inArray(att.mimetype, settings.mimetypes)>=0) {
rcmail.attachment_win = window.open(rcmail.env.comm_path+'&_action=get-attachment&'+qstring+'&_frame=1', 'rcubeeventattachment');
if (rcmail.attachment_win) {
window.setTimeout(function() { rcmail.attachment_win.focus(); }, 10);
@ -337,7 +337,7 @@ function rcube_calendar_ui(settings)
var $dialog = $("#eventshow").dialog('close').removeClass().addClass('uidialog');
var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:false };
me.selected_event = event;
$dialog.find('div.event-section, div.event-line').hide();
$('#event-title').html(Q(event.title)).show();
@ -363,9 +363,10 @@ function rcube_calendar_ui(settings)
if (event.free_busy)
$('#event-free-busy').show().children('.event-text').html(Q(rcmail.gettext(event.free_busy, 'calendar')));
if (event.priority > 0) {
var priolabels = [ '', rcmail.gettext('high'), rcmail.gettext('highest'), '', '', rcmail.gettext('normal'), '', '', rcmail.gettext('low'), rcmail.gettext('lowest') ];
var priolabels = [ '', rcmail.gettext('highest'), rcmail.gettext('high'), '', '', rcmail.gettext('normal'), '', '', rcmail.gettext('low'), rcmail.gettext('lowest') ];
$('#event-priority').show().children('.event-text').html(Q(event.priority+' '+priolabels[event.priority]));
}
if (event.sensitivity != 0) {
var sensitivityclasses = { 0:'public', 1:'private', 2:'confidential' };
$('#event-sensitivity').show().children('.event-text').html(Q(sensitivitylabels[event.sensitivity]));
@ -415,7 +416,7 @@ function rcube_calendar_ui(settings)
$('#event-rsvp')[(rsvp?'show':'hide')]();
$('#event-rsvp .rsvp-buttons input').prop('disabled', false).filter('input[rel='+rsvp+']').prop('disabled', true);
}
var buttons = {};
if (calendar.editable && event.editable !== false) {
buttons[rcmail.gettext('edit', 'calendar')] = function() {
@ -2348,7 +2349,7 @@ function rcube_calendar_ui(settings)
event.end = new Date(event.start.getTime() + (allDay ? DAY_MS : HOUR_MS));
}
// moved to all-day section: set times to 12:00 - 13:00
if (allDay && !event.allday) {
if (allDay && !event.allDay) {
event.start.setHours(12);
event.start.setMinutes(0);
event.start.setSeconds(0);
@ -2357,7 +2358,7 @@ function rcube_calendar_ui(settings)
event.end.setSeconds(0);
}
// moved from all-day section: set times to working hours
else if (event.allday && !allDay) {
else if (event.allDay && !allDay) {
var newstart = event.start.getTime();
revertFunc(); // revert to get original duration
var numdays = Math.max(1, Math.round((event.end.getTime() - event.start.getTime()) / DAY_MS)) - 1;
@ -2407,7 +2408,7 @@ function rcube_calendar_ui(settings)
}
},
viewRender: function(view) {
if (view.name == 'month')
if (fc && view.name == 'month')
fc.fullCalendar('option', 'maxHeight', Math.floor((view.element.parent().height()-18) / 6) - 35);
}
});
@ -2436,7 +2437,7 @@ function rcube_calendar_ui(settings)
/* Time completions */
var result = [];
var now = new Date();
var st, start = (this.element.attr('id').indexOf('endtime') > 0
var st, start = (String(this.element.attr('id')).indexOf('endtime') > 0
&& (st = $('#edit-starttime').val())
&& $('#edit-startdate').val() == $('#edit-enddate').val())
? parse_datetime(st, '') : null;

View file

@ -396,31 +396,13 @@ class database_driver extends calendar_driver
*/
private function _get_notification($event)
{
if ($event['alarms']) {
list($trigger, $action) = explode(':', $event['alarms']);
$notify = calendar::parse_alaram_value($trigger);
if (!empty($notify[1])){ // offset
$mult = 1;
switch ($notify[1]) {
case '-M': $mult = -60; break;
case '+M': $mult = 60; break;
case '-H': $mult = -3600; break;
case '+H': $mult = 3600; break;
case '-D': $mult = -86400; break;
case '+D': $mult = 86400; break;
}
$offset = $notify[0] * $mult;
$refdate = $mult > 0 ? $event['end'] : $event['start'];
$notify_at = $refdate + $offset;
}
else { // absolute timestamp
$notify_at = $notify[0];
}
if ($event['alarms'] && $event['start'] > time()) {
$alarm = calendar::get_next_alarm($event);
if ($event['start'] > time())
return date('Y-m-d H:i:s', $notify_at);
if ($alarm['time'] && $alarm['action'] == 'DISPLAY')
return date('Y-m-d H:i:s', $alarm['time']);
}
return null;
}

View file

@ -8,9 +8,12 @@
CREATE TABLE IF NOT EXISTS `kolab_alarms` (
`event_id` VARCHAR(255) NOT NULL,
`user_id` int(10) UNSIGNED NOT NULL,
`notifyat` DATETIME DEFAULT NULL,
`dismissed` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY(`event_id`)
PRIMARY KEY(`event_id`),
CONSTRAINT `fk_kolab_alarms_user_id` FOREIGN KEY (`user_id`)
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*!40000 ENGINE=INNODB */;
CREATE TABLE IF NOT EXISTS `itipinvitations` (

View file

@ -7,7 +7,7 @@
* @author Thomas Bruederli <bruederli@kolabsys.com>
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@ -26,6 +26,9 @@
class kolab_calendar
{
const COLOR_KEY_SHARED = '/shared/vendor/kolab/color';
const COLOR_KEY_PRIVATE = '/shared/vendor/kolab/color';
public $id;
public $ready = false;
public $readonly = true;
@ -35,17 +38,10 @@ class kolab_calendar
public $storage;
private $cal;
private $events;
private $id2uid;
private $events = array();
private $imap_folder = 'INBOX/Calendar';
private $namespace;
private $search_fields = array('title', 'description', 'location', '_attendees');
private $sensitivity_map = array('public', 'private', 'confidential');
private $priority_map = array('low' => 9, 'normal' => 5, 'high' => 1);
private $role_map = array('REQ-PARTICIPANT' => 'required', 'OPT-PARTICIPANT' => 'optional', 'CHAIR' => 'resource');
private $status_map = array('NEEDS-ACTION' => 'none', 'TENTATIVE' => 'tentative', 'CONFIRMED' => 'accepted', 'ACCEPTED' => 'accepted', 'DECLINED' => 'declined');
private $month_map = array('', 'january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december');
private $weekday_map = array('MO'=>'monday', 'TU'=>'tuesday', 'WE'=>'wednesday', 'TH'=>'thursday', 'FR'=>'friday', 'SA'=>'saturday', 'SU'=>'sunday');
/**
@ -59,12 +55,11 @@ class kolab_calendar
$this->imap_folder = $imap_folder;
// ID is derrived from folder name
$this->id = rcube_kolab::folder_id($this->imap_folder);
$this->id = kolab_storage::folder_id($this->imap_folder);
// fetch objects from the given IMAP folder
$this->storage = rcube_kolab::get_storage($this->imap_folder);
$this->ready = !PEAR::isError($this->storage);
$this->storage = kolab_storage::get_folder($this->imap_folder);
$this->ready = $this->storage && !PEAR::isError($this->storage);
// Set readonly and alarms flags according to folder permissions
if ($this->ready) {
@ -73,8 +68,8 @@ class kolab_calendar
$this->alarms = true;
}
else {
$rights = $this->storage->_folder->getMyRights();
if (!PEAR::isError($rights)) {
$rights = $this->storage->get_myrights();
if ($rights && !PEAR::isError($rights)) {
if (strpos($rights, 'i') !== false)
$this->readonly = false;
}
@ -96,7 +91,7 @@ class kolab_calendar
*/
public function get_name()
{
$folder = rcube_kolab::object_name($this->imap_folder, $this->namespace);
$folder = kolab_storage::object_name($this->imap_folder, $this->namespace);
return $folder;
}
@ -119,7 +114,7 @@ class kolab_calendar
*/
public function get_owner()
{
return $this->storage->_folder->getOwner();
return $this->storage->get_owner();
}
@ -130,10 +125,7 @@ class kolab_calendar
*/
public function get_namespace()
{
if ($this->namespace === null) {
$this->namespace = rcube_kolab::folder_namespace($this->imap_folder);
}
return $this->namespace;
return $this->storage->get_namespace();
}
@ -154,7 +146,8 @@ class kolab_calendar
public function get_color()
{
// color is defined in folder METADATA
if ($color = $this->storage->_folder->getKolabAttribute('color', HORDE_ANNOT_READ_PRIVATE_SHARED)) {
$metadata = $this->storage->get_metadata(array(self::COLOR_KEY_PRIVATE, self::COLOR_KEY_SHARED));
if (($color = $metadata[self::COLOR_KEY_PRIVATE]) || ($color = $metadata[self::COLOR_KEY_SHARED])) {
return $color;
}
@ -168,19 +161,11 @@ class kolab_calendar
}
/**
* Return the corresponding Kolab_Folder instance
* Return the corresponding kolab_storage_folder instance
*/
public function get_folder()
{
return $this->storage->_folder;
}
/**
* Getter for the attachment body
*/
public function get_attachment_body($id)
{
return $this->storage->getAttachment($id);
return $this->storage;
}
@ -189,17 +174,21 @@ class kolab_calendar
*/
public function get_event($id)
{
$this->_fetch_events();
// directly access storage object
if (!$this->events[$id] && ($record = $this->storage->get_object($id)))
$this->events[$id] = $this->_to_rcube_event($record);
// event not found, maybe a recurring instance is requested
if (!$this->events[$id]) {
$master_id = preg_replace('/-\d+$/', '', $id);
if ($this->events[$master_id] && $this->events[$master_id]['recurrence']) {
$master = $this->events[$master_id];
if ($record = $this->storage->get_object($master_id))
$this->events[$master_id] = $this->_to_rcube_event($record);
if (($master = $this->events[$master_id]) && $master['recurrence']) {
$this->_get_recurring_events($master, $master['start'], $master['start'] + 86400 * 365 * 10, $id);
}
}
return $this->events[$id];
}
@ -208,13 +197,21 @@ class kolab_calendar
* @param integer Event's new start (unix timestamp)
* @param integer Event's new end (unix timestamp)
* @param string Search query (optional)
* @param boolean Strip virtual events (optional)
* @param boolean Include virtual events (optional)
* @param array Additional parameters to query storage
* @return array A list of event records
*/
public function list_events($start, $end, $search = null, $virtual = 1)
public function list_events($start, $end, $search = null, $virtual = 1, $query = array())
{
$this->_fetch_events();
// query Kolab storage
$query[] = array('dtstart', '<=', $end);
$query[] = array('dtend', '>=', $start);
foreach ((array)$this->storage->select($query) as $record) {
$event = $this->_to_rcube_event($record);
$this->events[$event['id']] = $event;
}
if (!empty($search))
$search = mb_strtolower($search);
@ -275,9 +272,9 @@ class kolab_calendar
//generate new event from RC input
$object = $this->_from_rcube_event($event);
$saved = $this->storage->save($object);
$saved = $this->storage->save($object, 'event');
if (PEAR::isError($saved)) {
if (!$saved || PEAR::isError($saved)) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
@ -303,15 +300,15 @@ class kolab_calendar
public function update_event($event)
{
$updated = false;
$old = $this->storage->getObject($event['id']);
if (PEAR::isError($old))
$old = $this->storage->get_object($event['id']);
if (!$old || PEAR::isError($old))
return false;
$old['recurrence'] = ''; # clear old field, could have been removed in new, too
$object = array_merge($old, $this->_from_rcube_event($event));
$saved = $this->storage->save($object, $event['id']);
$object = $this->_from_rcube_event($event, $old);
$saved = $this->storage->save($object, 'event', $event['id']);
if (PEAR::isError($saved)) {
if (!$saved || PEAR::isError($saved)) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
@ -334,29 +331,15 @@ class kolab_calendar
*/
public function delete_event($event, $force = true)
{
$deleted = false;
$deleted = $this->storage->delete($event['id'], $force);
if (!$force) {
// Get IMAP object ID
$imap_uid = $this->storage->_getStorageId($event['id']);
}
$deleteme = $this->storage->delete($event['id'], $force);
if (PEAR::isError($deleteme)) {
if (!$deleted || PEAR::isError($deleted)) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error deleting event object from Kolab server:" . $deleteme->getMessage()),
'message' => "Error deleting event object from Kolab server"),
true, false);
}
else {
// Save IMAP object ID in session, will be used for restore action
if ($imap_uid)
$_SESSION['kolab_delete_uids'][$event['id']] = $imap_uid;
$deleted = true;
}
return $deleted;
}
@ -369,63 +352,18 @@ class kolab_calendar
*/
public function restore_event($event)
{
$imap_uid = $_SESSION['kolab_delete_uids'][$event['id']];
if (!$imap_uid)
return false;
$session = &Horde_Kolab_Session::singleton();
$imap = &$session->getImap();
if (is_object($imap) && is_a($imap, 'PEAR_Error')) {
$error = $imap;
if ($this->storage->undelete($event['id'])) {
return true;
}
else {
$result = $imap->select($this->imap_folder);
if (is_object($result) && is_a($result, 'PEAR_Error')) {
$error = $result;
}
else {
$result = $imap->undeleteMessages($imap_uid);
if (is_object($result) && is_a($result, 'PEAR_Error')) {
$error = $result;
}
else {
// re-sync the cache
$this->storage->synchronize();
}
}
}
if ($error) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error undeleting an event object(s) from the Kolab server:" . $error->getMessage()),
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error undeleting a contact object $uid from the Kolab server"),
true, false);
return false;
}
$rcmail = rcmail::get_instance();
$rcmail->session->remove('kolab_delete_uids');
return true;
}
/**
* Simply fetch all records and store them in private member vars
* We thereby rely on cahcing done by the Horde classes
*/
private function _fetch_events()
{
if (!isset($this->events)) {
$this->events = array();
foreach ((array)$this->storage->getObjects() as $record) {
$event = $this->_to_rcube_event($record);
$this->events[$event['id']] = $event;
}
}
return false;
}
@ -471,308 +409,99 @@ class kolab_calendar
/**
* Convert from Kolab_Format to internal representation
*/
private function _to_rcube_event($rec)
private function _to_rcube_event($record)
{
$start_time = date('H:i:s', $rec['start-date']);
$allday = $rec['_is_all_day'] || ($start_time == '00:00:00' && $start_time == date('H:i:s', $rec['end-date']));
if ($allday) { // in Roundcube all-day events only go from 12:00 to 13:00
$rec['start-date'] += 12 * 3600;
$rec['end-date'] -= 11 * 3600;
$rec['end-date'] -= $this->cal->gmt_offset - date('Z', $rec['end-date']); // shift times from server's timezone to user's timezone
$rec['start-date'] -= $this->cal->gmt_offset - date('Z', $rec['start-date']); // because generated with mktime() in Horde_Kolab_Format_Date::decodeDate()
// sanity check
if ($rec['end-date'] <= $rec['start-date'])
$rec['end-date'] += 86400;
}
// convert alarm time into internal format
if ($rec['alarm']) {
$alarm_value = $rec['alarm'];
$alarm_unit = 'M';
if ($rec['alarm'] % 1440 == 0) {
$alarm_value /= 1440;
$alarm_unit = 'D';
}
else if ($rec['alarm'] % 60 == 0) {
$alarm_value /= 60;
$alarm_unit = 'H';
}
$alarm_value *= -1;
}
// convert recurrence rules into internal pseudo-vcalendar format
if ($recurrence = $rec['recurrence']) {
$rrule = array(
'FREQ' => strtoupper($recurrence['cycle']),
'INTERVAL' => intval($recurrence['interval']),
);
if ($recurrence['range-type'] == 'number')
$rrule['COUNT'] = intval($recurrence['range']);
else if ($recurrence['range-type'] == 'date')
$rrule['UNTIL'] = $recurrence['range'];
if ($recurrence['day']) {
$byday = array();
$prefix = ($rrule['FREQ'] == 'MONTHLY' || $rrule['FREQ'] == 'YEARLY') ? intval($recurrence['daynumber'] ? $recurrence['daynumber'] : 1) : '';
foreach ($recurrence['day'] as $day)
$byday[] = $prefix . substr(strtoupper($day), 0, 2);
$rrule['BYDAY'] = join(',', $byday);
}
if ($recurrence['daynumber']) {
if ($recurrence['type'] == 'monthday' || $recurrence['type'] == 'daynumber')
$rrule['BYMONTHDAY'] = $recurrence['daynumber'];
else if ($recurrence['type'] == 'yearday')
$rrule['BYYEARDAY'] = $recurrence['daynumber'];
}
if ($recurrence['month']) {
$monthmap = array_flip($this->month_map);
$rrule['BYMONTH'] = strtolower($monthmap[$recurrence['month']]);
}
if ($recurrence['exclusion']) {
foreach ((array)$recurrence['exclusion'] as $excl)
$rrule['EXDATE'][] = strtotime($excl . date(' H:i:s', $rec['start-date'])); // use time of event start
$record['id'] = $record['uid'];
$record['calendar'] = $this->id;
// convert from DateTime to unix timestamp
if (is_a($record['start'], 'DateTime'))
$record['start'] = $record['start']->format('U');
if (is_a($record['end'], 'DateTime'))
$record['end'] = $record['end']->format('U');
// all-day events go from 12:00 - 13:00
if ($record['end'] <= $record['start'] && $record['allday'])
$record['end'] = $record['start'] + 3600;
if (!empty($record['_attachments'])) {
foreach ($record['_attachments'] as $name => $attachment) {
if ($attachment !== false) {
$attachment['name'] = $name;
$attachments[] = $attachment;
}
}
$record['attachments'] = $attachments;
}
$sensitivity_map = array_flip($this->sensitivity_map);
$status_map = array_flip($this->status_map);
$role_map = array_flip($this->role_map);
$record['sensitivity'] = intval($sensitivity_map[$record['sensitivity']]);
if (!empty($rec['_attachments'])) {
foreach ($rec['_attachments'] as $name => $attachment) {
// @TODO: 'type' and 'key' are the only supported (no 'size')
$attachments[] = array(
'id' => $attachment['key'],
'mimetype' => $attachment['type'],
'name' => $name,
);
}
}
if ($rec['organizer']) {
$attendees[] = array(
'role' => 'ORGANIZER',
'name' => $rec['organizer']['display-name'],
'email' => $rec['organizer']['smtp-address'],
'status' => 'ACCEPTED',
);
$_attendees .= $rec['organizer']['display-name'] . ' ' . $rec['organizer']['smtp-address'] . ' ';
}
foreach ((array)$rec['attendee'] as $attendee) {
$attendees[] = array(
'role' => $role_map[$attendee['role']],
'name' => $attendee['display-name'],
'email' => $attendee['smtp-address'],
'status' => $status_map[$attendee['status']],
'rsvp' => $attendee['request-response'],
);
$_attendees .= $rec['organizer']['display-name'] . ' ' . $rec['organizer']['smtp-address'] . ' ';
}
// Roundcube only supports one category assignment
$categories = explode(',', $rec['categories']);
return array(
'id' => $rec['uid'],
'uid' => $rec['uid'],
'title' => $rec['summary'],
'location' => $rec['location'],
'description' => $rec['body'],
'start' => $rec['start-date'],
'end' => $rec['end-date'],
'allday' => $allday,
'recurrence' => $rrule,
'alarms' => $alarm_value . $alarm_unit,
'_alarm' => intval($rec['alarm']),
'categories' => $categories[0],
'attachments' => $attachments,
'attendees' => $attendees,
'_attendees' => $_attendees,
'free_busy' => $rec['show-time-as'],
'priority' => is_numeric($rec['priority']) ? intval($rec['priority']) : (isset($this->priority_map[$rec['priority']]) ? $this->priority_map[$rec['priority']] : 0),
'sensitivity' => $sensitivity_map[$rec['sensitivity']],
'changed' => $rec['last-modification-date'],
'calendar' => $this->id,
);
if (is_array($record['categories']))
$record['categories'] = $record['categories'][0];
// remove internals
unset($record['_mailbox'], $record['_msguid'], $record['_formatobj'], $record['_attachments']);
return $record;
}
/**
* Convert the given event record into a data structure that can be passed to Kolab_Storage backend for saving
* (opposite of self::_to_rcube_event())
*/
private function _from_rcube_event($event)
private function _from_rcube_event($event, $old = array())
{
$priority_map = $this->priority_map;
$tz_offset = $this->cal->gmt_offset;
$object = array(
// kolab => roundcube
'uid' => $event['uid'],
'summary' => $event['title'],
'location' => $event['location'],
'body' => $event['description'],
'categories' => $event['categories'],
'start-date' => $event['start'],
'end-date' => $event['end'],
'sensitivity' =>$this->sensitivity_map[$event['sensitivity']],
'show-time-as' => $event['free_busy'],
'priority' => $event['priority'],
);
//handle alarms
if ($event['alarms']) {
//get the value
$alarmbase = explode(":", $event['alarms']);
//get number only
$avalue = preg_replace('/[^0-9]/', '', $alarmbase[0]);
if (preg_match("/H/",$alarmbase[0])) {
$object['alarm'] = $avalue*60;
} else if (preg_match("/D/",$alarmbase[0])) {
$object['alarm'] = $avalue*24*60;
} else {
$object['alarm'] = $avalue;
}
}
//recurr object/array
if (count($event['recurrence']) > 1) {
$ra = $event['recurrence'];
//Frequency abd interval
$object['recurrence']['cycle'] = strtolower($ra['FREQ']);
$object['recurrence']['interval'] = intval($ra['INTERVAL']);
//Range Type
if ($ra['UNTIL']) {
$object['recurrence']['range-type'] = 'date';
$object['recurrence']['range'] = $ra['UNTIL'];
}
if ($ra['COUNT']) {
$object['recurrence']['range-type'] = 'number';
$object['recurrence']['range'] = $ra['COUNT'];
}
//weekly
if ($ra['FREQ'] == 'WEEKLY') {
if ($ra['BYDAY']) {
foreach (split(",", $ra['BYDAY']) as $day)
$object['recurrence']['day'][] = $this->weekday_map[$day];
}
else {
// use weekday of start date if empty
$object['recurrence']['day'][] = strtolower(gmdate('l', $event['start'] + $tz_offset));
}
}
//monthly (temporary hack to follow current Horde logic)
if ($ra['FREQ'] == 'MONTHLY') {
if ($ra['BYDAY'] && preg_match('/(-?[1-4])([A-Z]+)/', $ra['BYDAY'], $m)) {
$object['recurrence']['daynumber'] = $m[1];
$object['recurrence']['day'] = array($this->weekday_map[$m[2]]);
$object['recurrence']['cycle'] = 'monthly';
$object['recurrence']['type'] = 'weekday';
}
else {
$object['recurrence']['daynumber'] = date('j', $event['start']);
$object['recurrence']['cycle'] = 'monthly';
$object['recurrence']['type'] = 'daynumber';
}
}
//yearly
if ($ra['FREQ'] == 'YEARLY') {
if (!$ra['BYMONTH'])
$ra['BYMONTH'] = gmdate('n', $event['start'] + $tz_offset);
$object['recurrence']['cycle'] = 'yearly';
$object['recurrence']['month'] = $this->month_map[intval($ra['BYMONTH'])];
if ($ra['BYDAY'] && preg_match('/(-?[1-4])([A-Z]+)/', $ra['BYDAY'], $m)) {
$object['recurrence']['type'] = 'weekday';
$object['recurrence']['daynumber'] = $m[1];
$object['recurrence']['day'] = array($this->weekday_map[$m[2]]);
}
else {
$object['recurrence']['type'] = 'monthday';
$object['recurrence']['daynumber'] = gmdate('j', $event['start'] + $tz_offset);
}
}
//exclusions
foreach ((array)$ra['EXDATE'] as $excl) {
$object['recurrence']['exclusion'][] = gmdate('Y-m-d', $excl + $tz_offset);
}
}
// whole day event
if ($event['allday']) {
$object['end-date'] += 12 * 3600; // end is at 13:00 => jump to the next day
$object['end-date'] += $tz_offset - date('Z'); // shift 00 times from user's timezone to server's timezone
$object['start-date'] += $tz_offset - date('Z'); // because Horde_Kolab_Format_Date::encodeDate() uses strftime()
// create timestamps at exactly 00:00. This is also needed for proper re-interpretation in _to_rcube_event() after updating an event
$object['start-date'] = mktime(0,0,0, date('n', $object['start-date']), date('j', $object['start-date']), date('Y', $object['start-date']));
$object['end-date'] = mktime(0,0,0, date('n', $object['end-date']), date('j', $object['end-date']), date('Y', $object['end-date']));
// sanity check: end date is same or smaller than start
if (date('Y-m-d', $object['end-date']) <= date('Y-m-d', $object['start-date']))
$object['end-date'] = mktime(13,0,0, date('n', $object['start-date']), date('j', $object['start-date']), date('Y', $object['start-date'])) + 86400;
$object['_is_all_day'] = 1;
}
$object = &$event;
// in Horde attachments are indexed by name
$object['_attachments'] = array();
if (!empty($event['attachments'])) {
if (is_array($event['attachments'])) {
$collisions = array();
foreach ($event['attachments'] as $idx => $attachment) {
// Roundcube ID has nothing to do with Horde ID, remove it
if ($attachment['content'])
unset($attachment['id']);
// Horde code assumes that there will be no more than
// one file with the same name: make filenames unique
$filename = $attachment['name'];
if ($collisions[$filename]++) {
$ext = preg_match('/(\.[a-z0-9]{1,6})$/i', $filename, $m) ? $m[1] : null;
$attachment['name'] = basename($filename, $ext) . '-' . $collisions[$filename] . $ext;
// flagged for deletion => set to false
if ($attachment['_deleted']) {
$object['_attachments'][$attachment['name']] = false;
}
else {
// Horde code assumes that there will be no more than
// one file with the same name: make filenames unique
$filename = $attachment['name'];
if ($collisions[$filename]++) {
$ext = preg_match('/(\.[a-z0-9]{1,6})$/i', $filename, $m) ? $m[1] : null;
$attachment['name'] = basename($filename, $ext) . '-' . $collisions[$filename] . $ext;
}
// set type parameter
if ($attachment['mimetype'])
$attachment['type'] = $attachment['mimetype'];
$object['_attachments'][$attachment['name']] = $attachment;
unset($event['attachments'][$idx]);
$object['_attachments'][$attachment['name']] = $attachment;
}
}
unset($event['attachments']);
}
// process event attendees
foreach ((array)$event['attendees'] as $attendee) {
$role = $attendee['role'];
if ($role == 'ORGANIZER') {
$object['organizer'] = array(
'display-name' => $attendee['name'],
'smtp-address' => $attendee['email'],
);
}
else {
$object['attendee'][] = array(
'display-name' => $attendee['name'],
'smtp-address' => $attendee['email'],
'status' => $this->status_map[$attendee['status']],
'role' => $this->role_map[$role],
'request-response' => $attendee['rsvp'],
);
}
// translate sensitivity property
$event['sensitivity'] = $this->sensitivity_map[$event['sensitivity']];
// set current user as ORGANIZER
$identity = $this->cal->rc->user->get_identity();
if (empty($event['attendees']) && $identity['email'])
$event['attendees'] = array(array('role' => 'ORGANIZER', 'name' => $identity['name'], 'email' => $identity['email']));
$event['_owner'] = $identity['email'];
// copy meta data (starting with _) from old object
foreach ((array)$old as $key => $val) {
if (!isset($event[$key]) && $key[0] == '_')
$event[$key] = $val;
}
return $object;
return $event;
}

View file

@ -7,7 +7,7 @@
* @author Thomas Bruederli <bruederli@kolabsys.com>
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@ -33,6 +33,7 @@ class kolab_driver extends calendar_driver
public $freebusy = true;
public $attachments = true;
public $undelete = true;
public $alarm_types = array('DISPLAY','EMAIL');
public $categoriesimmutable = true;
private $rc;
@ -64,7 +65,7 @@ class kolab_driver extends calendar_driver
return $this->calendars;
// get all folders that have "event" type
$folders = rcube_kolab::get_folders('event');
$folders = kolab_storage::get_folders('event');
$this->calendars = array();
if (PEAR::isError($folders)) {
@ -134,7 +135,7 @@ class kolab_driver extends calendar_driver
'readonly' => $cal->readonly,
'showalarms' => $cal->alarms,
'class_name' => $cal->get_namespace(),
'active' => rcube_kolab::is_subscribed($cal->get_realname()),
'active' => $cal->storage->is_subscribed(kolab_storage::SERVERSIDE_SUBSCRIPTION),
);
}
}
@ -160,11 +161,11 @@ class kolab_driver extends calendar_driver
}
// subscribe to new calendar by default
$storage = $this->rc->get_storage();
$storage->subscribe($folder);
$storage = kolab_storage::get_folder($folder);
$storage->subscribe($prop['active'], kolab_storage::SERVERSIDE_SUBSCRIPTION);
// create ID
$id = rcube_kolab::folder_id($folder);
$id = kolab_storage::folder_id($folder);
// save color in user prefs (temp. solution)
$prefs['kolab_calendars'] = $this->rc->config->get('kolab_calendars', array());
@ -197,7 +198,7 @@ class kolab_driver extends calendar_driver
}
// create ID
$id = rcube_kolab::folder_id($newfolder);
$id = kolab_storage::folder_id($newfolder);
// fallback to local prefs
$prefs['kolab_calendars'] = $this->rc->config->get('kolab_calendars', array());
@ -226,13 +227,9 @@ class kolab_driver extends calendar_driver
public function subscribe_calendar($prop)
{
if ($prop['id'] && ($cal = $this->calendars[$prop['id']])) {
$storage = $this->rc->get_storage();
if ($prop['active'])
return $storage->subscribe($cal->get_realname());
else
return $storage->unsubscribe($cal->get_realname());
return $cal->storage->subscribe($prop['active'], kolab_storage::SERVERSIDE_SUBSCRIPTION);
}
return false;
}
@ -302,24 +299,24 @@ class kolab_driver extends calendar_driver
// update the folder name
if (strlen($oldfolder)) {
if ($oldfolder != $folder) {
if (!($result = rcube_kolab::folder_rename($oldfolder, $folder)))
$this->last_error = rcube_kolab::$last_error;
if (!($result = kolab_storage::folder_rename($oldfolder, $folder)))
$this->last_error = kolab_storage::$last_error;
}
else
$result = true;
}
// create new folder
else {
if (!($result = rcube_kolab::folder_create($folder, 'event', false)))
$this->last_error = rcube_kolab::$last_error;
if (!($result = kolab_storage::folder_create($folder, 'event')))
$this->last_error = kolab_storage::$last_error;
}
// save color in METADATA
// TODO: also save 'showalarams' and other properties here
if ($result && $prop['color']) {
if (!($meta_saved = $storage->set_metadata($folder, array('/shared/vendor/kolab/color' => $prop['color'])))) // try in shared namespace
$meta_saved = $storage->set_metadata($folder, array('/private/vendor/kolab/color' => $prop['color'])); // try in private namespace
if (!($meta_saved = $storage->set_metadata(array(kolab_calendar::COLOR_KEY_SHARED => $prop['color'])))) // try in shared namespace
$meta_saved = $storage->set_metadata(array(kolab_calendar::COLOR_KEY_PRIVATE => $prop['color'])); // try in private namespace
if ($meta_saved)
unset($prop['color']); // unsetting will prevent fallback to local user prefs
}
@ -337,7 +334,7 @@ class kolab_driver extends calendar_driver
{
if ($prop['id'] && ($cal = $this->calendars[$prop['id']])) {
$folder = $cal->get_realname();
if (rcube_kolab::folder_delete($folder)) {
if (kolab_storage::folder_delete($folder)) {
// remove color in user prefs (temp. solution)
$prefs['kolab_calendars'] = $this->rc->config->get('kolab_calendars', array());
unset($prefs['kolab_calendars'][$prop['id']]);
@ -346,7 +343,7 @@ class kolab_driver extends calendar_driver
return true;
}
else
$this->last_error = rcube_kolab::$last_error;
$this->last_error = kolab_storage::$last_error;
}
return false;
@ -404,7 +401,6 @@ class kolab_driver extends calendar_driver
}
}
$GLOBALS['conf']['kolab']['no_triggering'] = true;
$success = $storage->insert_event($event);
if ($success)
@ -475,7 +471,6 @@ class kolab_driver extends calendar_driver
$master = $event;
$this->rc->session->remove('calendar_restore_event_data');
$GLOBALS['conf']['kolab']['no_triggering'] = true;
// read master if deleting a recurring event
if ($event['recurrence'] || $event['recurrence_id']) {
@ -566,7 +561,6 @@ class kolab_driver extends calendar_driver
return false;
$fromcalendar = $storage;
$storage->storage->synchronize();
}
}
else
@ -583,7 +577,7 @@ class kolab_driver extends calendar_driver
if (!empty($old['attachments'])) {
foreach ($old['attachments'] as $idx => $att) {
if ($att['id'] == $attachment) {
unset($old['attachments'][$idx]);
$old['attachments'][$idx]['_deleted'] = true;
}
}
}
@ -596,12 +590,12 @@ class kolab_driver extends calendar_driver
// skip entries without content (could be existing ones)
if (!$attachment['data'] && !$attachment['path'])
continue;
// we'll read file contacts into memory, Horde/Kolab classes does the same
// So we cannot save memory, rcube_imap class can do this better
$attachments[] = array(
'name' => $attachment['name'],
'type' => $attachment['mimetype'],
'content' => $attachment['data'] ? $attachment['data'] : file_get_contents($attachment['path']),
'mimetype' => $attachment['mimetype'],
'content' => $attachment['data'],
'path' => $attachment['path'],
);
}
}
@ -618,8 +612,6 @@ class kolab_driver extends calendar_driver
if ($old['recurrence']['EXDATE'])
$event['recurrence']['EXDATE'] = $old['recurrence']['EXDATE'];
$GLOBALS['conf']['kolab']['no_triggering'] = true;
switch ($savemode) {
case 'new':
// save submitted data as new (non-recurring) event
@ -629,7 +621,7 @@ class kolab_driver extends calendar_driver
// copy attachment data to new event
foreach ((array)$event['attachments'] as $idx => $attachment) {
if (!$attachment['data'])
$attachment['data'] = $fromcalendar->get_attachment_body($attachment['id']);
$attachment['data'] = $fromcalendar->get_attachment_body($attachment['id'], $event);
}
$success = $storage->insert_event($event);
@ -773,17 +765,19 @@ class kolab_driver extends calendar_driver
$time = $slot + $interval;
$events = array();
$query = array(array('tags', 'LIKE', '% x-has-alarms %'));
foreach ($this->calendars as $cid => $calendar) {
// skip calendars with alarms disabled
if (!$calendar->alarms || ($calendars && !in_array($cid, $calendars)))
continue;
foreach ($calendar->list_events($time, $time + 86400 * 365) as $e) {
foreach ($calendar->list_events($time, $time + 86400 * 365, null, 1, $query) as $e) {
// add to list if alarm is set
if ($e['_alarm'] && ($notifyat = $e['start'] - $e['_alarm'] * 60) <= $time) {
$alarm = calendar::get_next_alarm($e);
if ($alarm && $alarm['time'] && $alarm['time'] <= $time && $alarm['action'] == 'DISPLAY') {
$id = $e['id'];
$events[$id] = $e;
$events[$id]['notifyat'] = $notifyat;
$events[$id]['notifyat'] = $alarm['time'];
}
}
}
@ -792,11 +786,13 @@ class kolab_driver extends calendar_driver
if (!empty($events)) {
$event_ids = array_map(array($this->rc->db, 'quote'), array_keys($events));
$result = $this->rc->db->query(sprintf(
"SELECT * FROM kolab_alarms
WHERE event_id IN (%s)",
join(',', $event_ids),
$this->rc->db->now()
));
"SELECT * FROM kolab_alarms
WHERE event_id IN (%s) AND user_id=?",
join(',', $event_ids),
$this->rc->db->now()
),
$this->rc->user->ID
);
while ($result && ($e = $this->rc->db->fetch_assoc($result))) {
$dbdata[$e['event_id']] = $e;
@ -825,15 +821,24 @@ class kolab_driver extends calendar_driver
*/
public function dismiss_alarm($event_id, $snooze = 0)
{
// delete old alarm entry
$this->rc->db->query(
"DELETE FROM kolab_alarms
WHERE event_id=? AND user_id=?",
$event_id,
$this->rc->user->ID
);
// set new notifyat time or unset if not snoozed
$notifyat = $snooze > 0 ? date('Y-m-d H:i:s', time() + $snooze) : null;
$query = $this->rc->db->query(
"REPLACE INTO kolab_alarms
(event_id, dismissed, notifyat)
VALUES(?, ?, ?)",
"INSERT INTO kolab_alarms
(event_id, user_id, dismissed, notifyat)
VALUES(?, ?, ?, ?)",
$event_id,
$snooze > 0 ? 0 : 1,
$this->rc->user->ID,
$snooze > 0 ? 0 : 1,
$notifyat
);
@ -876,13 +881,14 @@ class kolab_driver extends calendar_driver
/**
* Get attachment body
* @see calendar_driver::get_attachment_body()
*/
public function get_attachment_body($id, $event)
{
if (!($storage = $this->calendars[$event['calendar']]))
if (!($cal = $this->calendars[$event['calendar']]))
return false;
return $storage->get_attachment_body($id);
return $cal->storage->get_attachment($event['id'], $id);
}
/**
@ -986,12 +992,11 @@ class kolab_driver extends calendar_driver
ignore_user_abort(true);
$cal = get_input_value('source', RCUBE_INPUT_GPC);
if (!($storage = $this->calendars[$cal]))
if (!($cal = $this->calendars[$cal]))
return false;
// trigger updates on folder
$folder = $storage->get_folder();
$trigger = $folder->trigger();
$trigger = $cal->storage->trigger();
if (is_object($trigger) && is_a($trigger, 'PEAR_Error')) {
raise_error(array(
'code' => 900, 'type' => 'php',
@ -1048,7 +1053,7 @@ class kolab_driver extends calendar_driver
// Disable folder name input
if (!empty($options) && ($options['norename'] || $options['protected'])) {
$input_name = new html_hiddenfield(array('name' => 'name', 'id' => 'calendar-name'));
$formfields['name']['value'] = Q(str_replace($delimiter, ' &raquo; ', rcube_kolab::object_name($folder)))
$formfields['name']['value'] = Q(str_replace($delimiter, ' &raquo; ', kolab_storage::object_name($folder)))
. $input_name->show($folder);
}
@ -1065,7 +1070,7 @@ class kolab_driver extends calendar_driver
$hidden_fields[] = array('name' => 'parent', 'value' => $path_imap);
}
else {
$select = rcube_kolab::folder_selector('event', array('name' => 'parent'), $folder);
$select = kolab_storage::folder_selector('event', array('name' => 'parent'), $folder);
$form['props']['fieldsets']['location']['content']['path'] = array(
'label' => $this->cal->gettext('parentcalendar'),
'value' => $select->show(strlen($folder) ? $path_imap : ''),

View file

@ -0,0 +1,774 @@
<?php
/**
* This is a copy of the Horde/Date.php class from the Horde framework
*/
define('HORDE_DATE_SUNDAY', 0);
define('HORDE_DATE_MONDAY', 1);
define('HORDE_DATE_TUESDAY', 2);
define('HORDE_DATE_WEDNESDAY', 3);
define('HORDE_DATE_THURSDAY', 4);
define('HORDE_DATE_FRIDAY', 5);
define('HORDE_DATE_SATURDAY', 6);
define('HORDE_DATE_MASK_SUNDAY', 1);
define('HORDE_DATE_MASK_MONDAY', 2);
define('HORDE_DATE_MASK_TUESDAY', 4);
define('HORDE_DATE_MASK_WEDNESDAY', 8);
define('HORDE_DATE_MASK_THURSDAY', 16);
define('HORDE_DATE_MASK_FRIDAY', 32);
define('HORDE_DATE_MASK_SATURDAY', 64);
define('HORDE_DATE_MASK_WEEKDAYS', 62);
define('HORDE_DATE_MASK_WEEKEND', 65);
define('HORDE_DATE_MASK_ALLDAYS', 127);
define('HORDE_DATE_MASK_SECOND', 1);
define('HORDE_DATE_MASK_MINUTE', 2);
define('HORDE_DATE_MASK_HOUR', 4);
define('HORDE_DATE_MASK_DAY', 8);
define('HORDE_DATE_MASK_MONTH', 16);
define('HORDE_DATE_MASK_YEAR', 32);
define('HORDE_DATE_MASK_ALLPARTS', 63);
/**
* Horde Date wrapper/logic class, including some calculation
* functions.
*
* $Horde: framework/Date/Date.php,v 1.8.10.18 2008/09/17 08:46:04 jan Exp $
*
* @package Horde_Date
*/
class Horde_Date {
/**
* Year
*
* @var integer
*/
var $year;
/**
* Month
*
* @var integer
*/
var $month;
/**
* Day
*
* @var integer
*/
var $mday;
/**
* Hour
*
* @var integer
*/
var $hour = 0;
/**
* Minute
*
* @var integer
*/
var $min = 0;
/**
* Second
*
* @var integer
*/
var $sec = 0;
/**
* Internally supported strftime() specifiers.
*
* @var string
*/
var $_supportedSpecs = '%CdDeHImMnRStTyY';
/**
* Build a new date object. If $date contains date parts, use them to
* initialize the object.
*
* Recognized formats:
* - arrays with keys 'year', 'month', 'mday', 'day' (since Horde 3.2),
* 'hour', 'min', 'minute' (since Horde 3.2), 'sec'
* - objects with properties 'year', 'month', 'mday', 'hour', 'min', 'sec'
* - yyyy-mm-dd hh:mm:ss (since Horde 3.1)
* - yyyymmddhhmmss (since Horde 3.1)
* - yyyymmddThhmmssZ (since Horde 3.1.4)
* - unix timestamps
*/
function Horde_Date($date = null)
{
if (function_exists('nl_langinfo')) {
$this->_supportedSpecs .= 'bBpxX';
}
if (is_array($date) || is_object($date)) {
foreach ($date as $key => $val) {
if (in_array($key, array('year', 'month', 'mday', 'hour', 'min', 'sec'))) {
$this->$key = (int)$val;
}
}
// If $date['day'] is present and numeric we may have been passed
// a Horde_Form_datetime array.
if (is_array($date) && isset($date['day']) &&
is_numeric($date['day'])) {
$this->mday = (int)$date['day'];
}
// 'minute' key also from Horde_Form_datetime
if (is_array($date) && isset($date['minute'])) {
$this->min = $date['minute'];
}
} elseif (!is_null($date)) {
// Match YYYY-MM-DD HH:MM:SS, YYYYMMDDHHMMSS and YYYYMMDD'T'HHMMSS'Z'.
if (preg_match('/(\d{4})-?(\d{2})-?(\d{2})T? ?(\d{2}):?(\d{2}):?(\d{2})Z?/', $date, $parts)) {
$this->year = (int)$parts[1];
$this->month = (int)$parts[2];
$this->mday = (int)$parts[3];
$this->hour = (int)$parts[4];
$this->min = (int)$parts[5];
$this->sec = (int)$parts[6];
} else {
// Try as a timestamp.
$parts = @getdate($date);
if ($parts) {
$this->year = $parts['year'];
$this->month = $parts['mon'];
$this->mday = $parts['mday'];
$this->hour = $parts['hours'];
$this->min = $parts['minutes'];
$this->sec = $parts['seconds'];
}
}
}
}
/**
* @static
*/
function isLeapYear($year)
{
if (strlen($year) != 4 || preg_match('/\D/', $year)) {
return false;
}
return (($year % 4 == 0 && $year % 100 != 0) || $year % 400 == 0);
}
/**
* Returns the day of the year (1-366) that corresponds to the
* first day of the given week.
*
* TODO: with PHP 5.1+, see http://derickrethans.nl/calculating_start_and_end_dates_of_a_week.php
*
* @param integer $week The week of the year to find the first day of.
* @param integer $year The year to calculate for.
*
* @return integer The day of the year of the first day of the given week.
*/
function firstDayOfWeek($week, $year)
{
$jan1 = new Horde_Date(array('year' => $year, 'month' => 1, 'mday' => 1));
$start = $jan1->dayOfWeek();
if ($start > HORDE_DATE_THURSDAY) {
$start -= 7;
}
return (($week * 7) - (7 + $start)) + 1;
}
/**
* @static
*/
function daysInMonth($month, $year)
{
if ($month == 2) {
if (Horde_Date::isLeapYear($year)) {
return 29;
} else {
return 28;
}
} elseif ($month == 4 || $month == 6 || $month == 9 || $month == 11) {
return 30;
} else {
return 31;
}
}
/**
* Return the day of the week (0 = Sunday, 6 = Saturday) of this
* object's date.
*
* @return integer The day of the week.
*/
function dayOfWeek()
{
if ($this->month > 2) {
$month = $this->month - 2;
$year = $this->year;
} else {
$month = $this->month + 10;
$year = $this->year - 1;
}
$day = (floor((13 * $month - 1) / 5) +
$this->mday + ($year % 100) +
floor(($year % 100) / 4) +
floor(($year / 100) / 4) - 2 *
floor($year / 100) + 77);
return (int)($day - 7 * floor($day / 7));
}
/**
* Returns the day number of the year (1 to 365/366).
*
* @return integer The day of the year.
*/
function dayOfYear()
{
$monthTotals = array(0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334);
$dayOfYear = $this->mday + $monthTotals[$this->month - 1];
if (Horde_Date::isLeapYear($this->year) && $this->month > 2) {
++$dayOfYear;
}
return $dayOfYear;
}
/**
* Returns the week of the month.
*
* @since Horde 3.2
*
* @return integer The week number.
*/
function weekOfMonth()
{
return ceil($this->mday / 7);
}
/**
* Returns the week of the year, first Monday is first day of first week.
*
* @return integer The week number.
*/
function weekOfYear()
{
return $this->format('W');
}
/**
* Return the number of weeks in the given year (52 or 53).
*
* @static
*
* @param integer $year The year to count the number of weeks in.
*
* @return integer $numWeeks The number of weeks in $year.
*/
function weeksInYear($year)
{
// Find the last Thursday of the year.
$day = 31;
$date = new Horde_Date(array('year' => $year, 'month' => 12, 'mday' => $day, 'hour' => 0, 'min' => 0, 'sec' => 0));
while ($date->dayOfWeek() != HORDE_DATE_THURSDAY) {
--$date->mday;
}
return $date->weekOfYear();
}
/**
* Set the date of this object to the $nth weekday of $weekday.
*
* @param integer $weekday The day of the week (0 = Sunday, etc).
* @param integer $nth The $nth $weekday to set to (defaults to 1).
*/
function setNthWeekday($weekday, $nth = 1)
{
if ($weekday < HORDE_DATE_SUNDAY || $weekday > HORDE_DATE_SATURDAY) {
return false;
}
$this->mday = 1;
$first = $this->dayOfWeek();
if ($weekday < $first) {
$this->mday = 8 + $weekday - $first;
} else {
$this->mday = $weekday - $first + 1;
}
$this->mday += 7 * $nth - 7;
$this->correct();
return true;
}
function dump($prefix = '')
{
echo ($prefix ? $prefix . ': ' : '') . $this->year . '-' . $this->month . '-' . $this->mday . "<br />\n";
}
/**
* Is the date currently represented by this object a valid date?
*
* @return boolean Validity, counting leap years, etc.
*/
function isValid()
{
if ($this->year < 0 || $this->year > 9999) {
return false;
}
return checkdate($this->month, $this->mday, $this->year);
}
/**
* Correct any over- or underflows in any of the date's members.
*
* @param integer $mask We may not want to correct some overflows.
*/
function correct($mask = HORDE_DATE_MASK_ALLPARTS)
{
if ($mask & HORDE_DATE_MASK_SECOND) {
while ($this->sec < 0) {
--$this->min;
$this->sec += 60;
}
while ($this->sec > 59) {
++$this->min;
$this->sec -= 60;
}
}
if ($mask & HORDE_DATE_MASK_MINUTE) {
while ($this->min < 0) {
--$this->hour;
$this->min += 60;
}
while ($this->min > 59) {
++$this->hour;
$this->min -= 60;
}
}
if ($mask & HORDE_DATE_MASK_HOUR) {
while ($this->hour < 0) {
--$this->mday;
$this->hour += 24;
}
while ($this->hour > 23) {
++$this->mday;
$this->hour -= 24;
}
}
if ($mask & HORDE_DATE_MASK_MONTH) {
while ($this->month > 12) {
++$this->year;
$this->month -= 12;
}
while ($this->month < 1) {
--$this->year;
$this->month += 12;
}
}
if ($mask & HORDE_DATE_MASK_DAY) {
while ($this->mday > Horde_Date::daysInMonth($this->month, $this->year)) {
$this->mday -= Horde_Date::daysInMonth($this->month, $this->year);
++$this->month;
$this->correct(HORDE_DATE_MASK_MONTH);
}
while ($this->mday < 1) {
--$this->month;
$this->correct(HORDE_DATE_MASK_MONTH);
$this->mday += Horde_Date::daysInMonth($this->month, $this->year);
}
}
}
/**
* Compare this date to another date object to see which one is
* greater (later). Assumes that the dates are in the same
* timezone.
*
* @param mixed $date The date to compare to.
*
* @return integer == 0 if the dates are equal
* >= 1 if this date is greater (later)
* <= -1 if the other date is greater (later)
*/
function compareDate($date)
{
if (!is_object($date) || !is_a($date, 'Horde_Date')) {
$date = new Horde_Date($date);
}
if ($this->year != $date->year) {
return $this->year - $date->year;
}
if ($this->month != $date->month) {
return $this->month - $date->month;
}
return $this->mday - $date->mday;
}
/**
* Compare this to another date object by time, to see which one
* is greater (later). Assumes that the dates are in the same
* timezone.
*
* @param mixed $date The date to compare to.
*
* @return integer == 0 if the dates are equal
* >= 1 if this date is greater (later)
* <= -1 if the other date is greater (later)
*/
function compareTime($date)
{
if (!is_object($date) || !is_a($date, 'Horde_Date')) {
$date = new Horde_Date($date);
}
if ($this->hour != $date->hour) {
return $this->hour - $date->hour;
}
if ($this->min != $date->min) {
return $this->min - $date->min;
}
return $this->sec - $date->sec;
}
/**
* Compare this to another date object, including times, to see
* which one is greater (later). Assumes that the dates are in the
* same timezone.
*
* @param mixed $date The date to compare to.
*
* @return integer == 0 if the dates are equal
* >= 1 if this date is greater (later)
* <= -1 if the other date is greater (later)
*/
function compareDateTime($date)
{
if (!is_object($date) || !is_a($date, 'Horde_Date')) {
$date = new Horde_Date($date);
}
if ($diff = $this->compareDate($date)) {
return $diff;
}
return $this->compareTime($date);
}
/**
* Get the time offset for local time zone.
*
* @param boolean $colon Place a colon between hours and minutes?
*
* @return string Timezone offset as a string in the format +HH:MM.
*/
function tzOffset($colon = true)
{
$secs = $this->format('Z');
if ($secs < 0) {
$sign = '-';
$secs = -$secs;
} else {
$sign = '+';
}
$colon = $colon ? ':' : '';
$mins = intval(($secs + 30) / 60);
return sprintf('%s%02d%s%02d',
$sign, $mins / 60, $colon, $mins % 60);
}
/**
* Return the unix timestamp representation of this date.
*
* @return integer A unix timestamp.
*/
function timestamp()
{
if (class_exists('DateTime')) {
return $this->format('U');
} else {
return Horde_Date::_mktime($this->hour, $this->min, $this->sec, $this->month, $this->mday, $this->year);
}
}
/**
* Return the unix timestamp representation of this date, 12:00am.
*
* @return integer A unix timestamp.
*/
function datestamp()
{
if (class_exists('DateTime')) {
$dt = new DateTime();
$dt->setDate($this->year, $this->month, $this->mday);
$dt->setTime(0, 0, 0);
return $dt->format('U');
} else {
return Horde_Date::_mktime(0, 0, 0, $this->month, $this->mday, $this->year);
}
}
/**
* Format time using the specifiers available in date() or in the DateTime
* class' format() method.
*
* @since Horde 3.3
*
* @param string $format
*
* @return string Formatted time.
*/
function format($format)
{
if (class_exists('DateTime')) {
$dt = new DateTime();
$dt->setDate($this->year, $this->month, $this->mday);
$dt->setTime($this->hour, $this->min, $this->sec);
return $dt->format($format);
} else {
return date($format, $this->timestamp());
}
}
/**
* Format time in ISO-8601 format. Works correctly since Horde 3.2.
*
* @return string Date and time in ISO-8601 format.
*/
function iso8601DateTime()
{
return $this->rfc3339DateTime() . $this->tzOffset();
}
/**
* Format time in RFC 2822 format.
*
* @return string Date and time in RFC 2822 format.
*/
function rfc2822DateTime()
{
return $this->format('D, j M Y H:i:s') . ' ' . $this->tzOffset(false);
}
/**
* Format time in RFC 3339 format.
*
* @since Horde 3.1
*
* @return string Date and time in RFC 3339 format. The seconds part has
* been added with Horde 3.2.
*/
function rfc3339DateTime()
{
return $this->format('Y-m-d\TH:i:s');
}
/**
* Format time to standard 'ctime' format.
*
* @return string Date and time.
*/
function cTime()
{
return $this->format('D M j H:i:s Y');
}
/**
* Format date and time using strftime() format.
*
* @since Horde 3.1
*
* @return string strftime() formatted date and time.
*/
function strftime($format)
{
if (preg_match('/%[^' . $this->_supportedSpecs . ']/', $format)) {
return strftime($format, $this->timestamp());
} else {
return $this->_strftime($format);
}
}
/**
* Format date and time using a limited set of the strftime() format.
*
* @return string strftime() formatted date and time.
*/
function _strftime($format)
{
if (preg_match('/%[bBpxX]/', $format)) {
require_once 'Horde/NLS.php';
}
return preg_replace(
array('/%b/e',
'/%B/e',
'/%C/e',
'/%d/e',
'/%D/e',
'/%e/e',
'/%H/e',
'/%I/e',
'/%m/e',
'/%M/e',
'/%n/',
'/%p/e',
'/%R/e',
'/%S/e',
'/%t/',
'/%T/e',
'/%x/e',
'/%X/e',
'/%y/e',
'/%Y/',
'/%%/'),
array('$this->_strftime(NLS::getLangInfo(constant(\'ABMON_\' . (int)$this->month)))',
'$this->_strftime(NLS::getLangInfo(constant(\'MON_\' . (int)$this->month)))',
'(int)($this->year / 100)',
'sprintf(\'%02d\', $this->mday)',
'$this->_strftime(\'%m/%d/%y\')',
'sprintf(\'%2d\', $this->mday)',
'sprintf(\'%02d\', $this->hour)',
'sprintf(\'%02d\', $this->hour == 0 ? 12 : ($this->hour > 12 ? $this->hour - 12 : $this->hour))',
'sprintf(\'%02d\', $this->month)',
'sprintf(\'%02d\', $this->min)',
"\n",
'$this->_strftime(NLS::getLangInfo($this->hour < 12 ? AM_STR : PM_STR))',
'$this->_strftime(\'%H:%M\')',
'sprintf(\'%02d\', $this->sec)',
"\t",
'$this->_strftime(\'%H:%M:%S\')',
'$this->_strftime(NLS::getLangInfo(D_FMT))',
'$this->_strftime(NLS::getLangInfo(T_FMT))',
'substr(sprintf(\'%04d\', $this->year), -2)',
(int)$this->year,
'%'),
$format);
}
/**
* mktime() implementation that supports dates outside of 1970-2038,
* from http://phplens.com/phpeverywhere/adodb_date_library.
*
* @TODO remove in Horde 4
*
* This does NOT work with pre-1970 daylight saving times.
*
* @static
*/
function _mktime($hr, $min, $sec, $mon = false, $day = false,
$year = false, $is_dst = false, $is_gmt = false)
{
if ($mon === false) {
return $is_gmt
? @gmmktime($hr, $min, $sec)
: @mktime($hr, $min, $sec);
}
if ($year > 1901 && $year < 2038 &&
($year >= 1970 || version_compare(PHP_VERSION, '5.0.0', '>='))) {
return $is_gmt
? @gmmktime($hr, $min, $sec, $mon, $day, $year)
: @mktime($hr, $min, $sec, $mon, $day, $year);
}
$gmt_different = $is_gmt
? 0
: (mktime(0, 0, 0, 1, 2, 1970, 0) - gmmktime(0, 0, 0, 1, 2, 1970, 0));
$mon = intval($mon);
$day = intval($day);
$year = intval($year);
if ($mon > 12) {
$y = floor($mon / 12);
$year += $y;
$mon -= $y * 12;
} elseif ($mon < 1) {
$y = ceil((1 - $mon) / 12);
$year -= $y;
$mon += $y * 12;
}
$_day_power = 86400;
$_hour_power = 3600;
$_min_power = 60;
$_month_table_normal = array('', 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
$_month_table_leaf = array('', 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
$_total_date = 0;
if ($year >= 1970) {
for ($a = 1970; $a <= $year; $a++) {
$leaf = Horde_Date::isLeapYear($a);
if ($leaf == true) {
$loop_table = $_month_table_leaf;
$_add_date = 366;
} else {
$loop_table = $_month_table_normal;
$_add_date = 365;
}
if ($a < $year) {
$_total_date += $_add_date;
} else {
for ($b = 1; $b < $mon; $b++) {
$_total_date += $loop_table[$b];
}
}
}
return ($_total_date + $day - 1) * $_day_power + $hr * $_hour_power + $min * $_min_power + $sec + $gmt_different;
}
for ($a = 1969 ; $a >= $year; $a--) {
$leaf = Horde_Date::isLeapYear($a);
if ($leaf == true) {
$loop_table = $_month_table_leaf;
$_add_date = 366;
} else {
$loop_table = $_month_table_normal;
$_add_date = 365;
}
if ($a > $year) {
$_total_date += $_add_date;
} else {
for ($b = 12; $b > $mon; $b--) {
$_total_date += $loop_table[$b];
}
}
}
$_total_date += $loop_table[$mon] - $day;
$_day_time = $hr * $_hour_power + $min * $_min_power + $sec;
$_day_time = $_day_power - $_day_time;
$ret = -($_total_date * $_day_power + $_day_time - $gmt_different);
if ($ret < -12220185600) {
// If earlier than 5 Oct 1582 - gregorian correction.
return $ret + 10 * 86400;
} elseif ($ret < -12219321600) {
// If in limbo, reset to 15 Oct 1582.
return -12219321600;
} else {
return $ret;
}
}
}

View file

@ -2,779 +2,10 @@
/**
* This is a concatenated copy of the following files:
* Horde/Date.php, PEAR/Date/Calc.php, Horde/Date/Recurrence.php
* PEAR/Date/Calc.php, Horde/Date/Recurrence.php
*/
define('HORDE_DATE_SUNDAY', 0);
define('HORDE_DATE_MONDAY', 1);
define('HORDE_DATE_TUESDAY', 2);
define('HORDE_DATE_WEDNESDAY', 3);
define('HORDE_DATE_THURSDAY', 4);
define('HORDE_DATE_FRIDAY', 5);
define('HORDE_DATE_SATURDAY', 6);
define('HORDE_DATE_MASK_SUNDAY', 1);
define('HORDE_DATE_MASK_MONDAY', 2);
define('HORDE_DATE_MASK_TUESDAY', 4);
define('HORDE_DATE_MASK_WEDNESDAY', 8);
define('HORDE_DATE_MASK_THURSDAY', 16);
define('HORDE_DATE_MASK_FRIDAY', 32);
define('HORDE_DATE_MASK_SATURDAY', 64);
define('HORDE_DATE_MASK_WEEKDAYS', 62);
define('HORDE_DATE_MASK_WEEKEND', 65);
define('HORDE_DATE_MASK_ALLDAYS', 127);
define('HORDE_DATE_MASK_SECOND', 1);
define('HORDE_DATE_MASK_MINUTE', 2);
define('HORDE_DATE_MASK_HOUR', 4);
define('HORDE_DATE_MASK_DAY', 8);
define('HORDE_DATE_MASK_MONTH', 16);
define('HORDE_DATE_MASK_YEAR', 32);
define('HORDE_DATE_MASK_ALLPARTS', 63);
/**
* Horde Date wrapper/logic class, including some calculation
* functions.
*
* $Horde: framework/Date/Date.php,v 1.8.10.18 2008/09/17 08:46:04 jan Exp $
*
* @package Horde_Date
*/
class Horde_Date {
/**
* Year
*
* @var integer
*/
var $year;
/**
* Month
*
* @var integer
*/
var $month;
/**
* Day
*
* @var integer
*/
var $mday;
/**
* Hour
*
* @var integer
*/
var $hour = 0;
/**
* Minute
*
* @var integer
*/
var $min = 0;
/**
* Second
*
* @var integer
*/
var $sec = 0;
/**
* Internally supported strftime() specifiers.
*
* @var string
*/
var $_supportedSpecs = '%CdDeHImMnRStTyY';
/**
* Build a new date object. If $date contains date parts, use them to
* initialize the object.
*
* Recognized formats:
* - arrays with keys 'year', 'month', 'mday', 'day' (since Horde 3.2),
* 'hour', 'min', 'minute' (since Horde 3.2), 'sec'
* - objects with properties 'year', 'month', 'mday', 'hour', 'min', 'sec'
* - yyyy-mm-dd hh:mm:ss (since Horde 3.1)
* - yyyymmddhhmmss (since Horde 3.1)
* - yyyymmddThhmmssZ (since Horde 3.1.4)
* - unix timestamps
*/
function Horde_Date($date = null)
{
if (function_exists('nl_langinfo')) {
$this->_supportedSpecs .= 'bBpxX';
}
if (is_array($date) || is_object($date)) {
foreach ($date as $key => $val) {
if (in_array($key, array('year', 'month', 'mday', 'hour', 'min', 'sec'))) {
$this->$key = (int)$val;
}
}
// If $date['day'] is present and numeric we may have been passed
// a Horde_Form_datetime array.
if (is_array($date) && isset($date['day']) &&
is_numeric($date['day'])) {
$this->mday = (int)$date['day'];
}
// 'minute' key also from Horde_Form_datetime
if (is_array($date) && isset($date['minute'])) {
$this->min = $date['minute'];
}
} elseif (!is_null($date)) {
// Match YYYY-MM-DD HH:MM:SS, YYYYMMDDHHMMSS and YYYYMMDD'T'HHMMSS'Z'.
if (preg_match('/(\d{4})-?(\d{2})-?(\d{2})T? ?(\d{2}):?(\d{2}):?(\d{2})Z?/', $date, $parts)) {
$this->year = (int)$parts[1];
$this->month = (int)$parts[2];
$this->mday = (int)$parts[3];
$this->hour = (int)$parts[4];
$this->min = (int)$parts[5];
$this->sec = (int)$parts[6];
} else {
// Try as a timestamp.
$parts = @getdate($date);
if ($parts) {
$this->year = $parts['year'];
$this->month = $parts['mon'];
$this->mday = $parts['mday'];
$this->hour = $parts['hours'];
$this->min = $parts['minutes'];
$this->sec = $parts['seconds'];
}
}
}
}
/**
* @static
*/
function isLeapYear($year)
{
if (strlen($year) != 4 || preg_match('/\D/', $year)) {
return false;
}
return (($year % 4 == 0 && $year % 100 != 0) || $year % 400 == 0);
}
/**
* Returns the day of the year (1-366) that corresponds to the
* first day of the given week.
*
* TODO: with PHP 5.1+, see http://derickrethans.nl/calculating_start_and_end_dates_of_a_week.php
*
* @param integer $week The week of the year to find the first day of.
* @param integer $year The year to calculate for.
*
* @return integer The day of the year of the first day of the given week.
*/
function firstDayOfWeek($week, $year)
{
$jan1 = new Horde_Date(array('year' => $year, 'month' => 1, 'mday' => 1));
$start = $jan1->dayOfWeek();
if ($start > HORDE_DATE_THURSDAY) {
$start -= 7;
}
return (($week * 7) - (7 + $start)) + 1;
}
/**
* @static
*/
function daysInMonth($month, $year)
{
if ($month == 2) {
if (Horde_Date::isLeapYear($year)) {
return 29;
} else {
return 28;
}
} elseif ($month == 4 || $month == 6 || $month == 9 || $month == 11) {
return 30;
} else {
return 31;
}
}
/**
* Return the day of the week (0 = Sunday, 6 = Saturday) of this
* object's date.
*
* @return integer The day of the week.
*/
function dayOfWeek()
{
if ($this->month > 2) {
$month = $this->month - 2;
$year = $this->year;
} else {
$month = $this->month + 10;
$year = $this->year - 1;
}
$day = (floor((13 * $month - 1) / 5) +
$this->mday + ($year % 100) +
floor(($year % 100) / 4) +
floor(($year / 100) / 4) - 2 *
floor($year / 100) + 77);
return (int)($day - 7 * floor($day / 7));
}
/**
* Returns the day number of the year (1 to 365/366).
*
* @return integer The day of the year.
*/
function dayOfYear()
{
$monthTotals = array(0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334);
$dayOfYear = $this->mday + $monthTotals[$this->month - 1];
if (Horde_Date::isLeapYear($this->year) && $this->month > 2) {
++$dayOfYear;
}
return $dayOfYear;
}
/**
* Returns the week of the month.
*
* @since Horde 3.2
*
* @return integer The week number.
*/
function weekOfMonth()
{
return ceil($this->mday / 7);
}
/**
* Returns the week of the year, first Monday is first day of first week.
*
* @return integer The week number.
*/
function weekOfYear()
{
return $this->format('W');
}
/**
* Return the number of weeks in the given year (52 or 53).
*
* @static
*
* @param integer $year The year to count the number of weeks in.
*
* @return integer $numWeeks The number of weeks in $year.
*/
function weeksInYear($year)
{
// Find the last Thursday of the year.
$day = 31;
$date = new Horde_Date(array('year' => $year, 'month' => 12, 'mday' => $day, 'hour' => 0, 'min' => 0, 'sec' => 0));
while ($date->dayOfWeek() != HORDE_DATE_THURSDAY) {
--$date->mday;
}
return $date->weekOfYear();
}
/**
* Set the date of this object to the $nth weekday of $weekday.
*
* @param integer $weekday The day of the week (0 = Sunday, etc).
* @param integer $nth The $nth $weekday to set to (defaults to 1).
*/
function setNthWeekday($weekday, $nth = 1)
{
if ($weekday < HORDE_DATE_SUNDAY || $weekday > HORDE_DATE_SATURDAY) {
return false;
}
$this->mday = 1;
$first = $this->dayOfWeek();
if ($weekday < $first) {
$this->mday = 8 + $weekday - $first;
} else {
$this->mday = $weekday - $first + 1;
}
$this->mday += 7 * $nth - 7;
$this->correct();
return true;
}
function dump($prefix = '')
{
echo ($prefix ? $prefix . ': ' : '') . $this->year . '-' . $this->month . '-' . $this->mday . "<br />\n";
}
/**
* Is the date currently represented by this object a valid date?
*
* @return boolean Validity, counting leap years, etc.
*/
function isValid()
{
if ($this->year < 0 || $this->year > 9999) {
return false;
}
return checkdate($this->month, $this->mday, $this->year);
}
/**
* Correct any over- or underflows in any of the date's members.
*
* @param integer $mask We may not want to correct some overflows.
*/
function correct($mask = HORDE_DATE_MASK_ALLPARTS)
{
if ($mask & HORDE_DATE_MASK_SECOND) {
while ($this->sec < 0) {
--$this->min;
$this->sec += 60;
}
while ($this->sec > 59) {
++$this->min;
$this->sec -= 60;
}
}
if ($mask & HORDE_DATE_MASK_MINUTE) {
while ($this->min < 0) {
--$this->hour;
$this->min += 60;
}
while ($this->min > 59) {
++$this->hour;
$this->min -= 60;
}
}
if ($mask & HORDE_DATE_MASK_HOUR) {
while ($this->hour < 0) {
--$this->mday;
$this->hour += 24;
}
while ($this->hour > 23) {
++$this->mday;
$this->hour -= 24;
}
}
if ($mask & HORDE_DATE_MASK_MONTH) {
while ($this->month > 12) {
++$this->year;
$this->month -= 12;
}
while ($this->month < 1) {
--$this->year;
$this->month += 12;
}
}
if ($mask & HORDE_DATE_MASK_DAY) {
while ($this->mday > Horde_Date::daysInMonth($this->month, $this->year)) {
$this->mday -= Horde_Date::daysInMonth($this->month, $this->year);
++$this->month;
$this->correct(HORDE_DATE_MASK_MONTH);
}
while ($this->mday < 1) {
--$this->month;
$this->correct(HORDE_DATE_MASK_MONTH);
$this->mday += Horde_Date::daysInMonth($this->month, $this->year);
}
}
}
/**
* Compare this date to another date object to see which one is
* greater (later). Assumes that the dates are in the same
* timezone.
*
* @param mixed $date The date to compare to.
*
* @return integer == 0 if the dates are equal
* >= 1 if this date is greater (later)
* <= -1 if the other date is greater (later)
*/
function compareDate($date)
{
if (!is_object($date) || !is_a($date, 'Horde_Date')) {
$date = new Horde_Date($date);
}
if ($this->year != $date->year) {
return $this->year - $date->year;
}
if ($this->month != $date->month) {
return $this->month - $date->month;
}
return $this->mday - $date->mday;
}
/**
* Compare this to another date object by time, to see which one
* is greater (later). Assumes that the dates are in the same
* timezone.
*
* @param mixed $date The date to compare to.
*
* @return integer == 0 if the dates are equal
* >= 1 if this date is greater (later)
* <= -1 if the other date is greater (later)
*/
function compareTime($date)
{
if (!is_object($date) || !is_a($date, 'Horde_Date')) {
$date = new Horde_Date($date);
}
if ($this->hour != $date->hour) {
return $this->hour - $date->hour;
}
if ($this->min != $date->min) {
return $this->min - $date->min;
}
return $this->sec - $date->sec;
}
/**
* Compare this to another date object, including times, to see
* which one is greater (later). Assumes that the dates are in the
* same timezone.
*
* @param mixed $date The date to compare to.
*
* @return integer == 0 if the dates are equal
* >= 1 if this date is greater (later)
* <= -1 if the other date is greater (later)
*/
function compareDateTime($date)
{
if (!is_object($date) || !is_a($date, 'Horde_Date')) {
$date = new Horde_Date($date);
}
if ($diff = $this->compareDate($date)) {
return $diff;
}
return $this->compareTime($date);
}
/**
* Get the time offset for local time zone.
*
* @param boolean $colon Place a colon between hours and minutes?
*
* @return string Timezone offset as a string in the format +HH:MM.
*/
function tzOffset($colon = true)
{
$secs = $this->format('Z');
if ($secs < 0) {
$sign = '-';
$secs = -$secs;
} else {
$sign = '+';
}
$colon = $colon ? ':' : '';
$mins = intval(($secs + 30) / 60);
return sprintf('%s%02d%s%02d',
$sign, $mins / 60, $colon, $mins % 60);
}
/**
* Return the unix timestamp representation of this date.
*
* @return integer A unix timestamp.
*/
function timestamp()
{
if (class_exists('DateTime')) {
return $this->format('U');
} else {
return Horde_Date::_mktime($this->hour, $this->min, $this->sec, $this->month, $this->mday, $this->year);
}
}
/**
* Return the unix timestamp representation of this date, 12:00am.
*
* @return integer A unix timestamp.
*/
function datestamp()
{
if (class_exists('DateTime')) {
$dt = new DateTime();
$dt->setDate($this->year, $this->month, $this->mday);
$dt->setTime(0, 0, 0);
return $dt->format('U');
} else {
return Horde_Date::_mktime(0, 0, 0, $this->month, $this->mday, $this->year);
}
}
/**
* Format time using the specifiers available in date() or in the DateTime
* class' format() method.
*
* @since Horde 3.3
*
* @param string $format
*
* @return string Formatted time.
*/
function format($format)
{
if (class_exists('DateTime')) {
$dt = new DateTime();
$dt->setDate($this->year, $this->month, $this->mday);
$dt->setTime($this->hour, $this->min, $this->sec);
return $dt->format($format);
} else {
return date($format, $this->timestamp());
}
}
/**
* Format time in ISO-8601 format. Works correctly since Horde 3.2.
*
* @return string Date and time in ISO-8601 format.
*/
function iso8601DateTime()
{
return $this->rfc3339DateTime() . $this->tzOffset();
}
/**
* Format time in RFC 2822 format.
*
* @return string Date and time in RFC 2822 format.
*/
function rfc2822DateTime()
{
return $this->format('D, j M Y H:i:s') . ' ' . $this->tzOffset(false);
}
/**
* Format time in RFC 3339 format.
*
* @since Horde 3.1
*
* @return string Date and time in RFC 3339 format. The seconds part has
* been added with Horde 3.2.
*/
function rfc3339DateTime()
{
return $this->format('Y-m-d\TH:i:s');
}
/**
* Format time to standard 'ctime' format.
*
* @return string Date and time.
*/
function cTime()
{
return $this->format('D M j H:i:s Y');
}
/**
* Format date and time using strftime() format.
*
* @since Horde 3.1
*
* @return string strftime() formatted date and time.
*/
function strftime($format)
{
if (preg_match('/%[^' . $this->_supportedSpecs . ']/', $format)) {
return strftime($format, $this->timestamp());
} else {
return $this->_strftime($format);
}
}
/**
* Format date and time using a limited set of the strftime() format.
*
* @return string strftime() formatted date and time.
*/
function _strftime($format)
{
if (preg_match('/%[bBpxX]/', $format)) {
require_once 'Horde/NLS.php';
}
return preg_replace(
array('/%b/e',
'/%B/e',
'/%C/e',
'/%d/e',
'/%D/e',
'/%e/e',
'/%H/e',
'/%I/e',
'/%m/e',
'/%M/e',
'/%n/',
'/%p/e',
'/%R/e',
'/%S/e',
'/%t/',
'/%T/e',
'/%x/e',
'/%X/e',
'/%y/e',
'/%Y/',
'/%%/'),
array('$this->_strftime(NLS::getLangInfo(constant(\'ABMON_\' . (int)$this->month)))',
'$this->_strftime(NLS::getLangInfo(constant(\'MON_\' . (int)$this->month)))',
'(int)($this->year / 100)',
'sprintf(\'%02d\', $this->mday)',
'$this->_strftime(\'%m/%d/%y\')',
'sprintf(\'%2d\', $this->mday)',
'sprintf(\'%02d\', $this->hour)',
'sprintf(\'%02d\', $this->hour == 0 ? 12 : ($this->hour > 12 ? $this->hour - 12 : $this->hour))',
'sprintf(\'%02d\', $this->month)',
'sprintf(\'%02d\', $this->min)',
"\n",
'$this->_strftime(NLS::getLangInfo($this->hour < 12 ? AM_STR : PM_STR))',
'$this->_strftime(\'%H:%M\')',
'sprintf(\'%02d\', $this->sec)',
"\t",
'$this->_strftime(\'%H:%M:%S\')',
'$this->_strftime(NLS::getLangInfo(D_FMT))',
'$this->_strftime(NLS::getLangInfo(T_FMT))',
'substr(sprintf(\'%04d\', $this->year), -2)',
(int)$this->year,
'%'),
$format);
}
/**
* mktime() implementation that supports dates outside of 1970-2038,
* from http://phplens.com/phpeverywhere/adodb_date_library.
*
* @TODO remove in Horde 4
*
* This does NOT work with pre-1970 daylight saving times.
*
* @static
*/
function _mktime($hr, $min, $sec, $mon = false, $day = false,
$year = false, $is_dst = false, $is_gmt = false)
{
if ($mon === false) {
return $is_gmt
? @gmmktime($hr, $min, $sec)
: @mktime($hr, $min, $sec);
}
if ($year > 1901 && $year < 2038 &&
($year >= 1970 || version_compare(PHP_VERSION, '5.0.0', '>='))) {
return $is_gmt
? @gmmktime($hr, $min, $sec, $mon, $day, $year)
: @mktime($hr, $min, $sec, $mon, $day, $year);
}
$gmt_different = $is_gmt
? 0
: (mktime(0, 0, 0, 1, 2, 1970, 0) - gmmktime(0, 0, 0, 1, 2, 1970, 0));
$mon = intval($mon);
$day = intval($day);
$year = intval($year);
if ($mon > 12) {
$y = floor($mon / 12);
$year += $y;
$mon -= $y * 12;
} elseif ($mon < 1) {
$y = ceil((1 - $mon) / 12);
$year -= $y;
$mon += $y * 12;
}
$_day_power = 86400;
$_hour_power = 3600;
$_min_power = 60;
$_month_table_normal = array('', 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
$_month_table_leaf = array('', 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
$_total_date = 0;
if ($year >= 1970) {
for ($a = 1970; $a <= $year; $a++) {
$leaf = Horde_Date::isLeapYear($a);
if ($leaf == true) {
$loop_table = $_month_table_leaf;
$_add_date = 366;
} else {
$loop_table = $_month_table_normal;
$_add_date = 365;
}
if ($a < $year) {
$_total_date += $_add_date;
} else {
for ($b = 1; $b < $mon; $b++) {
$_total_date += $loop_table[$b];
}
}
}
return ($_total_date + $day - 1) * $_day_power + $hr * $_hour_power + $min * $_min_power + $sec + $gmt_different;
}
for ($a = 1969 ; $a >= $year; $a--) {
$leaf = Horde_Date::isLeapYear($a);
if ($leaf == true) {
$loop_table = $_month_table_leaf;
$_add_date = 366;
} else {
$loop_table = $_month_table_normal;
$_add_date = 365;
}
if ($a > $year) {
$_total_date += $_add_date;
} else {
for ($b = 12; $b > $mon; $b--) {
$_total_date += $loop_table[$b];
}
}
}
$_total_date += $loop_table[$mon] - $day;
$_day_time = $hr * $_hour_power + $min * $_min_power + $sec;
$_day_time = $_day_power - $_day_time;
$ret = -($_total_date * $_day_power + $_day_time - $gmt_different);
if ($ret < -12220185600) {
// If earlier than 5 Oct 1582 - gregorian correction.
return $ret + 10 * 86400;
} elseif ($ret < -12219321600) {
// If in limbo, reset to 15 Oct 1582.
return -12219321600;
} else {
return $ret;
}
}
}
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
require_once(dirname(__FILE__) . '/Horde_Date.php');
// {{{ Header

File diff suppressed because it is too large Load diff

View file

@ -124,7 +124,10 @@ class calendar_ical
private function get_parser()
{
// use Horde:iCalendar to parse vcalendar file format
require_once 'Horde/iCalendar.php';
@include_once('Horde/iCalendar.php');
if (!class_exists('Horde_iCalendar'))
require_once($this->cal->home . '/lib/Horde_iCalendar.php');
// set target charset for parsed events
$GLOBALS['_HORDE_STRING_CHARSET'] = RCMAIL_CHARSET;

View file

@ -239,10 +239,11 @@ class calendar_itip
if ($stored[$base])
return $token;
// @TODO: REPLACE works only with MySQL
// delete old entry
$this->rc->db->query("DELETE FROM itipinvitations WHERE token=?", $base);
$query = $this->rc->db->query(
"REPLACE INTO itipinvitations
"INSERT INTO itipinvitations
(token, event_uid, user_id, event, expires)
VALUES(?, ?, ?, ?, ?)",
$base,

View file

@ -450,17 +450,16 @@ class calendar_ui
$select = $this->interval_selector(array('name' => 'interval', 'class' => 'edit-recurrence-interval', 'id' => 'edit-recurrence-interval-monthly'));
$html = html::div($attrib, html::label(null, $this->cal->gettext('every')) . $select->show(1) . html::span('label-after', $this->cal->gettext('months')));
/* multiple month selection is not supported by Kolab
$checkbox = new html_radiobutton(array('name' => 'bymonthday', 'class' => 'edit-recurrence-monthly-bymonthday'));
$checkbox = new html_checkbox(array('name' => 'bymonthday', 'class' => 'edit-recurrence-monthly-bymonthday'));
for ($monthdays = '', $d = 1; $d <= 31; $d++) {
$monthdays .= html::label(array('class' => 'monthday'), $checkbox->show('', array('value' => $d)) . $d);
$monthdays .= $d % 7 ? ' ' : html::br();
}
*/
// rule selectors
$radio = new html_radiobutton(array('name' => 'repeatmode', 'class' => 'edit-recurrence-monthly-mode'));
$table = new html_table(array('cols' => 2, 'border' => 0, 'cellpadding' => 0, 'class' => 'formtable'));
$table->add('label', html::label(null, $radio->show('BYMONTHDAY', array('value' => 'BYMONTHDAY')) . ' ' . $this->cal->gettext('onsamedate'))); // $this->cal->gettext('each')
$table->add('label', html::label(null, $radio->show('BYMONTHDAY', array('value' => 'BYMONTHDAY')) . ' ' . $this->cal->gettext('each')));
$table->add(null, $monthdays);
$table->add('label', html::label(null, $radio->show('', array('value' => 'BYDAY')) . ' ' . $this->cal->gettext('onevery')));
$table->add(null, $this->rrule_selectors($attrib['part']));
@ -475,8 +474,7 @@ class calendar_ui
$html = html::div($attrib, html::label(null, $this->cal->gettext('every')) . $select->show(1) . html::span('label-after', $this->cal->gettext('years')));
// month selector
$monthmap = array('','jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec');
$boxtype = is_a($this->cal->driver, 'kolab_driver') ? 'radio' : 'checkbox';
$checkbox = new html_inputfield(array('type' => $boxtype, 'name' => 'bymonth', 'class' => 'edit-recurrence-yearly-bymonth'));
$checkbox = new html_checkbox(array('name' => 'bymonth', 'class' => 'edit-recurrence-yearly-bymonth'));
for ($months = '', $m = 1; $m <= 12; $m++) {
$months .= html::label(array('class' => 'month'), $checkbox->show(null, array('value' => $m)) . $this->cal->gettext($monthmap[$m]));
$months .= $m % 4 ? ' ' : html::br();
@ -538,13 +536,10 @@ class calendar_ui
$this->cal->gettext('first'),
$this->cal->gettext('second'),
$this->cal->gettext('third'),
$this->cal->gettext('fourth')
$this->cal->gettext('fourth'),
$this->cal->gettext('last')
),
array(1, 2, 3, 4));
// Kolab doesn't support 'last' but others do.
if (!is_a($this->cal->driver, 'kolab_driver'))
$select_prefix->add($this->cal->gettext('last'), -1);
array(1, 2, 3, 4, -1));
$select_wday = new html_select(array('name' => 'byday', 'id' => "edit-recurrence-$part-byday"));
if ($noselect) $select_wday->add($noselect, '');
@ -555,8 +550,6 @@ class calendar_ui
$d = $j % 7;
$select_wday->add($this->cal->gettext($daymap[$d]), strtoupper(substr($daymap[$d], 0, 2)));
}
if ($part == 'monthly')
$select_wday->add($this->cal->gettext('dayofmonth'), '');
return $select_prefix->show() . '&nbsp;' . $select_wday->show();
}
@ -664,13 +657,13 @@ class calendar_ui
if (!empty($this->cal->attachment['name'])) {
$table->add('title', Q(rcube_label('filename')));
$table->add(null, Q($this->cal->attachment['name']));
$table->add(null, '[' . html::a('?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING']), Q(rcube_label('download'))) . ']');
$table->add('header', Q($this->cal->attachment['name']));
$table->add('download-link', html::a('?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING']), Q(rcube_label('download'))));
}
if (!empty($this->cal->attachment['size'])) {
$table->add('title', Q(rcube_label('filesize')));
$table->add(null, Q(show_bytes($this->cal->attachment['size'])));
$table->add('header', Q(show_bytes($this->cal->attachment['size'])));
}
return $table->show($attrib);

View file

@ -0,0 +1,31 @@
#!/bin/sh
# Copy Horde_iCalendar classes and dependencies to stdout.
# This will create a standalone copy of the classes requried for iCal parsing.
SRCDIR=$1
if [ ! -d "$SRCDIR" ]; then
echo "Usage: get_horde_icalendar.sh SRCDIR"
echo "Please enter a valid source directory of the Horde lib"
exit 1
fi
echo "<?php
/**
* This is a concatenated copy of the following files:
* Horde/String.php, Horde/iCalendar.php, Horde/iCalendar/*.php
* Pull the latest version of these file from the PEAR channel of the Horde project at http://pear.horde.org
*/
require_once(dirname(__FILE__) . '/Horde_Date.php');"
sed 's/<?php//; s/?>//' $SRCDIR/String.php
echo "\n"
sed 's/<?php//; s/?>//' $SRCDIR/iCalendar.php | sed -E "s/include_once.+//; s/NLS::getCharset\(\)/'UTF-8'/"
echo "\n"
for fn in `ls $SRCDIR/iCalendar/*.php | grep -v 'vcard.php'`; do
sed 's/<?php//; s/?>//' $fn | sed -E "s/(include|require)_once.+//"
done;

View file

@ -4,7 +4,7 @@
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd">
<name>calendar</name>
<uri>http://git.kolab.org/roundcube-plugins-kolab/</uri>
<uri>http://git.kolab.org/roundcubemail-plugins-kolab/</uri>
<summary>Calendar plugin</summary>
<description>-</description>
<lead>
@ -64,10 +64,9 @@
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="lib/Horde_Date_Recurrence.php" role="php">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
<file name="lib/Horde_Date.php" role="php"></file>
<file name="lib/Horde_Date_Recurrence.php" role="php"></file>
<file name="lib/Horde_iCalendar.php" role="php"></file>
<file name="lib/fullcalendar-rc.patch" role="data">
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
@ -157,6 +156,7 @@
<file name="config.inc.php.dist" role="data"></file>
<file name="LICENSE" role="data"></file>
<file name="README" role="data"></file>
<file name="TODO" role="data"></file>
<file name="localization/bg_BG.inc" role="data"></file>

View file

@ -286,39 +286,45 @@ a.miniColors-trigger {
#attachmentcontainer {
position: absolute;
top: 80px;
left: 20px;
right: 20px;
bottom: 20px;
top: 60px;
left: 0px;
right: 0px;
bottom: 0px;
}
#attachmentframe {
width: 100%;
height: 100%;
border: 1px solid #999999;
background-color: #F9F9F9;
border: 0;
background-color: #fff;
border-radius: 4px;
}
#partheader {
position: absolute;
top: 20px;
left: 220px;
right: 20px;
height: 40px;
position: relative;
padding: 3px 0;
background: #f9f9f9;
background: -moz-linear-gradient(top, #fff 0%, #e9e9e9 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fff), color-stop(100%,#e9e9e9));
background: -o-linear-gradient(top, #fff 0%, #e9e9e9 100%);
background: -ms-linear-gradient(top, #fff 0%, #e9e9e9 100%);
background: linear-gradient(top, #fff 0%, #e9e9e9 100%);
}
#partheader table td {
padding-left: 2px;
padding-right: 4px;
vertical-align: middle;
font-size: 11px;
color: #666;
padding: 2px 8px;
}
#partheader table td.title {
color: #666;
#partheader table td.header {
font-weight: bold;
}
#partheader table td.title a {
color: #666;
text-decoration: none;
}
#edit-attachments {
margin-top: 0.6em;
}
@ -502,8 +508,8 @@ td.topalign {
.event-update-confirm .message {
margin-top: 0.5em;
padding: 0.8em;
background-color: #F7FDCB;
border: 1px solid #C2D071;
border: 1px solid #ffdf0e;
background-color: #fef893;
}
.event-dialog-message .message,
@ -540,8 +546,6 @@ td.topalign {
#edit-attendees-notify {
margin: 0.3em 0;
padding: 0.5em;
border: 1px solid #ffdf0e;
background-color: #fef893;
}
#edit-attendees-table {

View file

@ -26,7 +26,7 @@
</div>
<div id="attachmentcontainer" class="uibox">
<roundcube:object name="plugin.attachmentframe" id="attachmentframe" style="width:100%; height:100%" />
<roundcube:object name="plugin.attachmentframe" id="attachmentframe" class="header-table" style="width:100%" />
</div>
</div>

View file

@ -51,7 +51,7 @@ class kolab_addressbook extends rcube_plugin
$this->rc = rcmail::get_instance();
// load required plugin
$this->require_plugin('kolab_core');
$this->require_plugin('libkolab');
// register hooks
$this->add_hook('addressbooks_list', array($this, 'address_sources'));
@ -245,7 +245,7 @@ class kolab_addressbook extends rcube_plugin
}
// get all folders that have "contact" type
$this->folders = rcube_kolab::get_folders('contact');
$this->folders = kolab_storage::get_folders('contact');
if (PEAR::isError($this->folders)) {
raise_error(array(
@ -264,7 +264,7 @@ class kolab_addressbook extends rcube_plugin
foreach ($names as $utf7name => $name) {
// create instance of rcube_contacts
$abook_id = rcube_kolab::folder_id($utf7name);
$abook_id = kolab_storage::folder_id($utf7name);
$abook = new rcube_kolab_contacts($utf7name);
$this->sources[$abook_id] = $abook;
}
@ -289,12 +289,11 @@ class kolab_addressbook extends rcube_plugin
// extend the list of contact fields to be displayed in the 'personal' section
if (is_array($p['form']['personal'])) {
$p['form']['contact']['content']['officelocation'] = array('size' => 40);
$p['form']['personal']['content']['initials'] = array('size' => 6);
$p['form']['personal']['content']['profession'] = array('size' => 40);
$p['form']['personal']['content']['children'] = array('size' => 40);
$p['form']['personal']['content']['pgppublickey'] = array('size' => 40);
$p['form']['personal']['content']['freebusyurl'] = array('size' => 40);
$p['form']['personal']['content']['pgppublickey'] = array('size' => 70);
$p['form']['personal']['content']['pkcs7publickey'] = array('size' => 70);
// re-order fields according to the coltypes list
$p['form']['contact']['content'] = $this->_sort_form_fields($p['form']['contact']['content']);
@ -304,8 +303,9 @@ class kolab_addressbook extends rcube_plugin
$p['form']['settings'] = array(
'name' => $this->gettext('settings'),
'content' => array(
'pgppublickey' => array('size' => 40, 'visible' => true),
'freebusyurl' => array('size' => 40, 'visible' => true),
'pgppublickey' => array('size' => 70, 'visible' => true),
'pkcs7publickey' => array('size' => 70, 'visible' => false),
)
);
*/
@ -481,7 +481,7 @@ class kolab_addressbook extends rcube_plugin
if (!$plugin['abort']) {
if ($oldfolder != $folder)
$result = rcube_kolab::folder_rename($oldfolder, $folder);
$result = kolab_storage::folder_rename($oldfolder, $folder);
else
$result = true;
}
@ -497,7 +497,7 @@ class kolab_addressbook extends rcube_plugin
$folder = $plugin['name'];
if (!$plugin['abort']) {
$result = rcube_kolab::folder_create($folder, 'contact', false);
$result = kolab_storage::folder_create($folder, 'contact');
}
else {
$result = $plugin['result'];
@ -545,7 +545,7 @@ class kolab_addressbook extends rcube_plugin
$this->rc->output->show_message('kolab_addressbook.book'.$type.'d', 'confirmation');
$this->rc->output->command('set_env', 'delimiter', $delimiter);
$this->rc->output->command('book_update', array(
'id' => rcube_kolab::folder_id($folder),
'id' => kolab_storage::folder_id($folder),
'name' => $name,
'readonly' => false,
'editable' => true,
@ -553,7 +553,7 @@ class kolab_addressbook extends rcube_plugin
'realname' => rcube_charset::convert($folder, 'UTF7-IMAP'), // IMAP folder name
'class_name' => $kolab_folder->get_namespace(),
'kolab' => true,
), rcube_kolab::folder_id($oldfolder));
), kolab_storage::folder_id($oldfolder));
$this->rc->output->send('iframe');
}
@ -574,12 +574,12 @@ class kolab_addressbook extends rcube_plugin
{
$folder = trim(get_input_value('_source', RCUBE_INPUT_GPC, true, 'UTF7-IMAP'));
if (rcube_kolab::folder_delete($folder)) {
if (kolab_storage::folder_delete($folder)) {
$this->rc->output->show_message('kolab_addressbook.bookdeleted', 'confirmation');
$this->rc->output->set_env('pagecount', 0);
$this->rc->output->command('set_rowcount', rcmail_get_rowcount_text(new rcube_result_set()));
$this->rc->output->command('list_contacts_clear');
$this->rc->output->command('book_delete_done', rcube_kolab::folder_id($folder));
$this->rc->output->command('book_delete_done', kolab_storage::folder_id($folder));
}
else {
$this->rc->output->show_message('kolab_addressbook.bookdeleteerror', 'error');

View file

@ -5,7 +5,7 @@
*
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@ -144,8 +144,8 @@ class kolab_addressbook_ui
if (strlen($folder)) {
$hidden_fields[] = array('name' => '_oldname', 'value' => $folder);
$this->rc->imap_connect();
$options = $this->rc->imap->mailbox_info($folder);
$this->rc->storage_connect();
$options = $this->rc->get_storage()->mailbox_info($folder);
}
$form = array();
@ -156,7 +156,7 @@ class kolab_addressbook_ui
);
if (!empty($options) && ($options['norename'] || $options['protected'])) {
$foldername = Q(str_replace($delimiter, ' &raquo; ', rcube_kolab::object_name($folder)));
$foldername = Q(str_replace($delimiter, ' &raquo; ', kolab_storage::object_name($folder)));
}
else {
$foldername = new html_inputfield(array('name' => '_name', 'id' => '_name', 'size' => 30));
@ -178,7 +178,7 @@ class kolab_addressbook_ui
$hidden_fields[] = array('name' => '_parent', 'value' => $path_imap);
}
else {
$select = rcube_kolab::folder_selector('contact', array('name' => '_parent'), $folder);
$select = kolab_storage::folder_selector('contact', array('name' => '_parent'), $folder);
$form['props']['fieldsets']['location']['content']['path'] = array(
'label' => $this->plugin->gettext('parentbook'),

View file

@ -4,7 +4,7 @@
* Backend class for a custom address book
*
* This part of the Roundcube+Kolab integration and connects the
* rcube_addressbook interface with the rcube_kolab wrapper for Kolab_Storage
* rcube_addressbook interface with the kolab_storage wrapper from libkolab
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
* @author Aleksander Machniak <machniak@kolabsys.com>
@ -46,30 +46,28 @@ class rcube_kolab_contacts extends rcube_addressbook
'department' => array('limit' => 1),
'email' => array('subtypes' => null),
'phone' => array(),
'address' => array('limit' => 2, 'subtypes' => array('home','business')),
'officelocation' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1,
'label' => 'kolab_addressbook.officelocation', 'category' => 'main'),
'website' => array('limit' => 1, 'subtypes' => null),
'im' => array('limit' => 1, 'subtypes' => null),
'address' => array('subtypes' => array('home','work','office')),
'website' => array('subtypes' => array('homepage','blog')),
'im' => array('subtypes' => null),
'gender' => array('limit' => 1),
'initials' => array('type' => 'text', 'size' => 6, 'maxlength' => 10, 'limit' => 1,
'label' => 'kolab_addressbook.initials', 'category' => 'personal'),
'birthday' => array('limit' => 1),
'anniversary' => array('limit' => 1),
'profession' => array('type' => 'text', 'size' => 40, 'maxlength' => 80, 'limit' => 1,
'label' => 'kolab_addressbook.profession', 'category' => 'personal'),
'manager' => array('limit' => 1),
'assistant' => array('limit' => 1),
'manager' => array('limit' => null),
'assistant' => array('limit' => null),
'spouse' => array('limit' => 1),
'children' => array('type' => 'text', 'size' => 40, 'maxlength' => 80, 'limit' => 1,
'children' => array('type' => 'text', 'size' => 40, 'maxlength' => 80, 'limit' => null,
'label' => 'kolab_addressbook.children', 'category' => 'personal'),
'pgppublickey' => array('type' => 'text', 'size' => 40, 'limit' => 1,
'label' => 'kolab_addressbook.pgppublickey'),
'freebusyurl' => array('type' => 'text', 'size' => 40, 'limit' => 1,
'label' => 'kolab_addressbook.freebusyurl'),
'pgppublickey' => array('type' => 'textarea', 'size' => 70, 'rows' => 10, 'limit' => 1,
'label' => 'kolab_addressbook.pgppublickey'),
'pkcs7publickey' => array('type' => 'textarea', 'size' => 70, 'rows' => 10, 'limit' => 1,
'label' => 'kolab_addressbook.pkcs7publickey'),
'notes' => array(),
'photo' => array(),
// TODO: define more Kolab-specific fields such as: language, latitude, longitude
// TODO: define more Kolab-specific fields such as: language, latitude, longitude, crypto settings
);
/**
@ -86,47 +84,14 @@ class rcube_kolab_contacts extends rcube_addressbook
private $gid;
private $storagefolder;
private $contactstorage;
private $liststorage;
private $contacts;
private $distlists;
private $groupmembers;
private $id2uid;
private $filter;
private $result;
private $namespace;
private $imap_folder = 'INBOX/Contacts';
private $gender_map = array(0 => 'male', 1 => 'female');
private $phonetypemap = array('home' => 'home1', 'work' => 'business1', 'work2' => 'business2', 'workfax' => 'businessfax');
private $addresstypemap = array('work' => 'business');
private $fieldmap = array(
// kolab => roundcube
'full-name' => 'name',
'given-name' => 'firstname',
'middle-names' => 'middlename',
'last-name' => 'surname',
'prefix' => 'prefix',
'suffix' => 'suffix',
'nick-name' => 'nickname',
'organization' => 'organization',
'department' => 'department',
'job-title' => 'jobtitle',
'initials' => 'initials',
'birthday' => 'birthday',
'anniversary' => 'anniversary',
'im-address' => 'im',
'web-page' => 'website',
'office-location' => 'officelocation',
'profession' => 'profession',
'manager-name' => 'manager',
'assistant' => 'assistant',
'spouse-name' => 'spouse',
'children' => 'children',
'body' => 'notes',
'pgp-publickey' => 'pgppublickey',
'free-busy-url' => 'freebusyurl',
'gender' => 'gender',
);
private $action;
public function __construct($imap_folder = null)
@ -136,9 +101,9 @@ class rcube_kolab_contacts extends rcube_addressbook
}
// extend coltypes configuration
$format = rcube_kolab::get_format('contact');
$this->coltypes['phone']['subtypes'] = $format->_phone_types;
$this->coltypes['address']['subtypes'] = $format->_address_types;
$format = kolab_format::factory('contact');
$this->coltypes['phone']['subtypes'] = array_keys($format->phonetypes);
$this->coltypes['address']['subtypes'] = array_keys($format->addresstypes);
// set localized labels for proprietary cols
foreach ($this->coltypes as $col => $prop) {
@ -147,17 +112,17 @@ class rcube_kolab_contacts extends rcube_addressbook
}
// fetch objects from the given IMAP folder
$this->storagefolder = rcube_kolab::get_folder($this->imap_folder);
$this->ready = !PEAR::isError($this->storagefolder);
$this->storagefolder = kolab_storage::get_folder($this->imap_folder);
$this->ready = $this->storagefolder && !PEAR::isError($this->storagefolder);
// Set readonly and editable flags according to folder permissions
if ($this->ready) {
if ($this->get_owner() == $_SESSION['username']) {
if ($this->storagefolder->get_owner() == $_SESSION['username']) {
$this->editable = true;
$this->readonly = false;
}
else {
$rights = $this->storagefolder->getMyRights();
$rights = $this->storagefolder->get_myrights();
if (!PEAR::isError($rights)) {
if (strpos($rights, 'i') !== false)
$this->readonly = false;
@ -166,6 +131,8 @@ class rcube_kolab_contacts extends rcube_addressbook
}
}
}
$this->action = rcmail::get_instance()->action;
}
@ -176,7 +143,7 @@ class rcube_kolab_contacts extends rcube_addressbook
*/
public function get_name()
{
$folder = rcube_kolab::object_name($this->imap_folder, $this->namespace);
$folder = kolab_storage::object_name($this->imap_folder, $this->namespace);
return $folder;
}
@ -192,17 +159,6 @@ class rcube_kolab_contacts extends rcube_addressbook
}
/**
* Getter for the IMAP folder owner
*
* @return string Name of the folder owner
*/
public function get_owner()
{
return $this->storagefolder->getOwner();
}
/**
* Getter for the name of the namespace to which the IMAP folder belongs
*
@ -210,8 +166,8 @@ class rcube_kolab_contacts extends rcube_addressbook
*/
public function get_namespace()
{
if ($this->namespace === null) {
$this->namespace = rcube_kolab::folder_namespace($this->imap_folder);
if ($this->namespace === null && $this->ready) {
$this->namespace = $this->storagefolder->get_namespace();
}
return $this->namespace;
@ -270,8 +226,8 @@ class rcube_kolab_contacts extends rcube_addressbook
$this->_fetch_groups();
$groups = array();
foreach ((array)$this->distlists as $group) {
if (!$search || strstr(strtolower($group['last-name']), strtolower($search)))
$groups[$group['last-name']] = array('ID' => $group['ID'], 'name' => $group['last-name']);
if (!$search || strstr(strtolower($group['name']), strtolower($search)))
$groups[$group['name']] = array('ID' => $group['ID'], 'name' => $group['name']);
}
// sort groups
@ -290,23 +246,38 @@ class rcube_kolab_contacts extends rcube_addressbook
*/
public function list_records($cols=null, $subset=0)
{
$this->result = $this->count();
$this->result = new rcube_result_set(0, ($this->list_page-1) * $this->page_size);;
// list member of the selected group
if ($this->gid) {
$this->_fetch_groups();
$seen = array();
$this->result->count = 0;
foreach ((array)$this->distlists[$this->gid]['member'] as $member) {
// skip member that don't match the search filter
if (is_array($this->filter['ids']) && array_search($member['ID'], $this->filter['ids']) === false)
continue;
if ($this->contacts[$member['ID']] && !$seen[$member['ID']]++)
if ($member['uid'] && ($contact = $this->storagefolder->get_object($member['uid'])) && !$seen[$member['ID']]++) {
$this->contacts[$member['ID']] = $this->_to_rcube_contact($contact);
$this->result->count++;
}
else if ($member['email'] && !$seen[$member['ID']]++) {
$this->contacts[$member['ID']] = $member;
$this->result->count++;
}
}
$ids = array_keys($seen);
}
else
$ids = is_array($this->filter['ids']) ? $this->filter['ids'] : array_keys($this->contacts);
else if (is_array($this->filter['ids'])) {
$ids = $this->filter['ids'];
if ($this->result->count = count($ids))
$this->_fetch_contacts(array(array('uid', '=', $ids)));
}
else {
$this->_fetch_contacts();
$ids = array_keys($this->contacts);
$this->result->count = count($ids);
}
// sort data arrays according to desired list sorting
if ($count = count($ids)) {
@ -348,8 +319,6 @@ class rcube_kolab_contacts extends rcube_addressbook
*/
public function search($fields, $value, $mode=0, $select=true, $nocount=false, $required=array())
{
$this->_fetch_contacts();
// search by ID
if ($fields == $this->primary_key) {
$ids = !is_array($value) ? explode(',', $value) : $value;
@ -384,6 +353,25 @@ class rcube_kolab_contacts extends rcube_addressbook
// build key name regexp
$regexp = '/^(' . implode($fields, '|') . ')(?:.*)$/';
// pass query to storage if only indexed cols are involved
// NOTE: this is only some rough pre-filtering but probably includes false positives
$squery = array();
if (count(array_intersect(kolab_format_contact::$fulltext_cols, $fields)) == $scount) {
switch ($mode) {
case 1: $prefix = ' '; $suffix = ' '; break; // strict
case 2: $prefix = ' '; $suffix = ''; break; // prefix
default: $prefix = ''; $suffix = ''; break; // substring
}
$search_string = is_array($value) ? join(' ', $value) : $value;
foreach (rcube_utils::normalize_string($search_string, true) as $word) {
$squery[] = array('words', 'LIKE', '%' . $prefix . $word . $suffix . '%');
}
}
// get all/matching records
$this->_fetch_contacts($squery);
// save searching conditions
$this->filter = array('fields' => $fields, 'value' => $value, 'mode' => $mode, 'ids' => array());
@ -408,17 +396,19 @@ class rcube_kolab_contacts extends rcube_addressbook
}
foreach ((array)$contact[$col] as $val) {
$val = mb_strtolower($val);
switch ($mode) {
case 1:
$got = ($val == $search);
break;
case 2:
$got = ($search == substr($val, 0, strlen($search)));
break;
default:
$got = (strpos($val, $search) !== false);
break;
foreach ((array)$val as $str) {
$str = mb_strtolower($str);
switch ($mode) {
case 1:
$got = ($str == $search);
break;
case 2:
$got = ($search == substr($str, 0, strlen($search)));
break;
default:
$got = (strpos($str, $search) !== false);
break;
}
}
if ($got) {
@ -461,9 +451,17 @@ class rcube_kolab_contacts extends rcube_addressbook
*/
public function count()
{
$this->_fetch_contacts();
$this->_fetch_groups();
$count = $this->gid ? count($this->distlists[$this->gid]['member']) : (is_array($this->filter['ids']) ? count($this->filter['ids']) : count($this->contacts));
if ($this->gid) {
$this->_fetch_groups();
$count = count($this->distlists[$this->gid]['member']);
}
else if (is_array($this->filter['ids'])) {
$count = count($this->filter['ids']);
}
else {
$count = $this->storagefolder->count();
}
return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
}
@ -488,11 +486,21 @@ class rcube_kolab_contacts extends rcube_addressbook
*/
public function get_record($id, $assoc=false)
{
$this->_fetch_contacts();
if ($this->contacts[$id]) {
$rec = null;
$uid = $this->_id2uid($id);
if (strpos($uid, 'mailto:') === 0) {
$this->_fetch_groups(true);
$rec = $this->contacts[$id];
$this->readonly = true; // set source to read-only
}
else if ($object = $this->storagefolder->get_object($uid)) {
$rec = $this->_to_rcube_contact($object);
}
if ($rec) {
$this->result = new rcube_result_set(1);
$this->result->add($this->contacts[$id]);
return $assoc ? $this->contacts[$id] : $this->result;
$this->result->add($rec);
return $assoc ? $rec : $this->result;
}
return false;
@ -512,7 +520,7 @@ class rcube_kolab_contacts extends rcube_addressbook
foreach ((array)$this->groupmembers[$id] as $gid) {
if ($group = $this->distlists[$gid])
$out[$gid] = $group['last-name'];
$out[$gid] = $group['name'];
}
return $out;
@ -546,26 +554,21 @@ class rcube_kolab_contacts extends rcube_addressbook
}
if (!$existing) {
$this->_connect();
// generate new Kolab contact item
$object = $this->_from_rcube_contact($save_data);
$object['uid'] = $this->contactstorage->generateUID();
$saved = $this->storagefolder->save($object, 'contact');
$saved = $this->contactstorage->save($object);
if (PEAR::isError($saved)) {
if (!$saved) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving contact object to Kolab server:" . $saved->getMessage()),
'message' => "Error saving contact object to Kolab server"),
true, false);
}
else {
$contact = $this->_to_rcube_contact($object);
$id = $contact['ID'];
$this->contacts[$id] = $contact;
$this->id2uid[$id] = $object['uid'];
$insert_id = $id;
}
}
@ -586,22 +589,21 @@ class rcube_kolab_contacts extends rcube_addressbook
public function update($id, $save_data)
{
$updated = false;
$this->_fetch_contacts();
if ($this->contacts[$id] && ($uid = $this->id2uid[$id])) {
$old = $this->contactstorage->getObject($uid);
$object = array_merge($old, $this->_from_rcube_contact($save_data));
if ($old = $this->storagefolder->get_object($this->_id2uid($id))) {
$object = $this->_from_rcube_contact($save_data, $old);
$saved = $this->contactstorage->save($object, $uid);
if (PEAR::isError($saved)) {
if (!$this->storagefolder->save($object, 'contact', $old['uid'])) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving contact object to Kolab server:" . $saved->getMessage()),
'message' => "Error saving contact object to Kolab server"),
true, false);
}
else {
$this->contacts[$id] = $this->_to_rcube_contact($object);
$updated = true;
// TODO: update data in groups this contact is member of
}
}
@ -619,45 +621,38 @@ class rcube_kolab_contacts extends rcube_addressbook
*/
public function delete($ids, $force=true)
{
$this->_fetch_contacts();
$this->_fetch_groups();
if (!is_array($ids))
$ids = explode(',', $ids);
$count = 0;
$imap_uids = array();
foreach ($ids as $id) {
if ($uid = $this->id2uid[$id]) {
$imap_uid = $this->contactstorage->_getStorageId($uid);
$deleted = $this->contactstorage->delete($uid, $force);
if ($uid = $this->_id2uid($id)) {
$is_mailto = strpos($uid, 'mailto:') === 0;
$deleted = $is_mailto || $this->storagefolder->delete($uid, $force);
if (PEAR::isError($deleted)) {
if (!$deleted) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error deleting a contact object from the Kolab server:" . $deleted->getMessage()),
'message' => "Error deleting a contact object $uid from the Kolab server"),
true, false);
}
else {
// remove from distribution lists
foreach ((array)$this->groupmembers[$id] as $gid)
$this->remove_from_group($gid, $id);
foreach ((array)$this->groupmembers[$id] as $gid) {
if (!$is_mailto || $gid == $this->gid)
$this->remove_from_group($gid, $id);
}
$imap_uids[$id] = $imap_uid;
// clear internal cache
unset($this->contacts[$id], $this->id2uid[$id], $this->groupmembers[$id]);
unset($this->contacts[$id], $this->groupmembers[$id]);
$count++;
}
}
}
// store IMAP uids for undelete()
if (!$force) {
$_SESSION['kolab_delete_uids'] = $imap_uids;
}
return $count;
}
@ -675,52 +670,22 @@ class rcube_kolab_contacts extends rcube_addressbook
if (!is_array($ids))
$ids = explode(',', $ids);
$count = 0;
$uids = array();
$imap_uids = $_SESSION['kolab_delete_uids'];
// convert contact IDs into IMAP UIDs
foreach ($ids as $id)
if ($uid = $imap_uids[$id])
$uids[] = $uid;
if (!empty($uids)) {
$session = &Horde_Kolab_Session::singleton();
$imap = &$session->getImap();
if (is_object($imap) && is_a($imap, 'PEAR_Error')) {
$error = $imap;
$count = 0;
foreach ($ids as $id) {
$uid = $this->_id2uid($id);
if ($this->storagefolder->undelete($uid)) {
$count++;
}
else {
$result = $imap->select($this->imap_folder);
if (is_object($result) && is_a($result, 'PEAR_Error')) {
$error = $result;
}
else {
$result = $imap->undeleteMessages(implode(',', $uids));
if (is_object($result) && is_a($result, 'PEAR_Error')) {
$error = $result;
}
else {
$this->_connect();
$this->contactstorage->synchronize();
}
}
}
if ($error) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error undeleting a contact object(s) from the Kolab server:" . $error->getMessage()),
'message' => "Error undeleting a contact object $uid from the Kolab server"),
true, false);
}
$rcmail = rcmail::get_instance();
$rcmail->session->remove('kolab_delete_uids');
}
return count($uids);
return $count;
}
@ -729,11 +694,8 @@ class rcube_kolab_contacts extends rcube_addressbook
*/
public function delete_all()
{
$this->_connect();
if (!PEAR::isError($this->contactstorage->deleteAll())) {
if ($this->storagefolder->delete_all()) {
$this->contacts = array();
$this->id2uid = array();
$this->result = null;
}
}
@ -760,22 +722,22 @@ class rcube_kolab_contacts extends rcube_addressbook
$result = false;
$list = array(
'uid' => $this->liststorage->generateUID(),
'last-name' => $name,
'uid' => kolab_format::generate_uid(),
'name' => $name,
'member' => array(),
);
$saved = $this->liststorage->save($list);
$saved = $this->storagefolder->save($list, 'distribution-list');
if (PEAR::isError($saved)) {
if (!$saved) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving distribution-list object to Kolab server:" . $saved->getMessage()),
'message' => "Error saving distribution-list object to Kolab server"),
true, false);
return false;
}
else {
$id = md5($list['uid']);
$id = $this->_uid2id($list['uid']);
$this->distlists[$id] = $list;
$result = array('id' => $id, 'name' => $name);
}
@ -795,13 +757,13 @@ class rcube_kolab_contacts extends rcube_addressbook
$result = false;
if ($list = $this->distlists[$gid])
$deleted = $this->liststorage->delete($list['uid']);
$deleted = $this->storagefolder->delete($list['uid']);
if (PEAR::isError($deleted)) {
if (!$deleted) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error deleting distribution-list object from the Kolab server:" . $deleted->getMessage()),
'message' => "Error deleting distribution-list object from the Kolab server"),
true, false);
}
else
@ -822,16 +784,16 @@ class rcube_kolab_contacts extends rcube_addressbook
$this->_fetch_groups();
$list = $this->distlists[$gid];
if ($newname != $list['last-name']) {
$list['last-name'] = $newname;
$saved = $this->liststorage->save($list, $list['uid']);
if ($newname != $list['name']) {
$list['name'] = $newname;
$saved = $this->storagefolder->save($list, 'distribution-list', $list['uid']);
}
if (PEAR::isError($saved)) {
if (!$saved) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving distribution-list object to Kolab server:" . $saved->getMessage()),
'message' => "Error saving distribution-list object to Kolab server"),
true, false);
return false;
}
@ -854,8 +816,7 @@ class rcube_kolab_contacts extends rcube_addressbook
$added = 0;
$exists = array();
$this->_fetch_groups();
$this->_fetch_contacts();
$this->_fetch_groups(true);
$list = $this->distlists[$gid];
foreach ((array)$list['member'] as $i => $member)
@ -865,28 +826,37 @@ class rcube_kolab_contacts extends rcube_addressbook
$ids = array_diff($ids, $exists);
foreach ($ids as $contact_id) {
if ($uid = $this->id2uid[$contact_id]) {
$contact = $this->contacts[$contact_id];
foreach ($this->get_col_values('email', $contact, true) as $email) {
$list['member'][] = array(
'uid' => $uid,
'display-name' => $contact['name'],
'smtp-address' => $email,
);
}
$uid = $this->_id2uid($contact_id);
if ($contact = $this->storagefolder->get_object($uid)) {
foreach ($this->get_col_values('email', $contact, true) as $email)
break;
$list['member'][] = array(
'uid' => $uid,
'email' => $email,
'name' => $contact['name'],
);
$this->groupmembers[$contact_id][] = $gid;
$added++;
}
else if (strpos($uid, 'mailto:') === 0 && ($contact = $this->contacts[$contact_id])) {
$list['member'][] = array(
'email' => $contact['email'],
'name' => $contact['name'],
);
$this->groupmembers[$contact_id][] = $gid;
$added++;
}
}
if ($added)
$saved = $this->liststorage->save($list, $list['uid']);
$saved = $this->storagefolder->save($list, 'distribution-list', $list['uid']);
if (PEAR::isError($saved)) {
if (!$saved) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving distribution-list to Kolab server:" . $saved->getMessage()),
'message' => "Error saving distribution-list to Kolab server"),
true, false);
$added = false;
}
@ -921,13 +891,13 @@ class rcube_kolab_contacts extends rcube_addressbook
// write distribution list back to server
$list['member'] = $new_member;
$saved = $this->liststorage->save($list, $list['uid']);
$saved = $this->storagefolder->save($list, 'distribution-list', $list['uid']);
if (PEAR::isError($saved)) {
if (!$saved) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving distribution-list object to Kolab server:" . $saved->getMessage()),
'message' => "Error saving distribution-list object to Kolab server"),
true, false);
}
else {
@ -970,44 +940,16 @@ class rcube_kolab_contacts extends rcube_addressbook
}
/**
* Establishes a connection to the Kolab_Data object for accessing contact data
* Query storage layer and store records in private member var
*/
private function _connect()
{
if (!isset($this->contactstorage)) {
$this->contactstorage = $this->storagefolder->getData(null);
}
}
/**
* Establishes a connection to the Kolab_Data object for accessing groups data
*/
private function _connect_groups()
{
if (!isset($this->liststorage)) {
$this->liststorage = $this->storagefolder->getData('distributionlist');
}
}
/**
* Simply fetch all records and store them in private member vars
*/
private function _fetch_contacts()
private function _fetch_contacts($query = array())
{
if (!isset($this->contacts)) {
$this->_connect();
// read contacts
$this->contacts = $this->id2uid = array();
foreach ((array)$this->contactstorage->getObjects() as $record) {
// Because of a bug, sometimes group records are returned
if ($record['__type'] == 'Group')
continue;
$this->contacts = array();
foreach ((array)$this->storagefolder->select($query) as $record) {
$contact = $this->_to_rcube_contact($record);
$id = $contact['ID'];
$this->contacts[$id] = $contact;
$this->id2uid[$id] = $record['uid'];
}
}
}
@ -1055,178 +997,158 @@ class rcube_kolab_contacts extends rcube_addressbook
/**
* Read distribution-lists AKA groups from server
*/
private function _fetch_groups()
private function _fetch_groups($with_contacts = false)
{
if (!isset($this->distlists)) {
$this->_connect_groups();
$this->distlists = $this->groupmembers = array();
foreach ((array)$this->liststorage->getObjects() as $record) {
// FIXME: folders without any distribution-list objects return contacts instead ?!
if ($record['__type'] != 'Group')
continue;
$record['ID'] = md5($record['uid']);
foreach ((array)$this->storagefolder->get_objects('distribution-list') as $record) {
$record['ID'] = $this->_uid2id($record['uid']);
foreach ((array)$record['member'] as $i => $member) {
$mid = md5($member['uid']);
$mid = $this->_uid2id($member['uid'] ? $member['uid'] : 'mailto:' . $member['email']);
$record['member'][$i]['ID'] = $mid;
$record['member'][$i]['readonly'] = empty($member['uid']);
$this->groupmembers[$mid][] = $record['ID'];
if ($with_contacts && empty($member['uid']))
$this->contacts[$mid] = $record['member'][$i];
}
$this->distlists[$record['ID']] = $record;
}
}
}
/**
* Encode object UID into a safe identifier
*/
private function _uid2id($uid)
{
return rtrim(strtr(base64_encode($uid), '+/', '-_'), '=');
}
/**
* Convert Roundcube object identifier back into the original UID
*/
private function _id2uid($id)
{
return base64_decode(str_pad(strtr($id, '-_', '+/'), strlen($id) % 4, '=', STR_PAD_RIGHT));
}
/**
* Map fields from internal Kolab_Format to Roundcube contact format
*/
private function _to_rcube_contact($record)
{
$out = array(
'ID' => md5($record['uid']),
'email' => array(),
'phone' => array(),
);
$record['ID'] = $this->_uid2id($record['uid']);
foreach ($this->fieldmap as $kolab => $rcube) {
if (strlen($record[$kolab]))
$out[$rcube] = $record[$kolab];
if (is_array($record['phone'])) {
$phones = $record['phone'];
unset($record['phone']);
foreach ((array)$phones as $i => $phone) {
$key = 'phone' . ($phone['type'] ? ':' . $phone['type'] : '');
$record[$key][] = $phone['number'];
}
}
if (isset($record['gender']))
$out['gender'] = $this->gender_map[$record['gender']];
foreach ((array)$record['email'] as $i => $email)
$out['email'][] = $email['smtp-address'];
if (!$record['email'] && $record['emails'])
$out['email'] = preg_split('/,\s*/', $record['emails']);
foreach ((array)$record['phone'] as $i => $phone)
$out['phone:'.$phone['type']][] = $phone['number'];
if (is_array($record['website'])) {
$urls = $record['website'];
unset($record['website']);
foreach ((array)$urls as $i => $url) {
$key = 'website' . ($url['type'] ? ':' . $url['type'] : '');
$record[$key][] = $url['url'];
}
}
if (is_array($record['address'])) {
foreach ($record['address'] as $i => $adr) {
$key = 'address:' . $adr['type'];
$out[$key][] = array(
'street' => $adr['street'],
$addresses = $record['address'];
unset($record['address']);
foreach ($addresses as $i => $adr) {
$key = 'address' . ($adr['type'] ? ':' . $adr['type'] : '');
$record[$key][] = array(
'street' => $adr['street'],
'locality' => $adr['locality'],
'zipcode' => $adr['postal-code'],
'region' => $adr['region'],
'country' => $adr['country'],
'zipcode' => $adr['code'],
'region' => $adr['region'],
'country' => $adr['country'],
);
}
}
// photo is stored as separate attachment
if ($record['picture'] && ($att = $record['_attachments'][$record['picture']])) {
$out['photo'] = $att['content'] ? $att['content'] : $this->contactstorage->getAttachment($att['key']);
if ($record['photo'] && strlen($record['photo']) < 255 && ($att = $record['_attachments'][$record['photo']])) {
// only fetch photo content if requested
if ($this->action == 'photo')
$record['photo'] = $att['content'] ? $att['content'] : $this->storagefolder->get_attachment($record['uid'], $att['id']);
}
// truncate publickey value for display
if ($record['pgppublickey'] && $this->action == 'show')
$record['pgppublickey'] = substr($record['pgppublickey'], 0, 140) . '...';
// remove empty fields
return array_filter($out);
return array_filter($record);
}
/**
* Map fields from Roundcube format to internal Kolab_Format
* Map fields from Roundcube format to internal kolab_format_contact properties
*/
private function _from_rcube_contact($contact)
private function _from_rcube_contact($contact, $old = array())
{
$object = array();
if (!$contact['uid'] && $contact['ID'])
$contact['uid'] = $this->_id2uid($contact['ID']);
else if (!$contact['uid'] && $old['uid'])
$contact['uid'] = $old['uid'];
foreach (array_flip($this->fieldmap) as $rcube => $kolab) {
if (isset($contact[$rcube]))
$object[$kolab] = is_array($contact[$rcube]) ? $contact[$rcube][0] : $contact[$rcube];
else if ($values = $this->get_col_values($rcube, $contact, true))
$object[$kolab] = is_array($values) ? $values[0] : $values;
}
// format dates
if ($object['birthday'] && ($date = @strtotime($object['birthday'])))
$object['birthday'] = date('Y-m-d', $date);
if ($object['anniversary'] && ($date = @strtotime($object['anniversary'])))
$object['anniversary'] = date('Y-m-d', $date);
$gendermap = array_flip($this->gender_map);
if (isset($object['gender']))
$object['gender'] = $gendermap[$object['gender']];
$emails = $this->get_col_values('email', $contact, true);
$object['emails'] = join(', ', array_filter($emails));
// overwrite 'email' field
$object['email'] = null;
foreach ($this->get_col_values('phone', $contact) as $type => $values) {
if ($this->phonetypemap[$type])
$type = $this->phonetypemap[$type];
foreach ((array)$values as $phone) {
if (!empty($phone)) {
$object['phone-' . $type] = $phone;
$object['phone'][] = array('number' => $phone, 'type' => $type);
$contact['email'] = array_filter($this->get_col_values('email', $contact, true));
$contact['im'] = array_filter($this->get_col_values('im', $contact, true));
foreach ($this->get_col_values('website', $contact) as $type => $values) {
foreach ((array)$values as $url) {
if (!empty($url)) {
$contact['website'][] = array('url' => $url, 'type' => $type);
}
}
unset($contact['website:'.$type]);
}
$object['address'] = array();
foreach ($this->get_col_values('phone', $contact) as $type => $values) {
foreach ((array)$values as $phone) {
if (!empty($phone)) {
$contact['phone'][] = array('number' => $phone, 'type' => $type);
}
}
unset($contact['phone:'.$type]);
}
$addresses = array();
foreach ($this->get_col_values('address', $contact) as $type => $values) {
if ($this->addresstypemap[$type])
$type = $this->addresstypemap[$type];
$updated = false;
$basekey = 'addr-' . $type . '-';
foreach ((array)$values as $adr) {
// skip empty address
$adr = array_filter($adr);
if (empty($adr))
continue;
// switch type if slot is already taken
if (isset($object[$basekey . 'type'])) {
$type = $type == 'home' ? 'business' : 'home';
$basekey = 'addr-' . $type . '-';
}
if (!isset($object[$basekey . 'type'])) {
$object[$basekey . 'type'] = $type;
$object[$basekey . 'street'] = $adr['street'];
$object[$basekey . 'locality'] = $adr['locality'];
$object[$basekey . 'postal-code'] = $adr['zipcode'];
$object[$basekey . 'region'] = $adr['region'];
$object[$basekey . 'country'] = $adr['country'];
// Update existing address entry of this type
foreach($object['address'] as $index => $address) {
if ($address['type'] == $type) {
$object['address'][$index] = $new_address;
$updated = true;
}
}
}
if (!$updated) {
$object['address'][] = array(
'type' => $type,
'street' => $adr['street'],
'locality' => $adr['locality'],
'postal-code' => $adr['zipcode'],
'region' => $adr['region'],
'country' => $adr['country'],
);
}
$addresses[] = array(
'type' => $type,
'street' => $adr['street'],
'locality' => $adr['locality'],
'code' => $adr['zipcode'],
'region' => $adr['region'],
'country' => $adr['country'],
);
}
unset($contact['address:'.$type]);
}
$contact['address'] = $addresses;
// copy meta data (starting with _) from old object
foreach ((array)$old as $key => $val) {
if (!isset($contact[$key]) && $key[0] == '_')
$contact[$key] = $val;
}
// save new photo as attachment
if ($contact['photo']) {
$attkey = 'photo.attachment';
$object['_attachments'][$attkey] = array(
'type' => rc_image_content_type($contact['photo']),
'content' => preg_match('![^a-z0-9/=+-]!i', $contact['photo']) ? $contact['photo'] : base64_decode($contact['photo']),
);
$object['picture'] = $attkey;
}
return $object;
// add empty values for some fields which can be removed in the UI
return array_filter($contact) + array('nickname' => '', 'birthday' => '', 'anniversary' => '', 'freebusyurl' => '');
}
}

View file

@ -5,7 +5,8 @@ $labels['initials'] = 'Initialen';
$labels['profession'] = 'Berufsbezeichnung';
$labels['officelocation'] = 'Büro Adresse';
$labels['children'] = 'Kinder';
$labels['pgppublickey'] = 'Öffentlicher PGP-Schlüssel';
$labels['pgppublickey'] = 'PGP-Schlüssel';
$labels['pkcs7publickey'] = 'S/MIME-Schlüssel';
$labels['freebusyurl'] = 'Frei/Belegt URL';
$labels['typebusiness'] = 'Dienstlich';
$labels['typebusinessfax'] = 'Dienst';

View file

@ -5,7 +5,8 @@ $labels['initials'] = 'Initialen';
$labels['profession'] = 'Berufsbezeichnung';
$labels['officelocation'] = 'Büro Adresse';
$labels['children'] = 'Kinder';
$labels['pgppublickey'] = 'Öffentlicher PGP-Schlüssel';
$labels['pgppublickey'] = 'PGP-Schlüssel';
$labels['pkcs7publickey'] = 'S/MIME-Schlüssel';
$labels['freebusyurl'] = 'Frei/Belegt URL';
$labels['typebusiness'] = 'Dienstlich';
$labels['typebusinessfax'] = 'Dienst';

View file

@ -5,7 +5,8 @@ $labels['initials'] = 'Initials';
$labels['profession'] = 'Profession';
$labels['officelocation'] = 'Office location';
$labels['children'] = 'Children';
$labels['pgppublickey'] = 'PGP publickey';
$labels['pgppublickey'] = 'PGP public key';
$labels['pkcs7publickey'] = 'S/MIME public key';
$labels['freebusyurl'] = 'Free-busy URL';
$labels['typebusiness'] = 'Business';
$labels['typebusinessfax'] = 'Business Fax';

View file

@ -4,7 +4,7 @@
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd">
<name>kolab_addressbook</name>
<uri>http://git.kolab.org/roundcube-plugins-kolab/</uri>
<uri>http://git.kolab.org/roundcubemail-plugins-kolab/</uri>
<summary>Kolab addressbook</summary>
<description>
Sample plugin to add a new address book source with data from Kolab storage.

View file

@ -0,0 +1,28 @@
#directorylist li.addressbook.readonly,
#directorylist li.addressbook.shared,
#directorylist li.addressbook.other {
/* background-image: url(kolab_folders.png); */
background-position: 5px -1000px;
background-repeat: no-repeat;
}
#directorylist li.addressbook.readonly {
background-position: 5px 0px;
}
#directorylist li.addressbook.shared {
background-position: 5px -54px;
}
#directorylist li.addressbook.shared.readonly {
background-position: 5px -72px;
}
#directorylist li.addressbook.other {
background-position: 5px -18px;
}
#directorylist li.addressbook.other.readonly {
background-position: 5px -36px;
}

View file

@ -0,0 +1,24 @@
<roundcube:object name="doctype" value="html5" />
<html>
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
</head>
<body class="iframe">
<h1 class="boxtitle"><roundcube:label name="kolab_addressbook.bookproperties" /></h1>
<div class="boxcontent">
<roundcube:object name="bookdetails" class="propform" />
</div>
<div id="formfooter">
<div class="footerleft formbuttons">
<roundcube:button command="book-save" type="input" class="button mainaction" label="save" />
</div>
</div>
<roundcube:include file="/includes/footer.html" />
</body>
</html>

View file

@ -4,7 +4,7 @@
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd">
<name>kolab_auth</name>
<uri>http://git.kolab.org/roundcube-plugins-kolab/</uri>
<uri>http://git.kolab.org/roundcubemail-plugins-kolab/</uri>
<summary>Kolab Authentication</summary>
<description>
Authenticates on LDAP server, finds canonized authentication ID for IMAP

View file

@ -61,16 +61,9 @@ class kolab_config extends rcube_plugin
if ($this->config)
return;
$this->require_plugin('kolab_folders');
return; // CURRENTLY DISABLED until libkolabxml has support for config objects
// load dependencies
require_once 'Horde/Util.php';
require_once 'Horde/Kolab/Format.php';
require_once 'Horde/Kolab/Format/XML.php';
require_once $this->home . '/lib/configuration.php';
require_once $this->home . '/lib/kolab_configuration.php';
String::setDefaultCharset('UTF-8');
$this->require_plugin('libkolab');
$this->config = new kolab_configuration();

View file

@ -4,7 +4,7 @@
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd">
<name>kolab_config</name>
<uri>http://git.kolab.org/roundcube-plugins-kolab/</uri>
<uri>http://git.kolab.org/roundcubemail-plugins-kolab/</uri>
<summary>Kolab configuration storage</summary>
<description>
Plugin to use Kolab server as a configuration storage. Provides an API to handle

View file

@ -4,7 +4,7 @@
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd">
<name>kolab_core</name>
<uri>http://git.kolab.org/roundcube-plugins-kolab/</uri>
<uri>http://git.kolab.org/roundcubemail-plugins-kolab/</uri>
<summary>Kolab API</summary>
<description>
Plugin to setup a basic environment for interaction with a Kolab server.

View file

@ -30,8 +30,6 @@ class kolab_folders extends rcube_plugin
public $mail_types = array('inbox', 'drafts', 'sentitems', 'outbox', 'wastebasket', 'junkemail');
private $rc;
const CTYPE_KEY = '/shared/vendor/kolab/folder-type';
/**
* Plugin initialization.
@ -40,6 +38,9 @@ class kolab_folders extends rcube_plugin
{
$this->rc = rcmail::get_instance();
// load required plugin
$this->require_plugin('libkolab');
// Folder listing hooks
$this->add_hook('storage_folders', array($this, 'mailboxes_list'));
@ -57,68 +58,32 @@ class kolab_folders extends rcube_plugin
*/
function mailboxes_list($args)
{
// infinite loop prevention
if ($this->is_processing) {
return $args;
}
if (!$this->metadata_support()) {
return $args;
}
$filter = $args['filter'];
$this->is_processing = true;
// all-folders request, use core method
if (!$filter) {
// get folders
$folders = kolab_storage::list_folders($args['root'], $args['name'], $args['filter'], $args['mode'] == 'LSUB');
$this->is_processing = false;
if (!is_array($folders)) {
return $args;
}
// get folders types
$folderdata = $this->get_folder_type_list($args['root'].$args['name'], true);
if (!is_array($folderdata)) {
return $args;
// Create default folders
if ($args['root'] == '' && $args['name'] = '*') {
$this->create_default_folders($folders, $args['filter']);
}
$regexp = '/^' . preg_quote($filter, '/') . '(\..+)?$/';
// In some conditions we can skip LIST command (?)
if ($args['mode'] == 'LIST' && $filter != 'mail'
&& $args['root'] == '' && $args['name'] == '*'
) {
foreach ($folderdata as $folder => $type) {
if (!preg_match($regexp, $type)) {
unset($folderdata[$folder]);
}
}
$args['folders'] = array_keys($folderdata);
return $args;
}
$storage = $this->rc->get_storage();
// Get folders list
if ($args['mode'] == 'LIST') {
if (!$storage->check_connection()) {
return $args;
}
$args['folders'] = $storage->conn->listMailboxes($args['root'], $args['name']);
}
else {
$args['folders'] = $this->list_subscribed($args['root'], $args['name']);
}
// In case of an error, return empty list
if (!is_array($args['folders'])) {
$args['folders'] = array();
return $args;
}
// Filter folders list
foreach ($args['folders'] as $idx => $folder) {
$type = $folderdata[$folder];
if ($filter == 'mail' && empty($type)) {
continue;
}
if (empty($type) || !preg_match($regexp, $type)) {
unset($args['folders'][$idx]);
}
}
$args['folders'] = $folders;
return $args;
}
@ -132,10 +97,11 @@ class kolab_folders extends rcube_plugin
return $args;
}
$table = $args['table'];
$table = $args['table'];
$storage = $this->rc->get_storage();
// get folders types
$folderdata = $this->get_folder_type_list('*');
$folderdata = $storage->get_metadata('*', kolab_storage::CTYPE_KEY);
if (!is_array($folderdata)) {
return $args;
@ -146,7 +112,7 @@ class kolab_folders extends rcube_plugin
for ($i=1, $cnt=$table->size(); $i<=$cnt; $i++) {
$attrib = $table->get_row_attribs($i);
$folder = $attrib['foldername']; // UTF7-IMAP
$type = $folderdata[$folder];
$type = !empty($folderdata[$folder]) ? $folderdata[$folder][kolab_storage::CTYPE_KEY] : null;
if (!$type)
$type = 'mail';
@ -266,8 +232,6 @@ class kolab_folders extends rcube_plugin
{
// Folder actions from folders list
if (empty($args['record'])) {
// Just clear Horde folders cache and return
$this->clear_folders_cache();
return $args;
}
@ -340,11 +304,6 @@ class kolab_folders extends rcube_plugin
}
}
// Clear Horde folders cache
if ($result) {
$this->clear_folders_cache();
}
$args['record']['class'] = self::folder_class_name($ctype);
$args['record']['subscribe'] = $subscribe;
$args['result'] = $result;
@ -355,7 +314,7 @@ class kolab_folders extends rcube_plugin
/**
* Checks if IMAP server supports any of METADATA, ANNOTATEMORE, ANNOTATEMORE2
*
* @return boolean
* @return boolean
*/
function metadata_support()
{
@ -376,9 +335,9 @@ class kolab_folders extends rcube_plugin
function get_folder_type($folder)
{
$storage = $this->rc->get_storage();
$folderdata = $storage->get_metadata($folder, array(kolab_folders::CTYPE_KEY));
$folderdata = $storage->get_metadata($folder, kolab_storage::CTYPE_KEY);
return explode('.', $folderdata[$folder][kolab_folders::CTYPE_KEY]);
return explode('.', $folderdata[$folder][kolab_storage::CTYPE_KEY]);
}
/**
@ -393,112 +352,7 @@ class kolab_folders extends rcube_plugin
{
$storage = $this->rc->get_storage();
return $storage->set_metadata($folder, array(kolab_folders::CTYPE_KEY => $type));
}
/**
* Returns list of subscribed folders (directly from IMAP server)
*
* @param string $root Optional root folder
* @param string $name Optional name pattern
*
* @return array List of mailboxes/folders
*/
private function list_subscribed($root='', $name='*')
{
$storage = $this->rc->get_storage();
if (!$storage->check_connection()) {
return null;
}
// Code copied from rcube_imap::_list_mailboxes()
// Server supports LIST-EXTENDED, we can use selection options
// #1486225: Some dovecot versions returns wrong result using LIST-EXTENDED
if (!$this->rc->config->get('imap_force_lsub') && $imap->get_capability('LIST-EXTENDED')) {
// This will also set mailbox options, LSUB doesn't do that
$a_folders = $storage->conn->listMailboxes($root, $name,
NULL, array('SUBSCRIBED'));
// remove non-existent folders
if (is_array($a_folders) && $name = '*' && !empty($storage->conn->data['LIST'])) {
foreach ($a_folders as $idx => $folder) {
if (($opts = $storage->conn->data['LIST'][$folder])
&& in_array('\\NonExistent', $opts)
) {
$storage->conn->unsubscribe($folder);
unset($a_folders[$idx]);
}
}
}
}
// retrieve list of folders from IMAP server using LSUB
else {
$a_folders = $storage->conn->listSubscribed($root, $name);
// unsubscribe non-existent folders, remove from the list
if (is_array($a_folders) && $name == '*' && !empty($storage->conn->data['LIST'])) {
foreach ($a_folders as $idx => $folder) {
if (!isset($storage->conn->data['LIST'][$folder])
|| in_array('\\Noselect', $storage->conn->data['LIST'][$folder])
) {
// Some servers returns \Noselect for existing folders
if (!$storage->folder_exists($folder)) {
$storage->conn->unsubscribe($folder);
unset($a_folders[$idx]);
}
}
}
}
}
return $a_folders;
}
/**
* Returns list of folder(s) type(s)
*
* @param string $mbox Folder name or pattern
* @param bool $defaults Enables creation of configured default folders
*
* @return array List of folders data, indexed by folder name
*/
function get_folder_type_list($mbox, $create_defaults = false)
{
$storage = $this->rc->get_storage();
// Use mailboxes. prefix so the cache will be cleared by core
// together with other mailboxes-related cache data
$cache_key = 'mailboxes.folder-type.'.$mbox;
// get cached metadata
$metadata = $storage->get_cache($cache_key);
if (!is_array($metadata)) {
$metadata = $storage->get_metadata($mbox, kolab_folders::CTYPE_KEY);
$need_update = true;
}
if (!is_array($metadata)) {
return false;
}
// make the result more flat
if ($need_update) {
$metadata = array_map('implode', $metadata);
}
// create default folders if needed
if ($create_defaults) {
$this->create_default_folders($metadata, $cache_key);
}
// write mailboxlist to cache
if ($need_update) {
$storage->update_cache($cache_key, $metadata);
}
return $metadata;
return $storage->set_metadata($folder, array(kolab_storage::CTYPE_KEY => $type));
}
/**
@ -511,7 +365,7 @@ class kolab_folders extends rcube_plugin
function get_default_folder($type)
{
$storage = $this->rc->get_storage();
$folderdata = $this->get_folder_type_list('*');
$folderdata = $storage->get_metadata('*', kolab_storage::CTYPE_KEY);
if (!is_array($folderdata)) {
return null;
@ -521,7 +375,8 @@ class kolab_folders extends rcube_plugin
$namespace = $storage->get_namespace();
// get all folders of specified type
$folderdata = array_intersect($folderdata, array($type));
$folderdata = array_map('implode', $folderdata);
$folderdata = array_intersect($folderdata, array($type));
unset($folders[0]);
foreach ($folderdata as $folder => $data) {
@ -562,24 +417,24 @@ class kolab_folders extends rcube_plugin
return implode(' ', $class);
}
/**
* Clear Horde's folder cache. See Kolab_List::singleton().
*/
private function clear_folders_cache()
{
unset($_SESSION['horde_session_objects']['kolab_folderlist']);
}
/**
* Creates default folders if they doesn't exist
*/
private function create_default_folders(&$folderdata, $cache_key = null)
private function create_default_folders(&$folders, $filter)
{
$storage = $this->rc->get_storage();
$namespace = $storage->get_namespace();
$folderdata = $storage->get_metadata('*', kolab_storage::CTYPE_KEY);
$defaults = array();
$need_update = false;
if (!is_array($folderdata)) {
return;
}
// "Flattenize" metadata array to become a name->type hash
$folderdata = array_map('implode', $folderdata);
// Find personal namespace prefix
if (is_array($namespace['personal']) && count($namespace['personal']) == 1) {
$prefix = $namespace['personal'][0][0];
@ -621,7 +476,7 @@ class kolab_folders extends rcube_plugin
}
// get all folders of specified type
$folders = array_intersect($folderdata, array($type));
$folders = array_intersect($folderdata, array($type));
unset($folders[0]);
// find folders in personal namespace
@ -653,16 +508,10 @@ class kolab_folders extends rcube_plugin
$result = $this->set_folder_type($foldername, $type);
// add new folder to the result
if ($result) {
$folderdata[$foldername] = $type;
$need_update = true;
if ($result && (!$filter || $filter == $type1)) {
$folders[] = $foldername;
}
}
// update cache
if ($need_update && $cache_key) {
$storage->update_cache($cache_key, $folderdata);
}
}
}

View file

@ -4,7 +4,7 @@
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd">
<name>kolab_folders</name>
<uri>http://git.kolab.org/roundcube-plugins-kolab/</uri>
<uri>http://git.kolab.org/roundcubemail-plugins-kolab/</uri>
<summary>Type-aware folder management/listing for Kolab</summary>
<description>
The plugin extends folders handling with features of the Kolab Suite
@ -21,10 +21,10 @@
<email>machniak@kolabsys.com</email>
<active>yes</active>
</lead>
<date>2011-11-01</date>
<date>2012-05-14</date>
<version>
<release>1.0</release>
<api>1.0</api>
<release>2.0</release>
<api>2.0</api>
</version>
<stability>
<release>stable</release>

View file

@ -26,7 +26,7 @@ class kolab_zpush extends rcube_plugin
{
public $task = 'settings';
public $urlbase;
private $rc;
private $ui;
private $cache;
@ -34,7 +34,7 @@ class kolab_zpush extends rcube_plugin
private $folders;
private $folders_meta;
private $root_meta;
const ROOT_MAILBOX = 'INBOX';
const CTYPE_KEY = '/shared/vendor/kolab/folder-type';
const ACTIVESYNC_KEY = '/private/vendor/kolab/activesync';
@ -45,17 +45,18 @@ class kolab_zpush extends rcube_plugin
public function init()
{
$this->rc = rcmail::get_instance();
$this->require_plugin('jqueryui');
$this->add_texts('localization/', true);
$this->include_script('kolab_zpush.js');
$this->register_action('plugin.zpushconfig', array($this, 'config_view'));
$this->register_action('plugin.zpushjson', array($this, 'json_command'));
if ($this->rc->action == 'plugin.zpushconfig')
$this->require_plugin('kolab_core');
if ($this->rc->action == 'plugin.zpushconfig') {
$this->require_plugin('libkolab');
}
}
@ -66,6 +67,8 @@ class kolab_zpush extends rcube_plugin
{
$storage = $this->rc->get_storage();
// @TODO: Metadata is already cached by rcube storage, get rid of cache here
$this->cache = $this->rc->get_cache('zpush', 'db', 900);
$this->cache->expunge();
@ -120,7 +123,7 @@ class kolab_zpush extends rcube_plugin
$laxpic = intval(get_input_value('laxpic', RCUBE_INPUT_POST));
$subsciptions = get_input_value('subscribed', RCUBE_INPUT_POST);
$err = false;
if ($device = $devices[$imei]) {
// update device config if changed
if ($devicealias != $this->root_meta['DEVICE'][$imei]['ALIAS'] ||
@ -146,12 +149,12 @@ class kolab_zpush extends rcube_plugin
// skip root folder (already handled above)
if ($folder == self::ROOT_MAILBOX)
continue;
if ($subsciptions[$folder] != $meta[$imei]['S']) {
$meta[$imei]['S'] = intval($subsciptions[$folder]);
$this->folders_meta[$folder] = $meta;
unset($meta['TYPE']);
// read metadata first
$folderdata = $storage->get_metadata($folder, array(self::ACTIVESYNC_KEY));
if ($asyncdata = $folderdata[$folder][self::ACTIVESYNC_KEY])
@ -161,25 +164,24 @@ class kolab_zpush extends rcube_plugin
$err |= !$storage->set_metadata($folder, array(self::ACTIVESYNC_KEY => $this->serialize_metadata($metadata)));
}
}
// update cache
$this->cache->remove('folders');
$this->cache->write('folders', $this->folders_meta);
$this->rc->output->command('plugin.zpush_save_complete', array('success' => !$err, 'id' => $imei, 'devicealias' => Q($devicealias)));
}
if ($err)
$this->rc->output->show_message($this->gettext('savingerror'), 'error');
else
$this->rc->output->show_message($this->gettext('successfullysaved'), 'confirmation');
break;
case 'delete':
$this->init_imap();
$devices = $this->list_devices();
if ($device = $devices[$imei]) {
unset($this->root_meta['DEVICE'][$imei], $this->root_meta['FOLDER'][$imei]);
@ -237,20 +239,20 @@ class kolab_zpush extends rcube_plugin
public function config_view()
{
require_once $this->home . '/kolab_zpush_ui.php';
$storage = $this->rc->get_storage();
// checks if IMAP server supports any of METADATA, ANNOTATEMORE, ANNOTATEMORE2
if (!($storage->get_capability('METADATA') || $storage->get_capability('ANNOTATEMORE') || $storage->get_capability('ANNOTATEMORE2'))) {
$this->rc->output->show_message($this->gettext('notsupported'), 'error');
}
$this->ui = new kolab_zpush_ui($this);
$this->register_handler('plugin.devicelist', array($this->ui, 'device_list'));
$this->register_handler('plugin.deviceconfigform', array($this->ui, 'device_config_form'));
$this->register_handler('plugin.foldersubscriptions', array($this->ui, 'folder_subscriptions'));
$this->rc->output->set_env('devicecount', count($this->list_devices()));
$this->rc->output->send('kolab_zpush.config');
}
@ -264,9 +266,10 @@ class kolab_zpush extends rcube_plugin
public function list_devices()
{
if (!isset($this->devices)) {
$this->init_imap();
$this->devices = (array)$this->root_meta['DEVICE'];
}
return $this->devices;
}
@ -299,7 +302,7 @@ class kolab_zpush extends rcube_plugin
}
$this->folders_meta[$folder]['TYPE'] = !empty($foldertype[0]) ? $foldertype[0] : 'mail';
}
// cache it!
$this->cache->write('folders', $this->folders_meta);
}
@ -317,7 +320,7 @@ class kolab_zpush extends rcube_plugin
{
if (!isset($this->folders_meta))
$this->list_folders();
return $this->folders_meta;
}

View file

@ -67,18 +67,18 @@ class kolab_zpush_ui
$input = new html_inputfield(array('name' => 'devicealias', 'id' => $field_id, 'size' => 40));
$table->add('title', html::label($field_id, $this->config->gettext('devicealias')));
$table->add(null, $input->show());
$field_id = 'config-device-mode';
$select = new html_select(array('name' => 'syncmode', 'id' => $field_id));
$select->add(array($this->config->gettext('modeauto'), $this->config->gettext('modeflat'), $this->config->gettext('modefolder')), array('-1', '0', '1'));
$table->add('title', html::label($field_id, $this->config->gettext('syncmode')));
$table->add(null, $select->show('-1'));
$field_id = 'config-device-laxpic';
$checkbox = new html_checkbox(array('name' => 'laxpic', 'value' => '1', 'id' => $field_id));
$table->add('title', $this->config->gettext('imageformat'));
$table->add(null, html::label($field_id, $checkbox->show() . ' ' . $this->config->gettext('laxpiclabel')));
if ($attrib['form'])
$this->rc->output->add_gui_object('editform', $attrib['form']);
@ -90,7 +90,7 @@ class kolab_zpush_ui
{
if (!$attrib['id'])
$attrib['id'] = 'foldersubscriptions';
// group folders by type (show only known types)
$folder_groups = array('mail' => array(), 'contact' => array(), 'event' => array(), 'task' => array());
$folder_meta = $this->config->folders_meta();
@ -99,7 +99,7 @@ class kolab_zpush_ui
if (is_array($folder_groups[$type]))
$folder_groups[$type][] = $folder;
}
// build block for every folder type
foreach ($folder_groups as $type => $group) {
if (empty($group))
@ -111,14 +111,14 @@ class kolab_zpush_ui
}
$this->rc->output->add_gui_object('subscriptionslist', $attrib['id']);
return html::div($attrib, $html);
}
public function folder_subscriptions_block($a_folders, $attrib)
{
$alarms = ($attrib['type'] == 'event' || $attrib['type'] == 'task');
$table = new html_table(array('cellspacing' => 0));
$table->add_header('subscription', $attrib['syncicon'] ? html::img(array('src' => $this->skin_path . $attrib['syncicon'], 'title' => $this->config->gettext('synchronize'))) : '');
$table->add_header('alarm', $alarms && $attrib['alarmicon'] ? html::img(array('src' => $this->skin_path . $attrib['alarmicon'], 'title' => $this->config->gettext('withalarms'))) : '');
@ -129,7 +129,7 @@ class kolab_zpush_ui
$names = array();
foreach ($a_folders as $folder) {
$foldername = $origname = preg_replace('/^INBOX &raquo;\s+/', '', rcube_kolab::object_name($folder));
$foldername = $origname = preg_replace('/^INBOX &raquo;\s+/', '', kolab_storage::object_name($folder));
// find folder prefix to truncate (the same code as in kolab_addressbook plugin)
for ($i = count($names)-1; $i >= 0; $i--) {
@ -161,7 +161,7 @@ class kolab_zpush_ui
$table->add('alarm', $checkbox_alarm->show('', array('value' => $folder, 'id' => $folder_id.'_alarm')));
else
$table->add('alarm', '');
$table->add(join(' ', $classes), html::label($folder_id, $padding . Q($foldername)));
}

View file

@ -4,7 +4,7 @@
http://pear.php.net/dtd/package-2.0
http://pear.php.net/dtd/package-2.0.xsd">
<name>kolab_zpush</name>
<uri>http://git.kolab.org/roundcube-plugins-kolab/</uri>
<uri>http://git.kolab.org/roundcubemail-plugins-kolab/</uri>
<summary>Z-Push configuration utility for Kolab accounts</summary>
<description></description>
<lead>
@ -13,10 +13,10 @@
<email>bruederli@kolabsys.com</email>
<active>yes</active>
</lead>
<date>2011-11-14</date>
<time>12:12:00</time>
<date>2012-05-14</date>
<version>
<release>0.3</release>
<release>1.0</release>
<api>1.0</api>
</version>
<stability>
<release>stable</release>
@ -29,6 +29,7 @@
<file name="kolab_zpush_ui.php" role="php"></file>
<file name="kolab_zpush.js" role="data"></file>
<file name="localization/de_CH.inc" role="data"></file>
<file name="localization/de_DE.inc" role="data"></file>
<file name="localization/en_US.inc" role="data"></file>
<file name="localization/pl_PL.inc" role="data"></file>
<file name="skins/default/templates/config.html" role="data"></file>

43
plugins/libkolab/README Normal file
View file

@ -0,0 +1,43 @@
libkolab plugin to access to Kolab groupware data
=================================================
The contained library classes establish a connection to the Kolab server
and manage the access to the Kolab groupware objects stored in various
IMAP folders. For reading and writing these objects, the PHP bindings of
the libkolabxml library are used.
REQUIREMENTS
------------
* libkolabxml PHP bindings
- kolabformat.so loaded into PHP
- kolabformat.php placed somewhere in the include_path
* PEAR: HTTP/Request2
* PEAR: Net/URL2
* Optional for old format support:
Horde Kolab_Format package and all of its dependencies
which are at least Horde_(Browser,DOM,NLS,String,Utils)
INSTALLATION
------------
To use local cache you need to create a dedicated table in Roundcube's database.
To do so, execute the SQL commands in SQL/<yourdatabase>.sql
CONFIGURATION
-------------
The following options can be configured in Roundcube's main config file
or a local config file (config.inc.php) located in the plugin folder.
// Enable caching of Kolab objects in local database
$rcmail_config['kolab_cache'] = true;
// Optional override of the URL to read and trigger Free/Busy information of Kolab users
// Defaults to https://<imap-server->/freebusy
$rcmail_config['kolab_freebusy_server'] = 'https://<some-host>/<freebusy-path>';
// Set this option to disable SSL certificate checks when triggering Free/Busy (enabled by default)
$rcmail_config['kolab_ssl_verify_peer'] = false;

View file

@ -0,0 +1,22 @@
/**
* libkolab database schema
*
* @version @package_version@
* @author Thomas Bruederli
* @licence GNU AGPL
**/
CREATE TABLE IF NOT EXISTS `kolab_cache` (
`resource` VARCHAR(255) CHARACTER SET ascii NOT NULL,
`type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
`msguid` BIGINT UNSIGNED NOT NULL,
`uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
`created` DATETIME DEFAULT NULL,
`data` TEXT NOT NULL,
`xml` TEXT NOT NULL,
`dtstart` DATETIME,
`dtend` DATETIME,
`tags` VARCHAR(255) NOT NULL,
`words` TEXT NOT NULL,
PRIMARY KEY(`resource`,`type`,`msguid`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;

View file

@ -0,0 +1,289 @@
<?php
/**
* Kolab format model class wrapping libkolabxml bindings
*
* Abstract base class for different Kolab groupware objects read from/written
* to the new Kolab 3 format using the PHP bindings of libkolabxml.
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
abstract class kolab_format
{
public static $timezone;
protected $obj;
protected $data;
protected $xmldata;
protected $loaded = false;
/**
* Factory method to instantiate a kolab_format object of the given type
*
* @param string Object type to instantiate
* @param string Cached xml data to initialize with
* @return object kolab_format
*/
public static function factory($type, $xmldata = null)
{
if (!isset(self::$timezone))
self::$timezone = new DateTimeZone('UTC');
$suffix = preg_replace('/[^a-z]+/', '', $type);
$classname = 'kolab_format_' . $suffix;
if (class_exists($classname))
return new $classname($xmldata);
return PEAR::raiseError(sprintf("Failed to load Kolab Format wrapper for type %s", $type));
}
/**
* Convert the given date/time value into a cDateTime object
*
* @param mixed Date/Time value either as unix timestamp, date string or PHP DateTime object
* @param DateTimeZone The timezone the date/time is in. Use global default if empty
* @param boolean True of the given date has no time component
* @return object The libkolabxml date/time object
*/
public static function get_datetime($datetime, $tz = null, $dateonly = false)
{
if (!$tz) $tz = self::$timezone;
$result = new cDateTime();
// got a unix timestamp (in UTC)
if (is_numeric($datetime)) {
$datetime = new DateTime('@'.$datetime, new DateTimeZone('UTC'));
if ($tz) $datetime->setTimezone($tz);
}
else if (is_string($datetime) && strlen($datetime))
$datetime = new DateTime($datetime, $tz);
if (is_a($datetime, 'DateTime')) {
$result->setDate($datetime->format('Y'), $datetime->format('n'), $datetime->format('j'));
if (!$dateonly)
$result->setTime($datetime->format('G'), $datetime->format('i'), $datetime->format('s'));
if ($tz && $tz->getName() == 'UTC')
$result->setUTC(true);
else if ($tz)
$result->setTimezone($tz->getName());
}
return $result;
}
/**
* Convert the given cDateTime into a PHP DateTime object
*
* @param object cDateTime The libkolabxml datetime object
* @return object DateTime PHP datetime instance
*/
public static function php_datetime($cdt)
{
if (!is_object($cdt) || !$cdt->isValid())
return null;
$d = new DateTime;
$d->setTimezone(self::$timezone);
try {
if ($tzs = $cdt->timezone()) {
$tz = new DateTimeZone($tzs);
$d->setTimezone($tz);
}
else if ($cdt->isUTC()) {
$d->setTimezone(new DateTimeZone('UTC'));
}
}
catch (Exception $e) { }
$d->setDate($cdt->year(), $cdt->month(), $cdt->day());
if ($cdt->isDateOnly()) {
$d->_dateonly = true;
$d->setTime(12, 0, 0); // set time to noon to avoid timezone troubles
}
else {
$d->setTime($cdt->hour(), $cdt->minute(), $cdt->second());
}
return $d;
}
/**
* Convert a libkolabxml vector to a PHP array
*
* @param object vector Object
* @return array Indexed array contaning vector elements
*/
public static function vector2array($vec, $max = PHP_INT_MAX)
{
$arr = array();
for ($i=0; $i < $vec->size() && $i < $max; $i++)
$arr[] = $vec->get($i);
return $arr;
}
/**
* Build a libkolabxml vector (string) from a PHP array
*
* @param array Array with vector elements
* @return object vectors
*/
public static function array2vector($arr)
{
$vec = new vectors;
foreach ((array)$arr as $val) {
if (strlen($val))
$vec->push($val);
}
return $vec;
}
/**
* Check for format errors after calling kolabformat::write*()
*
* @return boolean True if there were errors, False if OK
*/
protected function format_errors()
{
$ret = $log = false;
switch (kolabformat::error()) {
case kolabformat.NoError:
$ret = false;
break;
case kolabformat.Warning:
$ret = false;
$log = "Warning";
break;
default:
$ret = true;
$log = "Error";
}
if ($log) {
raise_error(array(
'code' => 660,
'type' => 'php',
'file' => __FILE__,
'line' => __LINE__,
'message' => "kolabformat write $log: " . kolabformat::errorMessage(),
), true);
}
return $ret;
}
/**
* Save the last generated UID to the object properties.
* Should be called after kolabformat::writeXXXX();
*/
protected function update_uid()
{
// get generated UID
if (!$this->data['uid']) {
$this->data['uid'] = kolabformat::getSerializedUID();
$this->obj->setUid($this->data['uid']);
}
}
/**
* Initialize libkolabxml object with cached xml data
*/
protected function init()
{
if (!$this->loaded) {
if ($this->xmldata) {
$this->load($this->xmldata);
$this->xmldata = null;
}
$this->loaded = true;
}
}
/**
* Direct getter for object properties
*/
public function __get($var)
{
return $this->data[$var];
}
/**
* Load Kolab object data from the given XML block
*
* @param string XML data
*/
abstract public function load($xml);
/**
* Set properties to the kolabformat object
*
* @param array Object data as hash array
*/
abstract public function set(&$object);
/**
*
*/
abstract public function is_valid();
/**
* Write object data to XML format
*
* @return string XML data
*/
abstract public function write();
/**
* Convert the Kolab object into a hash array data structure
*
* @return array Kolab object data as hash array
*/
abstract public function to_array();
/**
* Load object data from Kolab2 format
*
* @param array Hash array with object properties (produced by Horde Kolab_Format classes)
*/
abstract public function fromkolab2($object);
/**
* Callback for kolab_storage_cache to get object specific tags to cache
*
* @return array List of tags to save in cache
*/
public function get_tags()
{
return array();
}
/**
* Callback for kolab_storage_cache to get words to index for fulltext search
*
* @return array List of words to save in cache
*/
public function get_words()
{
return array();
}
}

View file

@ -0,0 +1,532 @@
<?php
/**
* Kolab Contact model class
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class kolab_format_contact extends kolab_format
{
public $CTYPE = 'application/vcard+xml';
public static $fulltext_cols = array('name', 'firstname', 'surname', 'middlename', 'email');
public $phonetypes = array(
'home' => Telephone::Home,
'work' => Telephone::Work,
'text' => Telephone::Text,
'main' => Telephone::Voice,
'homefax' => Telephone::Fax,
'workfax' => Telephone::Fax,
'mobile' => Telephone::Cell,
'video' => Telephone::Video,
'pager' => Telephone::Pager,
'car' => Telephone::Car,
'other' => Telephone::Textphone,
);
public $addresstypes = array(
'home' => Address::Home,
'work' => Address::Work,
'office' => 0,
);
private $gendermap = array(
'female' => Contact::Female,
'male' => Contact::Male,
);
private $relatedmap = array(
'manager' => Related::Manager,
'assistant' => Related::Assistant,
'spouse' => Related::Spouse,
'children' => Related::Child,
);
// old Kolab 2 format field map
private $kolab2_fieldmap = array(
// kolab => roundcube
'full-name' => 'name',
'given-name' => 'firstname',
'middle-names' => 'middlename',
'last-name' => 'surname',
'prefix' => 'prefix',
'suffix' => 'suffix',
'nick-name' => 'nickname',
'organization' => 'organization',
'department' => 'department',
'job-title' => 'jobtitle',
'birthday' => 'birthday',
'anniversary' => 'anniversary',
'phone' => 'phone',
'im-address' => 'im',
'web-page' => 'website',
'profession' => 'profession',
'manager-name' => 'manager',
'assistant' => 'assistant',
'spouse-name' => 'spouse',
'children' => 'children',
'body' => 'notes',
'pgp-publickey' => 'pgppublickey',
'free-busy-url' => 'freebusyurl',
'picture' => 'photo',
);
private $kolab2_phonetypes = array(
'home1' => 'home',
'business1' => 'work',
'business2' => 'work',
'businessfax' => 'workfax',
);
private $kolab2_addresstypes = array(
'business' => 'work'
);
private $kolab2_gender = array(0 => 'male', 1 => 'female');
/**
* Default constructor
*/
function __construct($xmldata = null)
{
$this->obj = new Contact;
$this->xmldata = $xmldata;
// complete phone types
$this->phonetypes['homefax'] |= Telephone::Home;
$this->phonetypes['workfax'] |= Telephone::Work;
}
/**
* Load Contact object data from the given XML block
*
* @param string XML data
*/
public function load($xml)
{
$this->obj = kolabformat::readContact($xml, false);
$this->loaded = true;
}
/**
* Write Contact object data to XML format
*
* @return string XML data
*/
public function write()
{
$this->init();
$this->xmldata = kolabformat::writeContact($this->obj);
if (!parent::format_errors())
parent::update_uid();
else
$this->xmldata = null;
return $this->xmldata;
}
/**
* Set contact properties to the kolabformat object
*
* @param array Contact data as hash array
*/
public function set(&$object)
{
$this->init();
// set some automatic values if missing
if (false && !$this->obj->created()) {
if (!empty($object['created']))
$object['created'] = new DateTime('now', self::$timezone);
$this->obj->setCreated(self::get_datetime($object['created']));
}
if (!empty($object['uid']))
$this->obj->setUid($object['uid']);
// do the hard work of setting object values
$nc = new NameComponents;
$nc->setSurnames(self::array2vector($object['surname']));
$nc->setGiven(self::array2vector($object['firstname']));
$nc->setAdditional(self::array2vector($object['middlename']));
$nc->setPrefixes(self::array2vector($object['prefix']));
$nc->setSuffixes(self::array2vector($object['suffix']));
$this->obj->setNameComponents($nc);
$this->obj->setName($object['name']);
if (isset($object['nickname']))
$this->obj->setNickNames(self::array2vector($object['nickname']));
if (isset($object['profession']))
$this->obj->setTitles(self::array2vector($object['profession']));
// organisation related properties (affiliation)
$org = new Affiliation;
$offices = new vectoraddress;
if ($object['organization'])
$org->setOrganisation($object['organization']);
if ($object['department'])
$org->setOrganisationalUnits(self::array2vector($object['department']));
if ($object['jobtitle'])
$org->setRoles(self::array2vector($object['jobtitle']));
$rels = new vectorrelated;
if ($object['manager']) {
foreach ((array)$object['manager'] as $manager)
$rels->push(new Related(Related::Text, $manager, Related::Manager));
}
if ($object['assistant']) {
foreach ((array)$object['assistant'] as $assistant)
$rels->push(new Related(Related::Text, $assistant, Related::Assistant));
}
$org->setRelateds($rels);
// email, im, url
$this->obj->setEmailAddresses(self::array2vector($object['email']));
$this->obj->setIMaddresses(self::array2vector($object['im']));
$vurls = new vectorurl;
foreach ((array)$object['website'] as $url) {
$type = $url['type'] == 'blog' ? Url::Blog : Url::None;
$vurls->push(new Url($url['url'], $type));
}
$this->obj->setUrls($vurls);
// addresses
$adrs = new vectoraddress;
foreach ((array)$object['address'] as $address) {
$adr = new Address;
$type = $this->addresstypes[$address['type']];
if (isset($type))
$adr->setTypes($type);
else if ($address['type'])
$adr->setLabel($address['type']);
if ($address['street'])
$adr->setStreet($address['street']);
if ($address['locality'])
$adr->setLocality($address['locality']);
if ($address['code'])
$adr->setCode($address['code']);
if ($address['region'])
$adr->setRegion($address['region']);
if ($address['country'])
$adr->setCountry($address['country']);
if ($address['type'] == 'office')
$offices->push($adr);
else
$adrs->push($adr);
}
$this->obj->setAddresses($adrs);
$org->setAddresses($offices);
// add org affiliation after addresses are set
$orgs = new vectoraffiliation;
$orgs->push($org);
$this->obj->setAffiliations($orgs);
// telephones
$tels = new vectortelephone;
foreach ((array)$object['phone'] as $phone) {
$tel = new Telephone;
if (isset($this->phonetypes[$phone['type']]))
$tel->setTypes($this->phonetypes[$phone['type']]);
$tel->setNumber($phone['number']);
$tels->push($tel);
}
$this->obj->setTelephones($tels);
if (isset($object['gender']))
$this->obj->setGender($this->gendermap[$object['gender']] ? $this->gendermap[$object['gender']] : Contact::NotSet);
if (isset($object['notes']))
$this->obj->setNote($object['notes']);
if (isset($object['freebusyurl']))
$this->obj->setFreeBusyUrl($object['freebusyurl']);
if (isset($object['birthday']))
$this->obj->setBDay(self::get_datetime($object['birthday'], null, true));
if (isset($object['anniversary']))
$this->obj->setAnniversary(self::get_datetime($object['anniversary'], null, true));
if (!empty($object['photo'])) {
if ($type = rc_image_content_type($object['photo']))
$this->obj->setPhoto($object['photo'], $type);
}
else if (isset($object['photo']))
$this->obj->setPhoto('','');
else if ($this->obj->photoMimetype()) // load saved photo for caching
$object['photo'] = $this->obj->photo();
// spouse and children are relateds
$rels = new vectorrelated;
if ($object['spouse']) {
$rels->push(new Related(Related::Text, $object['spouse'], Related::Spouse));
}
if ($object['children']) {
foreach ((array)$object['children'] as $child)
$rels->push(new Related(Related::Text, $child, Related::Child));
}
$this->obj->setRelateds($rels);
// insert/replace crypto keys
$pgp_index = $pkcs7_index = -1;
$keys = $this->obj->keys();
for ($i=0; $i < $keys->size(); $i++) {
$key = $keys->get($i);
if ($pgp_index < 0 && $key->type() == Key::PGP)
$pgp_index = $i;
else if ($pkcs7_index < 0 && $key->type() == Key::PKCS7_MIME)
$pkcs7_index = $i;
}
$pgpkey = $object['pgppublickey'] ? new Key($object['pgppublickey'], Key::PGP) : new Key();
$pkcs7key = $object['pkcs7publickey'] ? new Key($object['pkcs7publickey'], Key::PKCS7_MIME) : new Key();
if ($pgp_index >= 0)
$keys->set($pgp_index, $pgpkey);
else if (!empty($object['pgppublickey']))
$keys->push($pgpkey);
if ($pkcs7_index >= 0)
$keys->set($pkcs7_index, $pkcs7key);
else if (!empty($object['pkcs7publickey']))
$keys->push($pkcs7key);
$this->obj->setKeys($keys);
// TODO: handle language, gpslocation, etc.
// cache this data
$this->data = $object;
unset($this->data['_formatobj']);
}
/**
*
*/
public function is_valid()
{
return $this->data || (is_object($this->obj) && $this->obj->uid() /*$this->obj->isValid()*/);
}
/**
* Convert the Contact object into a hash array data structure
*
* @return array Contact data as hash array
*/
public function to_array()
{
// return cached result
if (!empty($this->data))
return $this->data;
$this->init();
// read object properties into local data object
$object = array(
'uid' => $this->obj->uid(),
# 'changed' => $this->obj->lastModified(),
'name' => $this->obj->name(),
);
$nc = $this->obj->nameComponents();
$object['surname'] = join(' ', self::vector2array($nc->surnames()));
$object['firstname'] = join(' ', self::vector2array($nc->given()));
$object['middlename'] = join(' ', self::vector2array($nc->additional()));
$object['prefix'] = join(' ', self::vector2array($nc->prefixes()));
$object['suffix'] = join(' ', self::vector2array($nc->suffixes()));
$object['nickname'] = join(' ', self::vector2array($this->obj->nickNames()));
$object['profession'] = join(' ', self::vector2array($this->obj->titles()));
// organisation related properties (affiliation)
$orgs = $this->obj->affiliations();
if ($orgs->size()) {
$org = $orgs->get(0);
$object['organization'] = $org->organisation();
$object['jobtitle'] = join(' ', self::vector2array($org->roles()));
$object['department'] = join(' ', self::vector2array($org->organisationalUnits()));
$this->read_relateds($org->relateds(), $object);
}
$object['email'] = self::vector2array($this->obj->emailAddresses());
$object['im'] = self::vector2array($this->obj->imAddresses());
$urls = $this->obj->urls();
for ($i=0; $i < $urls->size(); $i++) {
$url = $urls->get($i);
$subtype = $url->type() == Url::Blog ? 'blog' : 'homepage';
$object['website'][] = array('url' => $url->url(), 'type' => $subtype);
}
// addresses
$this->read_addresses($this->obj->addresses(), $object);
if ($org && ($offices = $org->addresses()))
$this->read_addresses($offices, $object, 'office');
// telehones
$tels = $this->obj->telephones();
$teltypes = array_flip($this->phonetypes);
for ($i=0; $i < $tels->size(); $i++) {
$tel = $tels->get($i);
$object['phone'][] = array('number' => $tel->number(), 'type' => $teltypes[$tel->types()]);
}
$object['notes'] = $this->obj->note();
$object['freebusyurl'] = $this->obj->freeBusyUrl();
if ($bday = self::php_datetime($this->obj->bDay()))
$object['birthday'] = $bday->format('c');
if ($anniversary = self::php_datetime($this->obj->anniversary()))
$object['anniversary'] = $anniversary->format('c');
$gendermap = array_flip($this->gendermap);
if (($g = $this->obj->gender()) && $gendermap[$g])
$object['gender'] = $gendermap[$g];
if ($this->obj->photoMimetype())
$object['photo'] = $this->obj->photo();
// relateds -> spouse, children
$this->read_relateds($this->obj->relateds(), $object);
// crypto settings: currently only key values are supported
$keys = $this->obj->keys();
for ($i=0; is_object($keys) && $i < $keys->size(); $i++) {
$key = $keys->get($i);
if ($key->type() == Key::PGP)
$object['pgppublickey'] = $key->key();
else if ($key->type() == Key::PKCS7_MIME)
$object['pkcs7publickey'] = $key->key();
}
$this->data = $object;
return $this->data;
}
/**
* Callback for kolab_storage_cache to get words to index for fulltext search
*
* @return array List of words to save in cache
*/
public function get_words()
{
$data = '';
foreach (self::$fulltext_cols as $col) {
$val = is_array($this->data[$col]) ? join(' ', $this->data[$col]) : $this->data[$col];
if (strlen($val))
$data .= $val . ' ';
}
return array_unique(rcube_utils::normalize_string($data, true));
}
/**
* Load data from old Kolab2 format
*
* @param array Hash array with object properties
*/
public function fromkolab2($record)
{
$object = array(
'uid' => $record['uid'],
'email' => array(),
'phone' => array(),
);
foreach ($this->kolab2_fieldmap as $kolab => $rcube) {
if (is_array($record[$kolab]) || strlen($record[$kolab]))
$object[$rcube] = $record[$kolab];
}
if (isset($record['gender']))
$object['gender'] = $this->kolab2_gender[$record['gender']];
foreach ((array)$record['email'] as $i => $email)
$object['email'][] = $email['smtp-address'];
if (!$record['email'] && $record['emails'])
$object['email'] = preg_split('/,\s*/', $record['emails']);
if (is_array($record['address'])) {
foreach ($record['address'] as $i => $adr) {
$object['address'][] = array(
'type' => $this->kolab2_addresstypes[$adr['type']] ? $this->kolab2_addresstypes[$adr['type']] : $adr['type'],
'street' => $adr['street'],
'locality' => $adr['locality'],
'code' => $adr['postal-code'],
'region' => $adr['region'],
'country' => $adr['country'],
);
}
}
// office location goes into an address block
if ($record['office-location'])
$object['address'][] = array('type' => 'office', 'locality' => $record['office-location']);
// merge initials into nickname
if ($record['initials'])
$object['nickname'] = trim($object['nickname'] . ', ' . $record['initials'], ', ');
// remove empty fields
$this->data = array_filter($object);
}
/**
* Helper method to copy contents of an Address vector to the contact data object
*/
private function read_addresses($addresses, &$object, $type = null)
{
$adrtypes = array_flip($this->addresstypes);
for ($i=0; $i < $addresses->size(); $i++) {
$adr = $addresses->get($i);
$object['address'][] = array(
'type' => $type ? $type : ($adrtypes[$adr->types()] ? $adrtypes[$adr->types()] : ''), /*$adr->label()),*/
'street' => $adr->street(),
'code' => $adr->code(),
'locality' => $adr->locality(),
'region' => $adr->region(),
'country' => $adr->country()
);
}
}
/**
* Helper method to map contents of a Related vector to the contact data object
*/
private function read_relateds($rels, &$object)
{
$typemap = array_flip($this->relatedmap);
for ($i=0; $i < $rels->size(); $i++) {
$rel = $rels->get($i);
if ($rel->type() != Related::Text) // we can't handle UID relations yet
continue;
$types = $rel->relationTypes();
foreach ($typemap as $t => $field) {
if ($types & $t) {
$object[$field][] = $rel->text();
break;
}
}
}
}
}

View file

@ -0,0 +1,160 @@
<?php
/**
* Kolab Distribution List model class
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class kolab_format_distributionlist extends kolab_format
{
public $CTYPE = 'application/vcard+xml';
function __construct($xmldata = null)
{
$this->obj = new DistList;
$this->xmldata = $xmldata;
}
/**
* Load Kolab object data from the given XML block
*
* @param string XML data
*/
public function load($xml)
{
$this->obj = kolabformat::readDistlist($xml, false);
$this->loaded = true;
}
/**
* Write object data to XML format
*
* @return string XML data
*/
public function write()
{
$this->init();
$this->xmldata = kolabformat::writeDistlist($this->obj);
if (!parent::format_errors())
parent::update_uid();
else
$this->xmldata = null;
return $this->xmldata;
}
public function set(&$object)
{
$this->init();
// set some automatic values if missing
if (!empty($object['uid']))
$this->obj->setUid($object['uid']);
$this->obj->setName($object['name']);
$seen = array();
$members = new vectorcontactref;
foreach ($object['member'] as $member) {
if ($member['uid'])
$m = new ContactReference(ContactReference::UidReference, $member['uid']);
else if ($member['email'])
$m = new ContactReference(ContactReference::EmailReference, $member['email']);
else
continue;
$m->setName($member['name']);
$members->push($m);
$seen[$member['email']]++;
}
$this->obj->setMembers($members);
// cache this data
$this->data = $object;
unset($this->data['_formatobj']);
}
public function is_valid()
{
return $this->data || (is_object($this->obj) && $this->obj->isValid());
}
/**
* Load data from old Kolab2 format
*/
public function fromkolab2($record)
{
$object = array(
'uid' => $record['uid'],
'changed' => $record['last-modification-date'],
'name' => $record['last-name'],
'member' => array(),
);
foreach ($record['member'] as $member) {
$object['member'][] = array(
'email' => $member['smtp-address'],
'name' => $member['display-name'],
'uid' => $member['uid'],
);
}
$this->data = $object;
}
/**
* Convert the Distlist object into a hash array data structure
*
* @return array Distribution list data as hash array
*/
public function to_array()
{
// return cached result
if (!empty($this->data))
return $this->data;
$this->init();
// read object properties
$object = array(
'uid' => $this->obj->uid(),
# 'changed' => $this->obj->lastModified(),
'name' => $this->obj->name(),
'member' => array(),
);
$members = $this->obj->members();
for ($i=0; $i < $members->size(); $i++) {
$member = $members->get($i);
# if ($member->type() == ContactReference::UidReference && ($uid = $member->uid()))
$object['member'][] = array(
'uid' => $member->uid(),
'email' => $member->email(),
'name' => $member->name(),
);
}
$this->data = $object;
return $this->data;
}
}

View file

@ -0,0 +1,638 @@
<?php
/**
* Kolab Event model class
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class kolab_format_event extends kolab_format
{
public $CTYPE = 'application/calendar+xml';
public static $fulltext_cols = array('title', 'description', 'location', 'attendees:name', 'attendees:email');
private $sensitivity_map = array(
'public' => kolabformat::ClassPublic,
'private' => kolabformat::ClassPrivate,
'confidential' => kolabformat::ClassConfidential,
);
private $role_map = array(
'REQ-PARTICIPANT' => kolabformat::Required,
'OPT-PARTICIPANT' => kolabformat::Optional,
'NON-PARTICIPANT' => kolabformat::NonParticipant,
'CHAIR' => kolabformat::Chair,
);
private $rrule_type_map = array(
'MINUTELY' => RecurrenceRule::Minutely,
'HOURLY' => RecurrenceRule::Hourly,
'DAILY' => RecurrenceRule::Daily,
'WEEKLY' => RecurrenceRule::Weekly,
'MONTHLY' => RecurrenceRule::Monthly,
'YEARLY' => RecurrenceRule::Yearly,
);
private $weekday_map = array(
'MO' => kolabformat::Monday,
'TU' => kolabformat::Tuesday,
'WE' => kolabformat::Wednesday,
'TH' => kolabformat::Thursday,
'FR' => kolabformat::Friday,
'SA' => kolabformat::Saturday,
'SU' => kolabformat::Sunday,
);
private $alarm_type_map = array(
'DISPLAY' => Alarm::DisplayAlarm,
'EMAIL' => Alarm::EMailAlarm,
'AUDIO' => Alarm::AudioAlarm,
);
private $status_map = array(
'UNKNOWN' => kolabformat::PartNeedsAction,
'NEEDS-ACTION' => kolabformat::PartNeedsAction,
'TENTATIVE' => kolabformat::PartTentative,
'ACCEPTED' => kolabformat::PartAccepted,
'DECLINED' => kolabformat::PartDeclined,
'DELEGATED' => kolabformat::PartDelegated,
);
private $kolab2_rolemap = array(
'required' => 'REQ-PARTICIPANT',
'optional' => 'OPT-PARTICIPANT',
'resource' => 'CHAIR',
);
private $kolab2_statusmap = array(
'none' => 'NEEDS-ACTION',
'tentative' => 'TENTATIVE',
'accepted' => 'CONFIRMED',
'accepted' => 'ACCEPTED',
'declined' => 'DECLINED',
);
private $kolab2_monthmap = array('', 'january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december');
/**
* Default constructor
*/
function __construct($xmldata = null)
{
$this->obj = new Event;
$this->xmldata = $xmldata;
}
/**
* Load Contact object data from the given XML block
*
* @param string XML data
*/
public function load($xml)
{
$this->obj = kolabformat::readEvent($xml, false);
$this->loaded = true;
}
/**
* Write Contact object data to XML format
*
* @return string XML data
*/
public function write()
{
$this->init();
$this->xmldata = kolabformat::writeEvent($this->obj);
if (!parent::format_errors())
parent::update_uid();
else
$this->xmldata = null;
return $this->xmldata;
}
/**
* Set contact properties to the kolabformat object
*
* @param array Contact data as hash array
*/
public function set(&$object)
{
$this->init();
// set some automatic values if missing
if (!$this->obj->created()) {
if (!empty($object['created']))
$object['created'] = new DateTime('now', self::$timezone);
$this->obj->setCreated(self::get_datetime($object['created']));
}
if (!empty($object['uid']))
$this->obj->setUid($object['uid']);
// increment sequence
$this->obj->setSequence($this->obj->sequence()+1);
// do the hard work of setting object values
$this->obj->setStart(self::get_datetime($object['start'], null, $object['allday']));
$this->obj->setEnd(self::get_datetime($object['end'], null, $object['allday']));
$this->obj->setSummary($object['title']);
$this->obj->setLocation($object['location']);
$this->obj->setDescription($object['description']);
$this->obj->setPriority($object['priority']);
$this->obj->setClassification($this->sensitivity_map[$object['sensitivity']]);
$this->obj->setCategories(self::array2vector($object['categories']));
$this->obj->setTransparency($object['free_busy'] == 'free');
$status = kolabformat::StatusUndefined;
if ($object['free_busy'] == 'tentative')
$status = kolabformat::StatusTentative;
if ($object['cancelled'])
$status = kolabformat::StatusCancelled;
$this->obj->setStatus($status);
// process event attendees
$organizer = new ContactReference;
$attendees = new vectorattendee;
foreach ((array)$object['attendees'] as $attendee) {
$cr = new ContactReference(ContactReference::EmailReference, $attendee['email']);
$cr->setName($attendee['name']);
if ($attendee['role'] == 'ORGANIZER') {
$organizer = $cr;
}
else {
$att = new Attendee;
$att->setContact($cr);
$att->setPartStat($this->status_map[$attendee['status']]);
$att->setRole($this->role_map[$attendee['role']] ? $this->role_map[$attendee['role']] : kolabformat::Required);
$att->setRSVP((bool)$attendee['rsvp']);
if ($att->isValid()) {
$attendees->push($att);
}
else {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Invalid event attendee: " . json_encode($attendee),
), true);
}
}
}
$this->obj->setOrganizer($organizer);
$this->obj->setAttendees($attendees);
// save recurrence rule
if ($object['recurrence']) {
$rr = new RecurrenceRule;
$rr->setFrequency($this->rrule_type_map[$object['recurrence']['FREQ']]);
if ($object['recurrence']['INTERVAL'])
$rr->setInterval(intval($object['recurrence']['INTERVAL']));
if ($object['recurrence']['BYDAY']) {
$byday = new vectordaypos;
foreach (explode(',', $object['recurrence']['BYDAY']) as $day) {
$occurrence = 0;
if (preg_match('/^([\d-]+)([A-Z]+)$/', $day, $m)) {
$occurrence = intval($m[1]);
$day = $m[2];
}
if (isset($this->weekday_map[$day]))
$byday->push(new DayPos($occurrence, $this->weekday_map[$day]));
}
$rr->setByday($byday);
}
if ($object['recurrence']['BYMONTHDAY']) {
$bymday = new vectori;
foreach (explode(',', $object['recurrence']['BYMONTHDAY']) as $day)
$bymday->push(intval($day));
$rr->setBymonthday($bymday);
}
if ($object['recurrence']['BYMONTH']) {
$bymonth = new vectori;
foreach (explode(',', $object['recurrence']['BYMONTH']) as $month)
$bymonth->push(intval($month));
$rr->setBymonth($bymonth);
}
if ($object['recurrence']['COUNT'])
$rr->setCount(intval($object['recurrence']['COUNT']));
else if ($object['recurrence']['UNTIL'])
$rr->setEnd(self::get_datetime($object['recurrence']['UNTIL'], null, true));
if ($rr->isValid()) {
$this->obj->setRecurrenceRule($rr);
// add exception dates (only if recurrence rule is valid)
$exdates = new vectordatetime;
foreach ((array)$object['recurrence']['EXDATE'] as $exdate)
$exdates->push(self::get_datetime($exdate, null, true));
$this->obj->setExceptionDates($exdates);
}
else {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Invalid event recurrence rule: " . json_encode($object['recurrence']),
), true);
}
}
// save alarm
$valarms = new vectoralarm;
if ($object['alarms']) {
list($offset, $type) = explode(":", $object['alarms']);
if ($type == 'EMAIL') { // email alarms implicitly go to event owner
$recipients = new vectorcontactref;
$recipients->push(new ContactReference(ContactReference::EmailReference, $object['_owner']));
$alarm = new Alarm($object['title'], strval($object['description']), $recipients);
}
else { // default: display alarm
$alarm = new Alarm($object['title']);
}
if (preg_match('/^@(\d+)/', $offset, $d)) {
$alarm->setStart(self::get_datetime($d[1], new DateTimeZone('UTC')));
}
else if (preg_match('/^([-+]?)(\d+)([SMHDW])/', $offset, $d)) {
$days = $hours = $minutes = $seconds = 0;
switch ($d[3]) {
case 'W': $days = 7*intval($d[2]); break;
case 'D': $days = intval($d[2]); break;
case 'H': $hours = intval($d[2]); break;
case 'M': $minutes = intval($d[2]); break;
case 'S': $seconds = intval($d[2]); break;
}
$alarm->setRelativeStart(new Duration($days, $hours, $minutes, $seconds, $d[1] == '-'), $d[1] == '-' ? kolabformat::Start : kolabformat::End);
}
$valarms->push($alarm);
}
$this->obj->setAlarms($valarms);
// save attachments
$vattach = new vectorattachment;
foreach ((array)$object['_attachments'] as $name => $attr) {
if (empty($attr))
continue;
$attach = new Attachment;
$attach->setLabel($name);
$attach->setUri('cid:' . $name, $attr['mimetype']);
$vattach->push($attach);
}
$this->obj->setAttachments($vattach);
// cache this data
$this->data = $object;
unset($this->data['_formatobj']);
}
/**
*
*/
public function is_valid()
{
return $this->data || (is_object($this->obj) && $this->obj->isValid() && $this->obj->uid());
}
/**
* Convert the Contact object into a hash array data structure
*
* @return array Contact data as hash array
*/
public function to_array()
{
// return cached result
if (!empty($this->data))
return $this->data;
$this->init();
$sensitivity_map = array_flip($this->sensitivity_map);
// read object properties
$object = array(
'uid' => $this->obj->uid(),
'changed' => self::php_datetime($this->obj->lastModified()),
'title' => $this->obj->summary(),
'location' => $this->obj->location(),
'description' => $this->obj->description(),
'allday' => $this->obj->start()->isDateOnly(),
'start' => self::php_datetime($this->obj->start()),
'end' => self::php_datetime($this->obj->end()),
'categories' => self::vector2array($this->obj->categories()),
'free_busy' => $this->obj->transparency() ? 'free' : 'busy', // TODO: transparency is only boolean
'sensitivity' => $sensitivity_map[$this->obj->classification()],
'priority' => $this->obj->priority(),
);
// status defines different event properties...
$status = $this->obj->status();
if ($status == kolabformat::StatusTentative)
$object['free_busy'] = 'tentative';
else if ($status == kolabformat::StatusCancelled)
$objec['cancelled'] = true;
// read organizer and attendees
if ($organizer = $this->obj->organizer()) {
$object['attendees'][] = array(
'role' => 'ORGANIZER',
'email' => $organizer->email(),
'name' => $organizer->name(),
);
}
$role_map = array_flip($this->role_map);
$status_map = array_flip($this->status_map);
$attvec = $this->obj->attendees();
for ($i=0; $i < $attvec->size(); $i++) {
$attendee = $attvec->get($i);
$cr = $attendee->contact();
$object['attendees'][] = array(
'role' => $role_map[$attendee->role()],
'status' => $status_map[$attendee->partStat()],
'rsvp' => $attendee->rsvp(),
'email' => $cr->email(),
'name' => $cr->name(),
);
}
// read recurrence rule
if (($rr = $this->obj->recurrenceRule()) && $rr->isValid()) {
$rrule_type_map = array_flip($this->rrule_type_map);
$object['recurrence'] = array('FREQ' => $rrule_type_map[$rr->frequency()]);
if ($intvl = $rr->interval())
$object['recurrence']['INTERVAL'] = $intvl;
if (($count = $rr->count()) && $count > 0) {
$object['recurrence']['COUNT'] = $count;
}
else if ($until = self::php_datetime($rr->end())) {
$until->setTime($object['start']->format('G'), $object['start']->format('i'), 0);
$object['recurrence']['UNTIL'] = $until->format('U');
}
if (($byday = $rr->byday()) && $byday->size()) {
$weekday_map = array_flip($this->weekday_map);
$weekdays = array();
for ($i=0; $i < $byday->size(); $i++) {
$daypos = $byday->get($i);
$prefix = $daypos->occurence();
$weekdays[] = ($prefix ? $prefix : '') . $weekday_map[$daypos->weekday()];
}
$object['recurrence']['BYDAY'] = join(',', $weekdays);
}
if (($bymday = $rr->bymonthday()) && $bymday->size()) {
$object['recurrence']['BYMONTHDAY'] = join(',', self::vector2array($bymday));
}
if (($bymonth = $rr->bymonth()) && $bymonth->size()) {
$object['recurrence']['BYMONTH'] = join(',', self::vector2array($bymonth));
}
if ($exceptions = $this->obj->exceptionDates()) {
for ($i=0; $i < $exceptions->size(); $i++) {
if ($exdate = self::php_datetime($exceptions->get($i)))
$object['recurrence']['EXDATE'][] = $exdate->format('U');
}
}
}
// read alarm
$valarms = $this->obj->alarms();
$alarm_types = array_flip($this->alarm_type_map);
for ($i=0; $i < $valarms->size(); $i++) {
$alarm = $valarms->get($i);
$type = $alarm_types[$alarm->type()];
if ($type == 'DISPLAY' || $type == 'EMAIL') { // only DISPLAY and EMAIL alarms are supported
if ($start = self::php_datetime($alarm->start())) {
$object['alarms'] = '@' . $start->format('U');
}
else if ($offset = $alarm->relativeStart()) {
$value = $alarm->relativeTo() == kolabformat::End ? '+' : '-';
if ($w = $offset->weeks()) $value .= $w . 'W';
else if ($d = $offset->days()) $value .= $d . 'D';
else if ($h = $offset->hours()) $value .= $h . 'H';
else if ($m = $offset->minutes()) $value .= $m . 'M';
else if ($s = $offset->seconds()) $value .= $s . 'S';
else continue;
$object['alarms'] = $value;
}
$object['alarms'] .= ':' . $type;
break;
}
}
// handle attachments
$vattach = $this->obj->attachments();
for ($i=0; $i < $vattach->size(); $i++) {
$attach = $vattach->get($i);
// skip cid: attachments which are mime message parts handled by kolab_storage_folder
if (substr($attach->uri(), 0, 4) != 'cid') {
$name = $attach->label();
$data = $attach->data();
$object['_attachments'][$name] = array(
'mimetype' => $attach->mimetype(),
'size' => strlen($data),
'content' => $data,
);
}
}
$this->data = $object;
return $this->data;
}
/**
* Callback for kolab_storage_cache to get object specific tags to cache
*
* @return array List of tags to save in cache
*/
public function get_tags()
{
$tags = array();
foreach ((array)$this->data['categories'] as $cat) {
$tags[] = rcube_utils::normalize_string($cat);
}
if (!empty($this->data['alarms'])) {
$tags[] = 'x-has-alarms';
}
return $tags;
}
/**
* Callback for kolab_storage_cache to get words to index for fulltext search
*
* @return array List of words to save in cache
*/
public function get_words()
{
$data = '';
foreach (self::$fulltext_cols as $colname) {
list($col, $field) = explode(':', $colname);
if ($field) {
$a = array();
foreach ((array)$this->data[$col] as $attr)
$a[] = $attr[$field];
$val = join(' ', $a);
}
else {
$val = is_array($this->data[$col]) ? join(' ', $this->data[$col]) : $this->data[$col];
}
if (strlen($val))
$data .= $val . ' ';
}
return array_unique(rcube_utils::normalize_string($data, true));
}
/**
* Load data from old Kolab2 format
*/
public function fromkolab2($rec)
{
if (PEAR::isError($rec))
return;
$start_time = date('H:i:s', $rec['start-date']);
$allday = $rec['_is_all_day'] || ($start_time == '00:00:00' && $start_time == date('H:i:s', $rec['end-date']));
// in Roundcube all-day events go from 12:00 to 13:00
if ($allday) {
$now = new DateTime('now', self::$timezone);
$gmt_offset = $now->getOffset();
$rec['start-date'] += 12 * 3600;
$rec['end-date'] -= 11 * 3600;
$rec['end-date'] -= $gmt_offset - date('Z', $rec['end-date']); // shift times from server's timezone to user's timezone
$rec['start-date'] -= $gmt_offset - date('Z', $rec['start-date']); // because generated with mktime() in Horde_Kolab_Format_Date::decodeDate()
// sanity check
if ($rec['end-date'] <= $rec['start-date'])
$rec['end-date'] += 86400;
}
// convert alarm time into internal format
if ($rec['alarm']) {
$alarm_value = $rec['alarm'];
$alarm_unit = 'M';
if ($rec['alarm'] % 1440 == 0) {
$alarm_value /= 1440;
$alarm_unit = 'D';
}
else if ($rec['alarm'] % 60 == 0) {
$alarm_value /= 60;
$alarm_unit = 'H';
}
$alarm_value *= -1;
}
// convert recurrence rules into internal pseudo-vcalendar format
if ($recurrence = $rec['recurrence']) {
$rrule = array(
'FREQ' => strtoupper($recurrence['cycle']),
'INTERVAL' => intval($recurrence['interval']),
);
if ($recurrence['range-type'] == 'number')
$rrule['COUNT'] = intval($recurrence['range']);
else if ($recurrence['range-type'] == 'date')
$rrule['UNTIL'] = $recurrence['range'];
if ($recurrence['day']) {
$byday = array();
$prefix = ($rrule['FREQ'] == 'MONTHLY' || $rrule['FREQ'] == 'YEARLY') ? intval($recurrence['daynumber'] ? $recurrence['daynumber'] : 1) : '';
foreach ($recurrence['day'] as $day)
$byday[] = $prefix . substr(strtoupper($day), 0, 2);
$rrule['BYDAY'] = join(',', $byday);
}
if ($recurrence['daynumber']) {
if ($recurrence['type'] == 'monthday' || $recurrence['type'] == 'daynumber')
$rrule['BYMONTHDAY'] = $recurrence['daynumber'];
else if ($recurrence['type'] == 'yearday')
$rrule['BYYEARDAY'] = $recurrence['daynumber'];
}
if ($recurrence['month']) {
$monthmap = array_flip($this->kolab2_monthmap);
$rrule['BYMONTH'] = strtolower($monthmap[$recurrence['month']]);
}
if ($recurrence['exclusion']) {
foreach ((array)$recurrence['exclusion'] as $excl)
$rrule['EXDATE'][] = strtotime($excl . date(' H:i:s', $rec['start-date'])); // use time of event start
}
}
$attendees = array();
if ($rec['organizer']) {
$attendees[] = array(
'role' => 'ORGANIZER',
'name' => $rec['organizer']['display-name'],
'email' => $rec['organizer']['smtp-address'],
'status' => 'ACCEPTED',
);
$_attendees .= $rec['organizer']['display-name'] . ' ' . $rec['organizer']['smtp-address'] . ' ';
}
foreach ((array)$rec['attendee'] as $attendee) {
$attendees[] = array(
'role' => $this->kolab2_rolemap[$attendee['role']],
'name' => $attendee['display-name'],
'email' => $attendee['smtp-address'],
'status' => $this->kolab2_statusmap[$attendee['status']],
'rsvp' => $attendee['request-response'],
);
$_attendees .= $rec['organizer']['display-name'] . ' ' . $rec['organizer']['smtp-address'] . ' ';
}
$this->data = array(
'uid' => $rec['uid'],
'title' => $rec['summary'],
'location' => $rec['location'],
'description' => $rec['body'],
'start' => $rec['start-date'],
'end' => $rec['end-date'],
'allday' => $allday,
'recurrence' => $rrule,
'alarms' => $alarm_value . $alarm_unit,
'categories' => explode(',', $rec['categories']),
'attachments' => $attachments,
'attendees' => $attendees,
'free_busy' => $rec['show-time-as'],
'priority' => $rec['priority'],
'sensitivity' => $rec['sensitivity'],
'changed' => $rec['last-modification-date'],
);
}
}

View file

@ -0,0 +1,462 @@
<?php
/**
* Kolab storage class providing static methods to access groupware objects on a Kolab server.
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class kolab_storage
{
const CTYPE_KEY = '/shared/vendor/kolab/folder-type';
const SERVERSIDE_SUBSCRIPTION = 0;
const CLIENTSIDE_SUBSCRIPTION = 1;
public static $last_error;
private static $ready = false;
private static $config;
private static $cache;
private static $imap;
/**
* Setup the environment needed by the libs
*/
public static function setup()
{
if (self::$ready)
return true;
$rcmail = rcube::get_instance();
self::$config = $rcmail->config;
self::$imap = $rcmail->get_storage();
self::$ready = class_exists('kolabformat') &&
(self::$imap->get_capability('METADATA') || self::$imap->get_capability('ANNOTATEMORE') || self::$imap->get_capability('ANNOTATEMORE2'));
if (self::$ready) {
// set imap options
self::$imap->set_options(array(
'skip_deleted' => true,
'threading' => false,
));
self::$imap->set_pagesize(9999);
}
return self::$ready;
}
/**
* Get a list of storage folders for the given data type
*
* @param string Data type to list folders for (contact,distribution-list,event,task,note)
*
* @return array List of Kolab_Folder objects (folder names in UTF7-IMAP)
*/
public static function get_folders($type)
{
$folders = $folderdata = array();
if (self::setup()) {
foreach ((array)self::list_folders('', '*', $type, false, $folderdata) as $foldername) {
$folders[$foldername] = new kolab_storage_folder($foldername, $folderdata[$foldername]);
}
}
return $folders;
}
/**
* Getter for a specific storage folder
*
* @param string IMAP folder to access (UTF7-IMAP)
* @return object kolab_storage_folder The folder object
*/
public static function get_folder($folder)
{
return self::setup() ? new kolab_storage_folder($folder) : null;
}
/**
* Getter for a single Kolab object, identified by its UID.
* This will search all folders storing objects of the given type.
*
* @param string Object UID
* @param string Object type (contact,distribution-list,event,task,note)
* @return array The Kolab object represented as hash array or false if not found
*/
public static function get_object($uid, $type)
{
self::setup();
$folder = null;
foreach ((array)self::list_folders('', '*', $type) as $foldername) {
if (!$folder)
$folder = new kolab_storage_folder($foldername);
else
$folder->set_folder($foldername);
if ($object = $folder->get_object($uid))
return $object;
}
return false;
}
/**
*
*/
public static function get_freebusy_server()
{
return unslashify(self::$config->get('kolab_freebusy_server', 'https://' . $_SESSION['imap_host'] . '/freebusy'));
}
/**
* Compose an URL to query the free/busy status for the given user
*/
public static function get_freebusy_url($email)
{
return self::get_freebusy_server() . '/' . $email . '.ifb';
}
/**
* Creates folder ID from folder name
*
* @param string $folder Folder name (UTF7-IMAP)
*
* @return string Folder ID string
*/
public static function folder_id($folder)
{
return asciiwords(strtr($folder, '/.-', '___'));
}
/**
* Deletes IMAP folder
*
* @param string $name Folder name (UTF7-IMAP)
*
* @return bool True on success, false on failure
*/
public static function folder_delete($name)
{
self::setup();
$success = self::$imap->delete_folder($name);
self::$last_error = self::$imap->get_error_str();
return $success;
}
/**
* Creates IMAP folder
*
* @param string $name Folder name (UTF7-IMAP)
* @param string $type Folder type
* @param bool $subscribed Sets folder subscription
*
* @return bool True on success, false on failure
*/
public static function folder_create($name, $type = null, $subscribed = false)
{
self::setup();
if ($saved = self::$imap->create_folder($name, $subscribed)) {
// set metadata for folder type
if ($type) {
$saved = self::$imap->set_metadata($name, array(self::CTYPE_KEY => $type));
// revert if metadata could not be set
if (!$saved) {
self::$imap->delete_folder($name);
}
}
}
if ($saved) {
return true;
}
self::$last_error = self::$imap->get_error_str();
return false;
}
/**
* Renames IMAP folder
*
* @param string $oldname Old folder name (UTF7-IMAP)
* @param string $newname New folder name (UTF7-IMAP)
*
* @return bool True on success, false on failure
*/
public static function folder_rename($oldname, $newname)
{
self::setup();
$success = self::$imap->rename_folder($oldname, $newname);
self::$last_error = self::$imap->get_error_str();
return $success;
}
/**
* Getter for human-readable name of Kolab object (folder)
* See http://wiki.kolab.org/UI-Concepts/Folder-Listing for reference
*
* @param string $folder IMAP folder name (UTF7-IMAP)
* @param string $folder_ns Will be set to namespace name of the folder
*
* @return string Name of the folder-object
*/
public static function object_name($folder, &$folder_ns=null)
{
self::setup();
$found = false;
$namespace = self::$imap->get_namespace();
if (!empty($namespace['shared'])) {
foreach ($namespace['shared'] as $ns) {
if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) {
$prefix = '';
$folder = substr($folder, strlen($ns[0]));
$delim = $ns[1];
$found = true;
$folder_ns = 'shared';
break;
}
}
}
if (!$found && !empty($namespace['other'])) {
foreach ($namespace['other'] as $ns) {
if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) {
// remove namespace prefix
$folder = substr($folder, strlen($ns[0]));
$delim = $ns[1];
// get username
$pos = strpos($folder, $delim);
if ($pos) {
$prefix = '('.substr($folder, 0, $pos).') ';
$folder = substr($folder, $pos+1);
}
else {
$prefix = '('.$folder.')';
$folder = '';
}
$found = true;
$folder_ns = 'other';
break;
}
}
}
if (!$found && !empty($namespace['personal'])) {
foreach ($namespace['personal'] as $ns) {
if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) {
// remove namespace prefix
$folder = substr($folder, strlen($ns[0]));
$prefix = '';
$delim = $ns[1];
$found = true;
break;
}
}
}
if (empty($delim))
$delim = self::$imap->get_hierarchy_delimiter();
$folder = rcube_charset::convert($folder, 'UTF7-IMAP');
$folder = str_replace($delim, ' &raquo; ', $folder);
if ($prefix)
$folder = $prefix . ' ' . $folder;
if (!$folder_ns)
$folder_ns = 'personal';
return $folder;
}
/**
* Creates a SELECT field with folders list
*
* @param string $type Folder type
* @param array $attrs SELECT field attributes (e.g. name)
* @param string $current The name of current folder (to skip it)
*
* @return html_select SELECT object
*/
public static function folder_selector($type, $attrs, $current = '')
{
// get all folders of specified type
$folders = self::get_folders($type);
$delim = self::$imap->get_hierarchy_delimiter();
$names = array();
$len = strlen($current);
if ($len && ($rpos = strrpos($current, $delim))) {
$parent = substr($current, 0, $rpos);
$p_len = strlen($parent);
}
// Filter folders list
foreach ($folders as $c_folder) {
$name = $c_folder->name;
// skip current folder and it's subfolders
if ($len && ($name == $current || strpos($name, $current.$delim) === 0)) {
continue;
}
// always show the parent of current folder
if ($p_len && $name == $parent) { }
// skip folders where user have no rights to create subfolders
else if ($c_folder->get_owner() != $_SESSION['username']) {
$rights = $c_folder->get_myrights();
if (!preg_match('/[ck]/', $rights)) {
continue;
}
}
$names[$name] = rcube_charset::convert($name, 'UTF7-IMAP');
}
// Make sure parent folder is listed (might be skipped e.g. if it's namespace root)
if ($p_len && !isset($names[$parent])) {
$names[$parent] = rcube_charset::convert($parent, 'UTF7-IMAP');
}
// Sort folders list
asort($names, SORT_LOCALE_STRING);
$folders = array_keys($names);
$names = array();
// Build SELECT field of parent folder
$select = new html_select($attrs);
$select->add('---', '');
foreach ($folders as $name) {
$imap_name = $name;
$name = $origname = self::object_name($name);
// find folder prefix to truncate
for ($i = count($names)-1; $i >= 0; $i--) {
if (strpos($name, $names[$i].' &raquo; ') === 0) {
$length = strlen($names[$i].' &raquo; ');
$prefix = substr($name, 0, $length);
$count = count(explode(' &raquo; ', $prefix));
$name = str_repeat('&nbsp;&nbsp;', $count-1) . '&raquo; ' . substr($name, $length);
break;
}
}
$names[] = $origname;
$select->add($name, $imap_name);
}
return $select;
}
/**
* Returns a list of folder names
*
* @param string Optional root folder
* @param string Optional name pattern
* @param string Data type to list folders for (contact,distribution-list,event,task,note,mail)
* @param string Enable to return subscribed folders only
* @param array Will be filled with folder-types data
*
* @return array List of folders
*/
public static function list_folders($root = '', $mbox = '*', $filter = null, $subscribed = false, &$folderdata = array())
{
if (!self::setup()) {
return null;
}
if (!$filter) {
// Get ALL folders list, standard way
if ($subscribed) {
return self::$imap->list_folders_subscribed($root, $mbox);
}
else {
return self::$imap->list_folders($root, $mbox);
}
}
$prefix = $root . $mbox;
// get folders types
$folderdata = self::$imap->get_metadata($prefix, self::CTYPE_KEY);
if (!is_array($folderdata)) {
return array();
}
$folderdata = array_map('implode', $folderdata);
$regexp = '/^' . preg_quote($filter, '/') . '(\..+)?$/';
// In some conditions we can skip LIST command (?)
if ($subscribed == false && $filter != 'mail' && $prefix == '*') {
foreach ($folderdata as $folder => $type) {
if (!preg_match($regexp, $type)) {
unset($folderdata[$folder]);
}
}
return array_keys($folderdata);
}
// Get folders list
if ($subscribed) {
$folders = self::$imap->list_folders_subscribed_direct($root, $mbox);
}
else {
$folders = self::$imap->list_folders_direct($root, $mbox);
}
// In case of an error, return empty list (?)
if (!is_array($folders)) {
return array();
}
// Filter folders list
foreach ($folders as $idx => $folder) {
$type = $folderdata[$folder];
if ($filter == 'mail' && empty($type)) {
continue;
}
if (empty($type) || !preg_match($regexp, $type)) {
unset($folders[$idx]);
}
}
return $folders;
}
}

View file

@ -0,0 +1,568 @@
<?php
/**
* Kolab storage cache class providing a local caching layer for Kolab groupware objects.
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class kolab_storage_cache
{
private $db;
private $imap;
private $folder;
private $uid2msg;
private $objects;
private $index = array();
private $resource_uri;
private $enabled = true;
private $synched = false;
private $ready = false;
private $binary_cols = array('photo','pgppublickey','pkcs7publickey');
/**
* Default constructor
*/
public function __construct(kolab_storage_folder $storage_folder = null)
{
$rcmail = rcube::get_instance();
$this->db = $rcmail->get_dbh();
$this->imap = $rcmail->get_storage();
$this->enabled = $rcmail->config->get('kolab_cache', false);
if ($storage_folder)
$this->set_folder($storage_folder);
}
/**
* Connect cache with a storage folder
*
* @param kolab_storage_folder The storage folder instance to connect with
*/
public function set_folder(kolab_storage_folder $storage_folder)
{
$this->folder = $storage_folder;
if (empty($this->folder->name)) {
$this->ready = false;
return;
}
// compose fully qualified ressource uri for this instance
$this->resource_uri = $this->folder->get_resource_uri();
$this->ready = $this->enabled;
}
/**
* Synchronize local cache data with remote
*/
public function synchronize()
{
// only sync once per request cycle
if ($this->synched)
return;
// lock synchronization for this folder or wait if locked
$this->_sync_lock();
// synchronize IMAP mailbox cache
$this->imap->folder_sync($this->folder->name);
// compare IMAP index with object cache index
$imap_index = $this->imap->index($this->folder->name);
$this->index = $imap_index->get();
// determine objects to fetch or to invalidate
if ($this->ready) {
// read cache index
$sql_result = $this->db->query(
"SELECT msguid, uid FROM kolab_cache WHERE resource=? AND type<>?",
$this->resource_uri,
'lock'
);
$old_index = array();
while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
$old_index[] = $sql_arr['msguid'];
$this->uid2msg[$sql_arr['uid']] = $sql_arr['msguid'];
}
// fetch new objects from imap
$fetch_index = array_diff($this->index, $old_index);
foreach ($this->_fetch($fetch_index, '*') as $object) {
$msguid = $object['_msguid'];
$this->set($msguid, $object);
}
// delete invalid entries from local DB
$del_index = array_diff($old_index, $this->index);
if (!empty($del_index)) {
$quoted_ids = join(',', array_map(array($this->db, 'quote'), $del_index));
$this->db->query(
"DELETE FROM kolab_cache WHERE resource=? AND msguid IN ($quoted_ids)",
$this->resource_uri
);
}
}
// remove lock
$this->_sync_unlock();
$this->synched = time();
}
/**
* Read a single entry from cache or
*
* @param string Related IMAP message UID
* @param string Object type to read
* @param string IMAP folder name the entry relates to
* @param array Hash array with object properties or null if not found
*/
public function get($msguid, $type = null, $foldername = null)
{
// delegate to another cache instance
if ($foldername && $foldername != $this->folder->name) {
return kolab_storage::get_folder($foldername)->cache->get($msguid, $object);
}
// load object if not in memory
if (!isset($this->objects[$msguid])) {
if ($this->ready) {
$sql_result = $this->db->query(
"SELECT * FROM kolab_cache ".
"WHERE resource=? AND msguid=?",
$this->resource_uri,
$msguid
);
if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
$this->objects[$msguid] = $this->_unserialize($sql_arr);
}
}
// fetch from IMAP if not present in cache
if (empty($this->objects[$msguid])) {
$result = $this->_fetch(array($msguid), $type, $foldername);
$this->objects[$msguid] = $result[0];
}
}
return $this->objects[$msguid];
}
/**
* Insert/Update a cache entry
*
* @param string Related IMAP message UID
* @param mixed Hash array with object properties to save or false to delete the cache entry
* @param string IMAP folder name the entry relates to
*/
public function set($msguid, $object, $foldername = null)
{
// delegate to another cache instance
if ($foldername && $foldername != $this->folder->name) {
kolab_storage::get_folder($foldername)->cache->set($msguid, $object);
return;
}
// write to cache
if ($this->ready) {
// remove old entry
$this->db->query("DELETE FROM kolab_cache WHERE resource=? AND msguid=?",
$this->resource_uri, $msguid);
// write new object data if not false (wich means deleted)
if ($object) {
$sql_data = $this->_serialize($object);
$objtype = $object['_type'] ? $object['_type'] : $this->folder->type;
$result = $this->db->query(
"INSERT INTO kolab_cache ".
" (resource, type, msguid, uid, data, xml, dtstart, dtend, tags, words)".
" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
$this->resource_uri,
$objtype,
$msguid,
$object['uid'],
$sql_data['data'],
$sql_data['xml'],
$sql_data['dtstart'],
$sql_data['dtend'],
$sql_data['tags'],
$sql_data['words']
);
if (!$this->db->affected_rows($result)) {
rcmail::raise_error(array(
'code' => 900, 'type' => 'php',
'message' => "Failed to write to kolab cache"
), true);
}
}
}
// keep a copy in memory for fast access
$this->objects[$msguid] = $object;
if ($object)
$this->uid2msg[$object['uid']] = $msguid;
}
/**
* Move an existing cache entry to a new resource
*
* @param string Entry's IMAP message UID
* @param string Entry's Object UID
* @param string Target IMAP folder to move it to
*/
public function move($msguid, $objuid, $target_folder)
{
$target = kolab_storage::get_folder($target_folder);
// resolve new message UID in target folder
if ($new_msguid = $target->cache->uid2msguid($objuid)) {
$this->db->query(
"UPDATE kolab_cache SET resource=?, msguid=? ".
"WHERE resource=? AND msguid=?",
$target->get_resource_uri(),
$new_msguid,
$this->resource_uri,
$msguid
);
}
else {
// just clear cache entry
$this->set($msguid, false);
}
unset($this->uid2msg[$uid]);
}
/**
* Remove all objects from local cache
*/
public function purge($type = null)
{
$result = $this->db->query(
"DELETE FROM kolab_cache WHERE resource=?".
($type ? ' AND type=?' : ''),
$this->resource_uri,
$type
);
return $this->db->affected_rows($result);
}
/**
* Select Kolab objects filtered by the given query
*
* @param array Pseudo-SQL query as list of filter parameter triplets
* triplet: array('<colname>', '<comparator>', '<value>')
* @return array List of Kolab data objects (each represented as hash array)
*/
public function select($query = array())
{
$result = array();
// read from local cache DB (assume it to be synchronized)
if ($this->ready) {
$sql_result = $this->db->query(
"SELECT * FROM kolab_cache ".
"WHERE resource=? " . $this->_sql_where($query),
$this->resource_uri
);
while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
if ($object = $this->_unserialize($sql_arr))
$result[] = $object;
}
}
else {
// extract object type from query parameter
$filter = $this->_query2assoc($query);
// use 'list' for folder's default objects
if ($filter['type'] == $this->type) {
$index = $this->index;
}
else { // search by object type
$search = 'UNDELETED HEADER X-Kolab-Type ' . kolab_storage_folder::KTYPE_PREFIX . $filter['type'];
$index = $this->imap->search_once($this->folder->name, $search)->get();
}
// fetch all messages in $index from IMAP
$result = $this->_fetch($index, $filter['type']);
// TODO: post-filter result according to query
}
return $result;
}
/**
* Get number of objects mathing the given query
*
* @param array $query Pseudo-SQL query as list of filter parameter triplets
* @return integer The number of objects of the given type
*/
public function count($query = array())
{
$count = 0;
// cache is in sync, we can count records in local DB
if ($this->synched) {
$sql_result = $this->db->query(
"SELECT COUNT(*) AS NUMROWS FROM kolab_cache ".
"WHERE resource=? " . $this->_sql_where($query),
$this->resource_uri
);
$sql_arr = $this->db->fetch_assoc($sql_result);
$count = intval($sql_arr['NUMROWS']);
}
else {
// search IMAP by object type
$filter = $this->_query2assoc($query);
$ctype = kolab_storage_folder::KTYPE_PREFIX . $filter['type'];
$index = $this->imap->search_once($this->folder->name, 'UNDELETED HEADER X-Kolab-Type ' . $ctype);
$count = $index->count();
}
return $count;
}
/**
* Helper method to compose a valid SQL query from pseudo filter triplets
*/
private function _sql_where($query)
{
$sql_where = '';
foreach ($query as $param) {
if ($param[1] == '=' && is_array($param[2])) {
$qvalue = '(' . join(',', array_map(array($this->db, 'quote'), $param[2])) . ')';
$param[1] = 'IN';
}
else {
$qvalue = $this->db->quote($param[2]);
}
$sql_where .= sprintf(' AND %s %s %s',
$this->db->quote_identifier($param[0]),
$param[1],
$qvalue
);
}
return $sql_where;
}
/**
* Helper method to convert the given pseudo-query triplets into
* an associative filter array with 'equals' values only
*/
private function _query2assoc($query)
{
// extract object type from query parameter
$filter = array();
foreach ($query as $param) {
if ($param[1] == '=')
$filter[$param[0]] = $param[2];
}
return $filter;
}
/**
* Fetch messages from IMAP
*
* @param array List of message UIDs to fetch
* @return array List of parsed Kolab objects
*/
private function _fetch($index, $type = null, $folder = null)
{
$results = array();
foreach ((array)$index as $msguid) {
if ($object = $this->folder->read_object($msguid, $type, $folder)) {
$results[] = $object;
$this->uid2msg[$object['uid']] = $msguid;
}
}
return $results;
}
/**
* Helper method to convert the given Kolab object into a dataset to be written to cache
*/
private function _serialize($object)
{
$bincols = array_flip($this->binary_cols);
$sql_data = array('dtstart' => null, 'dtend' => null, 'xml' => '', 'tags' => '', 'words' => '');
// set type specific values
if ($this->folder->type == 'event') {
// database runs in server's timezone so using date() is what we want
$sql_data['dtstart'] = date('Y-m-d H:i:s', is_object($object['start']) ? $object['start']->format('U') : $object['start']);
$sql_data['dtend'] = date('Y-m-d H:i:s', is_object($object['end']) ? $object['end']->format('U') : $object['end']);
// extend date range for recurring events
if ($object['recurrence']) {
$sql_data['dtend'] = date('Y-m-d H:i:s', $object['recurrence']['UNTIL'] ?: strtotime('now + 2 years'));
}
}
if ($object['_formatobj']) {
$sql_data['xml'] = (string)$object['_formatobj']->write();
$sql_data['tags'] = ' ' . join(' ', $object['_formatobj']->get_tags()) . ' '; // pad with spaces for strict/prefix search
$sql_data['words'] = ' ' . join(' ', $object['_formatobj']->get_words()) . ' ';
}
// extract object data
$data = array();
foreach ($object as $key => $val) {
if ($val === "" || $val === null) {
// skip empty properties
continue;
}
if (isset($bincols[$key])) {
$data[$key] = base64_encode($val);
}
else if ($key[0] != '_') {
$data[$key] = $val;
}
else if ($key == '_attachments') {
foreach ($val as $k => $att) {
unset($att['content'], $att['path']);
if ($att['id'])
$data[$key][$k] = $att;
}
}
}
$sql_data['data'] = serialize($data);
return $sql_data;
}
/**
* Helper method to turn stored cache data into a valid storage object
*/
private function _unserialize($sql_arr)
{
$object = unserialize($sql_arr['data']);
// decode binary properties
foreach ($this->binary_cols as $key) {
if (!empty($object[$key]))
$object[$key] = base64_decode($object[$key]);
}
// add meta data
$object['_type'] = $sql_arr['type'];
$object['_msguid'] = $sql_arr['msguid'];
$object['_mailbox'] = $this->folder->name;
$object['_formatobj'] = kolab_format::factory($sql_arr['type'], $sql_arr['xml']);
return $object;
}
/**
* Check lock record for this folder and wait if locked or set lock
*/
private function _sync_lock()
{
if (!$this->ready)
return;
$sql_arr = $this->db->fetch_assoc($this->db->query(
"SELECT msguid AS locked, ".$this->db->unixtimestamp('created')." AS created FROM kolab_cache ".
"WHERE resource=? AND type=?",
$this->resource_uri,
'lock'
));
// create lock record if not exists
if (!$sql_arr) {
$this->db->query(
"INSERT INTO kolab_cache (resource, type, msguid, created, uid, data, xml)".
" VALUES (?, ?, 1, ?, '', '', '')",
$this->resource_uri,
'lock',
date('Y-m-d H:i:s')
);
}
// wait if locked (expire locks after 10 minutes)
else if (intval($sql_arr['locked']) > 0 && (time() - $sql_arr['created']) < 600) {
usleep(500000);
return $this->_sync_lock();
}
// set lock
else {
$this->db->query(
"UPDATE kolab_cache SET msguid=1, created=? ".
"WHERE resource=? AND type=?",
date('Y-m-d H:i:s'),
$this->resource_uri,
'lock'
);
}
}
/**
* Remove lock for this folder
*/
private function _sync_unlock()
{
$this->db->query(
"UPDATE kolab_cache SET msguid=0, created='' ".
"WHERE resource=? AND type=?",
$this->resource_uri,
'lock'
);
}
/**
* Resolve an object UID into an IMAP message UID
*
* @param string Kolab object UID
* @param boolean Include deleted objects
* @return int The resolved IMAP message UID
*/
public function uid2msguid($uid, $deleted = false)
{
if (!isset($this->uid2msg[$uid])) {
// use IMAP SEARCH to get the right message
$index = $this->imap->search_once($this->folder->name, ($deleted ? '' : 'UNDELETED ') . 'HEADER SUBJECT ' . $uid);
$results = $index->get();
$this->uid2msg[$uid] = $results[0];
}
return $this->uid2msg[$uid];
}
}

View file

@ -0,0 +1,835 @@
<?php
/**
* The kolab_storage_folder class represents an IMAP folder on the Kolab server.
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class kolab_storage_folder
{
const KTYPE_PREFIX = 'application/x-vnd.kolab.';
/**
* The folder name.
* @var string
*/
public $name;
/**
* The type of this folder.
* @var string
*/
public $type;
/**
* The attached cache object
* @var kolab_storage_cache
*/
public $cache;
private $type_annotation;
private $imap;
private $info;
private $owner;
private $resource_uri;
private $uid2msg = array();
/**
* Default constructor
*/
function __construct($name, $type = null)
{
$this->imap = rcube::get_instance()->get_storage();
$this->imap->set_options(array('skip_deleted' => true));
$this->cache = new kolab_storage_cache($this);
$this->set_folder($name, $type);
}
/**
* Set the IMAP folder this instance connects to
*
* @param string The folder name/path
* @param string Optional folder type if known
*/
public function set_folder($name, $type = null)
{
if (!$type) {
$metadata = $this->imap->get_metadata($name, array(kolab_storage::CTYPE_KEY));
$type = $metadata[$name][kolab_storage::CTYPE_KEY];
}
$this->name = $name;
$this->type_annotation = $type;
$this->type = reset(explode('.', $type));
$this->resource_uri = null;
$this->imap->set_folder($this->name);
$this->cache->set_folder($this);
}
/**
*
*/
private function get_folder_info()
{
if (!isset($this->info))
$this->info = $this->imap->folder_info($this->name);
return $this->info;
}
/**
* Returns IMAP metadata/annotations (GETMETADATA/GETANNOTATION)
*
* @param array List of metadata keys to read
* @return array Metadata entry-value hash array on success, NULL on error
*/
public function get_metadata($keys)
{
$metadata = $this->imap->get_metadata($this->name, (array)$keys);
return $metadata[$this->name];
}
/**
* Sets IMAP metadata/annotations (SETMETADATA/SETANNOTATION)
*
* @param array $entries Entry-value array (use NULL value as NIL)
* @return boolean True on success, False on failure
*/
public function set_metadata($entries)
{
return $this->imap->set_metadata($this->name, $entries);
}
/**
* Returns the owner of the folder.
*
* @return string The owner of this folder.
*/
public function get_owner()
{
// return cached value
if (isset($this->owner))
return $this->owner;
$info = $this->get_folder_info();
$rcmail = rcube::get_instance();
switch ($info['namespace']) {
case 'personal':
$this->owner = $rcmail->user->get_username();
break;
case 'shared':
$this->owner = 'anonymous';
break;
default:
$owner = '';
list($prefix, $user) = explode($this->imap->get_hierarchy_delimiter(), $info['name']);
if (strpos($user, '@') === false) {
$domain = strstr($rcmail->user->get_username(), '@');
if (!empty($domain))
$user .= $domain;
}
$this->owner = $user;
break;
}
return $this->owner;
}
/**
* Getter for the name of the namespace to which the IMAP folder belongs
*
* @return string Name of the namespace (personal, other, shared)
*/
public function get_namespace()
{
return $this->imap->folder_namespace($this->name);
}
/**
* Get IMAP ACL information for this folder
*
* @return string Permissions as string
*/
public function get_myrights()
{
$rights = $this->info['rights'];
if (!is_array($rights))
$rights = $this->imap->my_rights($this->name);
return join('', (array)$rights);
}
/**
* Compose a unique resource URI for this IMAP folder
*/
public function get_resource_uri()
{
if (!empty($this->resource_uri))
return $this->resource_uri;
// strip namespace prefix from folder name
$ns = $this->get_namespace();
$nsdata = $this->imap->get_namespace($ns);
if (is_array($nsdata[0]) && strlen($nsdata[0][0]) && strpos($this->name, $nsdata[0][0]) === 0) {
$subpath = substr($this->name, strlen($nsdata[0][0]));
if ($ns == 'other') {
list($user, $suffix) = explode($nsdata[0][1], $subpath);
$subpath = $suffix;
}
}
else {
$subpath = $this->name;
}
// compose fully qualified ressource uri for this instance
$this->resource_uri = 'imap://' . urlencode($this->get_owner()) . '@' . $this->imap->options['host'] . '/' . $subpath;
return $this->resource_uri;
}
/**
* Check subscription status of this folder
*
* @param string Subscription type (kolab_storage::SERVERSIDE_SUBSCRIPTION or kolab_storage::CLIENTSIDE_SUBSCRIPTION)
* @return boolean True if subscribed, false if not
*/
public function is_subscribed($type = 0)
{
static $subscribed; // local cache
if ($type == kolab_storage::SERVERSIDE_SUBSCRIPTION) {
if (!$subscribed)
$subscribed = $this->imap->list_folders_subscribed();
return in_array($this->name, $subscribed);
}
else if (kolab_storage::CLIENTSIDE_SUBSCRIPTION) {
// TODO: implement this
return true;
}
return false;
}
/**
* Change subscription status of this folder
*
* @param boolean The desired subscription status: true = subscribed, false = not subscribed
* @param string Subscription type (kolab_storage::SERVERSIDE_SUBSCRIPTION or kolab_storage::CLIENTSIDE_SUBSCRIPTION)
* @return True on success, false on error
*/
public function subscribe($subscribed, $type = 0)
{
if ($type == kolab_storage::SERVERSIDE_SUBSCRIPTION) {
return $subscribed ? $this->imap->subscribe($this->name) : $this->imap->unsubscribe($this->name);
}
else {
// TODO: implement this
}
return false;
}
/**
* Get number of objects stored in this folder
*
* @param string $type Object type (e.g. contact, event, todo, journal, note, configuration)
* @return integer The number of objects of the given type
*/
public function count($type = null)
{
if (!$type) $type = $this->type;
// synchronize cache first
$this->cache->synchronize();
return $this->cache->count(array(array('type','=',$type)));
}
/**
* List all Kolab objects of the given type
*
* @param string $type Object type (e.g. contact, event, todo, journal, note, configuration)
* @return array List of Kolab data objects (each represented as hash array)
*/
public function get_objects($type = null)
{
if (!$type) $type = $this->type;
// synchronize caches
$this->cache->synchronize();
// fetch objects from cache
return $this->cache->select(array(array('type','=',$type)));
}
/**
* Select *some* Kolab objects matching the given query
*
* @param array Pseudo-SQL query as list of filter parameter triplets
* triplet: array('<colname>', '<comparator>', '<value>')
* @return array List of Kolab data objects (each represented as hash array)
*/
public function select($query = array())
{
// check query argument
if (empty($query))
return $this->get_objects();
$type = null;
foreach ($query as $i => $param) {
if ($param[0] == 'type') {
$type = $param[2];
}
else if (($param[0] == 'dtstart' || $param[0] == 'dtend') && is_numeric($param[2])) {
$query[$i][2] = date('Y-m-d H:i:s', $param[2]);
}
}
// add type selector if not in $query
if (!$type)
$query[] = array('type','=',$this->type);
// synchronize caches
$this->cache->synchronize();
// fetch objects from cache
return $this->cache->select($query);
}
/**
* Getter for a single Kolab object, identified by its UID
*
* @param string Object UID
* @return array The Kolab object represented as hash array
*/
public function get_object($uid)
{
// synchronize caches
$this->cache->synchronize();
$msguid = $this->cache->uid2msguid($uid);
if ($msguid && ($object = $this->cache->get($msguid)))
return $object;
return false;
}
/**
* Fetch a Kolab object attachment which is stored in a separate part
* of the mail MIME message that represents the Kolab record.
*
* @param string Object's UID
* @param string The attachment's mime number
* @param string IMAP folder where message is stored;
* If set, that also implies that the given UID is an IMAP UID
* @return mixed The attachment content as binary string
*/
public function get_attachment($uid, $part, $mailbox = null)
{
if ($msguid = ($mailbox ? $uid : $this->cache->uid2msguid($uid))) {
$this->imap->set_folder($mailbox ? $mailbox : $this->name);
return $this->imap->get_message_part($msguid, $part);
}
return null;
}
/**
* Fetch the mime message from the storage server and extract
* the Kolab groupware object from it
*
* @param string The IMAP message UID to fetch
* @param string The object type expected (use wildcard '*' to accept all types)
* @param string The folder name where the message is stored
* @return mixed Hash array representing the Kolab object, a kolab_format instance or false if not found
*/
public function read_object($msguid, $type = null, $folder = null)
{
if (!$type) $type = $this->type;
if (!$folder) $folder = $this->name;
$this->imap->set_folder($folder);
$headers = $this->imap->get_message_headers($msguid);
$object_type = substr($headers->others['x-kolab-type'], strlen(self::KTYPE_PREFIX));
$content_type = self::KTYPE_PREFIX . $object_type;
// check object type header and abort on mismatch
if ($type != '*' && $object_type != $type)
return false;
$message = new rcube_message($msguid);
$attachments = array();
// get XML part
foreach ((array)$message->attachments as $part) {
if (!$xml && ($part->mimetype == $content_type || preg_match('!application/([a-z]+\+)?xml!', $part->mimetype))) {
$xml = $part->body ? $part->body : $message->get_part_content($part->mime_id);
}
else if ($part->filename || $part->content_id) {
$key = $part->content_id ? trim($part->content_id, '<>') : $part->filename;
$attachments[$key] = array(
'id' => $part->mime_id,
'mimetype' => $part->mimetype,
'size' => $part->size,
);
}
}
if (!$xml) {
raise_error(array(
'code' => 600,
'type' => 'php',
'file' => __FILE__,
'line' => __LINE__,
'message' => "Could not find Kolab data part in message $msguid ($this->name).",
), true);
return false;
}
$format = kolab_format::factory($object_type);
if (is_a($format, 'PEAR_Error'))
return false;
// check kolab format version
if (strpos($xml, '<' . $object_type) !== false) {
// old Kolab 2.0 format detected
$handler = class_exists('Horde_Kolab_Format') ? Horde_Kolab_Format::factory('XML', $object_type) : null;
if (!is_object($handler) || is_a($handler, 'PEAR_Error')) {
return false;
}
// XML-to-array
$object = $handler->load($xml);
$format->fromkolab2($object);
}
else {
// load Kolab 3 format using libkolabxml
$format->load($xml);
}
if ($format->is_valid()) {
$object = $format->to_array();
$object['_type'] = $object_type;
$object['_msguid'] = $msguid;
$object['_mailbox'] = $this->name;
$object['_attachments'] = array_merge((array)$object['_attachments'], $attachments);
$object['_formatobj'] = $format;
return $object;
}
else {
// try to extract object UID from XML block
if (preg_match('!<uid>(.+)</uid>!Uims', $xml, $m))
$msgadd = " UID = " . trim(strip_tags($m[1]));
raise_error(array(
'code' => 600,
'type' => 'php',
'file' => __FILE__,
'line' => __LINE__,
'message' => "Could not parse Kolab object data in message $msguid ($this->name)." . $msgadd,
), true);
}
return false;
}
/**
* Save an object in this folder.
*
* @param array $object The array that holds the data of the object.
* @param string $type The type of the kolab object.
* @param string $uid The UID of the old object if it existed before
* @return boolean True on success, false on error
*/
public function save(&$object, $type = null, $uid = null)
{
if (!$type)
$type = $this->type;
// copy attachments from old message
if (!empty($object['_msguid']) && ($old = $this->cache->get($object['_msguid'], $type, $object['_mailbox']))) {
foreach ((array)$old['_attachments'] as $name => $att) {
if (!isset($object['_attachments'][$name])) {
$object['_attachments'][$name] = $old['_attachments'][$name];
}
// load photo.attachment from old Kolab2 format to be directly embedded in xcard block
if ($name == 'photo.attachment' && !isset($object['photo']) && !$object['_attachments'][$name]['content'] && $att['id']) {
$object['photo'] = $this->get_attachment($object['_msguid'], $att['id'], $object['_mailbox']);
unset($object['_attachments'][$name]);
}
}
}
if ($raw_msg = $this->build_message($object, $type)) {
$result = $this->imap->save_message($this->name, $raw_msg, '', false);
// delete old message
if ($result && !empty($object['_msguid']) && !empty($object['_mailbox'])) {
$this->imap->delete_message($object['_msguid'], $object['_mailbox']);
$this->cache->set($object['_msguid'], false, $object['_mailbox']);
}
else if ($result && $uid && ($msguid = $this->cache->uid2msguid($uid))) {
$this->imap->delete_message($msguid, $this->name);
$this->cache->set($object['_msguid'], false);
}
// update cache with new UID
if ($result) {
$object['_msguid'] = $result;
$this->cache->set($result, $object);
}
}
return $result;
}
/**
* Delete the specified object from this folder.
*
* @param mixed $object The Kolab object to delete or object UID
* @param boolean $expunge Should the folder be expunged?
* @param boolean $trigger Should the folder update be triggered?
*
* @return boolean True if successful, false on error
*/
public function delete($object, $expunge = true, $trigger = true)
{
$msguid = is_array($object) ? $object['_msguid'] : $this->cache->uid2msguid($object);
$success = false;
if ($msguid && $expunge) {
$success = $this->imap->delete_message($msguid, $this->name);
}
else if ($msguid) {
$success = $this->imap->set_flag($msguid, 'DELETED', $this->name);
}
if ($success) {
$this->cache->set($result, false);
}
return $success;
}
/**
*
*/
public function delete_all()
{
$this->cache->purge();
return $this->imap->clear_folder($this->name);
}
/**
* Restore a previously deleted object
*
* @param string Object UID
* @return mixed Message UID on success, false on error
*/
public function undelete($uid)
{
if ($msguid = $this->cache->uid2msguid($uid, true)) {
if ($this->imap->set_flag($msguid, 'UNDELETED', $this->name)) {
return $msguid;
}
}
return false;
}
/**
* Move a Kolab object message to another IMAP folder
*
* @param string Object UID
* @param string IMAP folder to move object to
* @return boolean True on success, false on failure
*/
public function move($uid, $target_folder)
{
if ($msguid = $this->cache->uid2msguid($uid)) {
if ($success = $this->imap->move_message($msguid, $target_folder, $this->name)) {
$this->cache->move($msguid, $uid, $target_folder);
return true;
}
else {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Failed to move message $msguid to $target_folder: " . $this->imap->get_error_str(),
), true);
}
}
return false;
}
/**
* Creates source of the configuration object message
*/
private function build_message(&$object, $type)
{
// load old object to preserve data we don't understand/process
if (is_object($object['_formatobj']))
$format = $object['_formatobj'];
else if ($object['_msguid'] && ($old = $this->cache->get($object['_msguid'], $type, $object['_mailbox'])))
$format = $old['_formatobj'];
// create new kolab_format instance
if (!$format)
$format = kolab_format::factory($type);
$format->set($object);
$xml = $format->write();
$object['uid'] = $format->uid; // read UID from format
$object['_formatobj'] = $format;
if (!$format->is_valid() || empty($object['uid'])) {
return false;
}
$mime = new Mail_mime("\r\n");
$rcmail = rcube::get_instance();
$headers = array();
$part_id = 1;
if ($ident = $rcmail->user->get_identity()) {
$headers['From'] = $ident['email'];
$headers['To'] = $ident['email'];
}
$headers['Date'] = date('r');
$headers['X-Kolab-Type'] = self::KTYPE_PREFIX . $type;
$headers['Subject'] = $object['uid'];
// $headers['Message-ID'] = $rcmail->gen_message_id();
$headers['User-Agent'] = $rcmail->config->get('useragent');
$mime->headers($headers);
$mime->setTXTBody('This is a Kolab Groupware object. '
. 'To view this object you will need an email client that understands the Kolab Groupware format. '
. "For a list of such email clients please visit http://www.kolab.org/\n\n");
$mime->addAttachment($xml, // file
$format->CTYPE, // content-type
'kolab.xml', // filename
false, // is_file
'8bit', // encoding
'attachment', // disposition
RCMAIL_CHARSET // charset
);
$part_id++;
// save object attachments as separate parts
// TODO: optimize memory consumption by using tempfiles for transfer
foreach ((array)$object['_attachments'] as $name => $att) {
if (empty($att['content']) && !empty($att['id'])) {
$msguid = !empty($object['_msguid']) ? $object['_msguid'] : $object['uid'];
$att['content'] = $this->get_attachment($msguid, $att['id'], $object['_mailbox']);
}
$headers = array('Content-ID' => Mail_mimePart::encodeHeader('Content-ID', '<' . $name . '>', RCMAIL_CHARSET, 'quoted-printable'));
if (!empty($att['content'])) {
$mime->addAttachment($att['content'], $att['mimetype'], $name, false, 'base64', 'attachment', '', '', '', null, null, '', null, $headers);
$part_id++;
}
else if (!empty($att['path'])) {
$mime->addAttachment($att['path'], $att['mimetype'], $name, true, 'base64', 'attachment', '', '', '', null, null, '', null, $headers);
$part_id++;
}
$object['_attachments'][$name]['id'] = $part_id;
}
return $mime->getMessage();
}
/**
* Triggers any required updates after changes within the
* folder. This is currently only required for handling free/busy
* information with Kolab.
*
* @return boolean|PEAR_Error True if successfull.
*/
public function trigger()
{
$owner = $this->get_owner();
$result = false;
switch($this->type) {
case 'event':
if ($this->get_namespace() == 'personal') {
$result = $this->trigger_url(
sprintf('%s/trigger/%s/%s.pfb', kolab_storage::get_freebusy_server(), $owner, $this->imap->mod_folder($this->name)),
$this->imap->options['user'],
$this->imap->options['password']
);
}
break;
default:
return true;
}
if ($result && is_object($result) && is_a($result, 'PEAR_Error')) {
return PEAR::raiseError(sprintf("Failed triggering folder %s. Error was: %s",
$this->name, $result->getMessage()));
}
return $result;
}
/**
* Triggers a URL.
*
* @param string $url The URL to be triggered.
* @param string $auth_user Username to authenticate with
* @param string $auth_passwd Password for basic auth
* @return boolean|PEAR_Error True if successfull.
*/
private function trigger_url($url, $auth_user = null, $auth_passwd = null)
{
require_once('HTTP/Request2.php');
try {
$rcmail = rcube::get_instance();
$request = new HTTP_Request2($url);
$request->setConfig(array('ssl_verify_peer' => $rcmail->config->get('kolab_ssl_verify_peer', true)));
// set authentication credentials
if ($auth_user && $auth_passwd)
$request->setAuth($auth_user, $auth_passwd);
$result = $request->send();
// rcube::write_log('trigger', $result->getBody());
}
catch (Exception $e) {
return PEAR::raiseError($e->getMessage());
}
return true;
}
/* Legacy methods to keep compatibility with the old Horde Kolab_Storage classes */
/**
* Compatibility method
*/
public function getOwner()
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::getOwner()");
return $this->get_owner();
}
/**
* Get IMAP ACL information for this folder
*/
public function getMyRights()
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::getMyRights()");
return $this->get_myrights();
}
/**
* NOP to stay compatible with the formerly used Horde classes
*/
public function getData()
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::getData()");
return $this;
}
/**
* List all Kolab objects of the given type
*/
public function getObjects($type = null)
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::getObjects()");
return $this->get_objects($type);
}
/**
* Getter for a single Kolab object, identified by its UID
*/
public function getObject($uid)
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::getObject()");
return $this->get_object($uid);
}
/**
*
*/
public function getAttachment($key)
{
PEAR::raiseError("Call to deprecated method not returning anything.");
return null;
}
/**
* Alias function of delete()
*/
public function deleteMessage($id, $trigger = true, $expunge = true)
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::deleteMessage()");
return $this->delete(array('_msguid' => $id), $trigger, $expunge);
}
/**
*
*/
public function deleteAll()
{
PEAR::raiseError("Call to deprecated method kolab_storage_folder::deleteAll()");
return $this->delete_all();
}
}

View file

@ -0,0 +1,74 @@
<?php
/**
* Kolab core library
*
* Plugin to setup a basic environment for the interaction with a Kolab server.
* Other Kolab-related plugins will depend on it and can use the library classes
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class libkolab extends rcube_plugin
{
/**
* Required startup method of a Roundcube plugin
*/
public function init()
{
// load local config
$this->load_config();
$this->add_hook('storage_init', array($this, 'storage_init'));
// extend include path to load bundled lib classes
$include_path = $this->home . '/lib' . PATH_SEPARATOR . ini_get('include_path');
set_include_path($include_path);
$rcmail = rcmail::get_instance();
try {
kolab_format::$timezone = new DateTimeZone($rcmail->config->get('timezone', 'GMT'));
}
catch (Exception $e) {
raise_error($e, true);
kolab_format::$timezone = new DateTimeZone('GMT');
}
// load (old) dependencies if available
if (@include_once('Horde/Util.php')) {
include_once 'Horde/Kolab/Format.php';
include_once 'Horde/Kolab/Format/XML.php';
include_once 'Horde/Kolab/Format/XML/contact.php';
include_once 'Horde/Kolab/Format/XML/event.php';
String::setDefaultCharset('UTF-8');
}
}
/**
* Hook into IMAP FETCH HEADER.FIELDS command and request Kolab-specific headers
*/
function storage_init($p)
{
$p['fetch_headers'] = trim($p['fetch_headers'] .' X-KOLAB-TYPE');
return $p;
}
}

View file

@ -26,7 +26,7 @@
*/
class odfviewer extends rcube_plugin
{
public $task = 'mail|logout';
public $task = 'mail|calendar|logout';
private $tempdir = 'plugins/odfviewer/files/';
private $tempbase = 'plugins/odfviewer/files/';
@ -56,10 +56,9 @@ class odfviewer extends rcube_plugin
$ua = new rcube_browser;
if ($ua->ie && $ua->ver < 9)
return;
// extend list of mimetypes that should open in preview
$rcmail = rcmail::get_instance();
if ($rcmail->action == 'preview' || $rcmail->action == 'show') {
if ($rcmail->action == 'preview' || $rcmail->action == 'show' || $rcmail->task == 'calendar') {
$mimetypes = $rcmail->config->get('client_mimetypes', 'text/plain,text/html,text/xml,image/jpeg,image/gif,image/png,application/x-javascript,application/pdf,application/x-shockwave-flash');
if (!is_array($mimetypes))
$mimetypes = explode(',', $mimetypes);
@ -75,20 +74,24 @@ class odfviewer extends rcube_plugin
*/
function get_part($args)
{
global $IMAP, $MESSAGE;
if (!$args['download'] && $args['mimetype'] && in_array($args['mimetype'], $this->odf_mimetypes)) {
if (empty($_GET['_load'])) {
$suffix = preg_match('/(\.\w+)$/', $args['part']->filename, $m) ? $m[1] : '.odt';
$fn = md5(session_id() . $_SERVER['REQUEST_URI']) . $suffix;
// FIXME: copy file to disk because only apache can send the file correctly
$tempfn = $this->tempdir . $fn;
if (!file_exists($tempfn)) {
$fp = fopen($tempfn, 'w');
$IMAP->get_message_part($MESSAGE->uid, $args['part']->mime_id, $args['part'], false, $fp);
fclose($fp);
if ($args['body']) {
file_put_contents($tempfn, $args['body']);
}
else {
$fp = fopen($tempfn, 'w');
$imap = rcmail::get_instance()->get_storage();
$imap->get_message_part($args['uid'], $args['id'], $args['part'], false, $fp);
fclose($fp);
}
// remember tempfiles in session to clean up on logout
$_SESSION['odfviewer']['tempfiles'][] = $fn;
}

View file

@ -34,161 +34,168 @@
var core={},gui={},xmldom={},odf={};
// Input 1
function Runtime(){}Runtime.ByteArray=function(){};Runtime.ByteArray.prototype.slice=function(){};Runtime.prototype.byteArrayFromArray=function(){};Runtime.prototype.byteArrayFromString=function(){};Runtime.prototype.byteArrayToString=function(){};Runtime.prototype.concatByteArrays=function(){};Runtime.prototype.read=function(){};Runtime.prototype.readFile=function(){};Runtime.prototype.readFileSync=function(){};Runtime.prototype.loadXML=function(){};Runtime.prototype.writeFile=function(){};
Runtime.prototype.isFile=function(){};Runtime.prototype.getFileSize=function(){};Runtime.prototype.deleteFile=function(){};Runtime.prototype.log=function(){};Runtime.prototype.setTimeout=function(){};Runtime.prototype.libraryPaths=function(){};Runtime.prototype.type=function(){};Runtime.prototype.getDOMImplementation=function(){};Runtime.prototype.getWindow=function(){};var IS_COMPILED_CODE=true;
Runtime.byteArrayToString=function(i,k){function e(e){var a="",b,h=e.length,c,d,f;for(b=0;b<h;b+=1)c=e[b],c<128?a+=String.fromCharCode(c):(b+=1,d=e[b],c<224?a+=String.fromCharCode((c&31)<<6|d&63):(b+=1,f=e[b],a+=String.fromCharCode((c&15)<<12|(d&63)<<6|f&63)));return a}if(k==="utf8")return e(i);else k!=="binary"&&this.log("Unsupported encoding: "+k);return function(e){var a="",b,h=e.length;for(b=0;b<h;b+=1)a+=String.fromCharCode(e[b]&255);return a}(i)};
Runtime.getFunctionName=function(i){return i.name===void 0?(i=/function\s+(\w+)/.exec(i))&&i[1]:i.name};
function BrowserRuntime(i){function k(b,a){var c,d,f;a?f=b:a=b;if(i){d=i.ownerDocument;if(f)c=d.createElement("span"),c.className=f,c.appendChild(d.createTextNode(f)),i.appendChild(c),i.appendChild(d.createTextNode(" "));c=d.createElement("span");c.appendChild(d.createTextNode(a));i.appendChild(c);i.appendChild(d.createElement("br"))}else console&&console.log(a)}var e=this,g={},a=window.ArrayBuffer&&window.Uint8Array;this.ByteArray=a?function(b){Uint8Array.prototype.slice=function(b,c){if(c===void 0)b===
void 0&&(b=0),c=this.length;var a=this.subarray(b,c),f,j;c-=b;f=new Uint8Array(new ArrayBuffer(c));for(j=0;j<c;j+=1)f[j]=a[j];return f};return new Uint8Array(new ArrayBuffer(b))}:function(b){var a=[];a.length=b;return a};this.concatByteArrays=a?function(b,a){var c,d=b.length,f=a.length,j=new this.ByteArray(d+f);for(c=0;c<d;c+=1)j[c]=b[c];for(c=0;c<f;c+=1)j[c+d]=a[c];return j}:function(b,a){return b.concat(a)};this.byteArrayFromArray=function(b){return b.slice()};this.byteArrayFromString=function(b,
a){if(a==="utf8"){var c=b.length,d,f,j,g=0;for(f=0;f<c;f+=1)j=b.charCodeAt(f),g+=1+(j>128)+(j>2048);d=new e.ByteArray(g);for(f=g=0;f<c;f+=1)j=b.charCodeAt(f),j<128?(d[g]=j,g+=1):j<2048?(d[g]=192|j>>>6,d[g+1]=128|j&63,g+=2):(d[g]=224|j>>>12&15,d[g+1]=128|j>>>6&63,d[g+2]=128|j&63,g+=3);return d}else a!=="binary"&&e.log("unknown encoding: "+a);c=b.length;d=new e.ByteArray(c);for(f=0;f<c;f+=1)d[f]=b.charCodeAt(f)&255;return d};this.byteArrayToString=Runtime.byteArrayToString;this.readFile=function(b,
a,c){if(g.hasOwnProperty(b))c(null,g[b]);else{var d=new XMLHttpRequest;d.open("GET",b,true);d.onreadystatechange=function(){var f;d.readyState===4&&(d.status===0&&!d.responseText?c("File "+b+" is empty."):d.status===200||d.status===0?(f=a==="binary"?typeof VBArray!=="undefined"?(new VBArray(d.responseBody)).toArray():e.byteArrayFromString(d.responseText,"binary"):d.responseText,g[b]=f,c(null,f)):c(d.responseText||d.statusText))};d.overrideMimeType&&(a!=="binary"?d.overrideMimeType("text/plain; charset="+
a):d.overrideMimeType("text/plain; charset=x-user-defined"));try{d.send(null)}catch(f){c(f.message)}}};this.read=function(b,a,c,d){if(g.hasOwnProperty(b))d(null,g[b].slice(a,a+c));else{var f=new XMLHttpRequest;f.open("GET",b,true);f.onreadystatechange=function(){var j;f.readyState===4&&(f.status===0&&!f.responseText?d("File "+b+" is empty."):f.status===200||f.status===0?(j=typeof VBArray!=="undefined"?(new VBArray(f.responseBody)).toArray():e.byteArrayFromString(f.responseText,"binary"),g[b]=j,d(null,
j.slice(a,a+c))):d(f.responseText||f.statusText))};f.overrideMimeType&&f.overrideMimeType("text/plain; charset=x-user-defined");try{f.send(null)}catch(j){d(j.message)}}};this.readFileSync=function(b,a){var c=new XMLHttpRequest,d;c.open("GET",b,false);c.overrideMimeType&&(a!=="binary"?c.overrideMimeType("text/plain; charset="+a):c.overrideMimeType("text/plain; charset=x-user-defined"));try{if(c.send(null),c.status===200||c.status===0)d=c.responseText}catch(f){}return d};this.writeFile=function(b,a,
c){g[b]=a;var d=new XMLHttpRequest;d.open("PUT",b,true);d.onreadystatechange=function(){d.readyState===4&&(d.status===0&&!d.responseText?c("File "+b+" is empty."):d.status>=200&&d.status<300||d.status===0?c(null):c("Status "+String(d.status)+": "+d.responseText||d.statusText))};a=a.buffer&&!d.sendAsBinary?a.buffer:e.byteArrayToString(a,"binary");try{d.sendAsBinary?d.sendAsBinary(a):d.send(a)}catch(f){e.log("HUH? "+f+" "+a),c(f.message)}};this.deleteFile=function(b,a){var c=new XMLHttpRequest;c.open("DELETE",
b,true);c.onreadystatechange=function(){c.readyState===4&&(c.status<200&&c.status>=300?a(c.responseText):a(null))};c.send(null)};this.loadXML=function(b,a){var c=new XMLHttpRequest;c.open("GET",b,true);c.overrideMimeType("text/xml");c.onreadystatechange=function(){c.readyState===4&&(c.status===0&&!c.responseText?a("File "+b+" is empty."):c.status===200||c.status===0?a(null,c.responseXML):a(c.responseText))};try{c.send(null)}catch(d){a(d.message)}};this.isFile=function(b,a){e.getFileSize(b,function(b){a(b!==
-1)})};this.getFileSize=function(b,a){var c=new XMLHttpRequest;c.open("HEAD",b,true);c.onreadystatechange=function(){if(c.readyState===4){var b=c.getResponseHeader("Content-Length");b?a(parseInt(b,10)):a(-1)}};c.send(null)};this.log=k;this.setTimeout=function(b,a){setTimeout(function(){b()},a)};this.libraryPaths=function(){return["lib"]};this.setCurrentDirectory=function(){};this.type=function(){return"BrowserRuntime"};this.getDOMImplementation=function(){return window.document.implementation};this.exit=
function(b){k("Calling exit with code "+String(b)+", but exit() is not implemented.")};this.getWindow=function(){return window}}
function NodeJSRuntime(){var i=require("fs"),k="";this.ByteArray=function(e){return new Buffer(e)};this.byteArrayFromArray=function(e){var g=new Buffer(e.length),a,b=e.length;for(a=0;a<b;a+=1)g[a]=e[a];return g};this.concatByteArrays=function(e,g){var a=new Buffer(e.length+g.length);e.copy(a,0,0);g.copy(a,e.length,0);return a};this.byteArrayFromString=function(e,g){return new Buffer(e,g)};this.byteArrayToString=function(e,g){return e.toString(g)};this.readFile=function(e,g,a){g!=="binary"?i.readFile(e,
g,a):i.readFile(e,null,a)};this.writeFile=function(e,g,a){i.writeFile(e,g,"binary",function(b){a(b||null)})};this.deleteFile=i.unlink;this.read=function(e,g,a,b){k&&(e=k+"/"+e);i.open(e,"r+",666,function(h,c){if(h)b(h);else{var d=new Buffer(a);i.read(c,d,0,a,g,function(a){i.close(c);b(a,d)})}})};this.readFileSync=function(e,g){return!g?"":i.readFileSync(e,g)};this.loadXML=function(){throw"Not implemented.";};this.isFile=function(e,g){k&&(e=k+"/"+e);i.stat(e,function(a,b){g(!a&&b.isFile())})};this.getFileSize=
function(e,g){k&&(e=k+"/"+e);i.stat(e,function(a,b){a?g(-1):g(b.size)})};this.log=function(e){process.stderr.write(e+"\n")};this.setTimeout=function(e,g){setTimeout(function(){e()},g)};this.libraryPaths=function(){return[__dirname]};this.setCurrentDirectory=function(e){k=e};this.currentDirectory=function(){return k};this.type=function(){return"NodeJSRuntime"};this.getDOMImplementation=function(){return null};this.exit=process.exit;this.getWindow=function(){return null}}
function RhinoRuntime(){var i=this,k=Packages.javax.xml.parsers.DocumentBuilderFactory.newInstance(),e,g,a="";k.setValidating(false);k.setNamespaceAware(true);k.setExpandEntityReferences(false);k.setSchema(null);g=Packages.org.xml.sax.EntityResolver({resolveEntity:function(a,h){var c=new Packages.java.io.FileReader(h);return new Packages.org.xml.sax.InputSource(c)}});e=k.newDocumentBuilder();e.setEntityResolver(g);this.ByteArray=function(a){return[a]};this.byteArrayFromArray=function(a){return a};
this.byteArrayFromString=function(a){var h=[],c,d=a.length;for(c=0;c<d;c+=1)h[c]=a.charCodeAt(c)&255;return h};this.byteArrayToString=Runtime.byteArrayToString;this.concatByteArrays=function(a,h){return a.concat(h)};this.loadXML=function(a,h){var c=new Packages.java.io.File(a),d;try{d=e.parse(c)}catch(f){print(f);h(f);return}h(null,d)};this.readFile=function(a,h,c){var d=new Packages.java.io.File(a),f=h==="binary"?"latin1":h;d.isFile()?(a=readFile(a,f),h==="binary"&&(a=i.byteArrayFromString(a,"binary")),
c(null,a)):c(a+" is not a file.")};this.writeFile=function(a,h,c){var a=new Packages.java.io.FileOutputStream(a),d,f=h.length;for(d=0;d<f;d+=1)a.write(h[d]);a.close();c(null)};this.deleteFile=function(a,h){(new Packages.java.io.File(a))["delete"]()?h(null):h("Could not delete "+a)};this.read=function(b,h,c,d){a&&(b=a+"/"+b);var f;f=b;var j="binary";(new Packages.java.io.File(f)).isFile()?(j==="binary"&&(j="latin1"),f=readFile(f,j)):f=null;f?d(null,this.byteArrayFromString(f.substring(h,h+c),"binary")):
d("Cannot read "+b)};this.readFileSync=function(a,h){return!h?"":readFile(a,h)};this.isFile=function(b,h){a&&(b=a+"/"+b);var c=new Packages.java.io.File(b);h(c.isFile())};this.getFileSize=function(b,h){a&&(b=a+"/"+b);var c=new Packages.java.io.File(b);h(c.length())};this.log=print;this.setTimeout=function(a){a()};this.libraryPaths=function(){return["lib"]};this.setCurrentDirectory=function(b){a=b};this.currentDirectory=function(){return a};this.type=function(){return"RhinoRuntime"};this.getDOMImplementation=
function(){return e.getDOMImplementation()};this.exit=quit;this.getWindow=function(){return null}}var runtime=function(){return typeof window!=="undefined"?new BrowserRuntime(window.document.getElementById("logoutput")):typeof require!=="undefined"?new NodeJSRuntime:new RhinoRuntime}();
(function(){function i(e){var a=e[0],b;b=eval("if (typeof "+a+" === 'undefined') {eval('"+a+" = {};');}"+a);for(a=1;a<e.length-1;a+=1)b.hasOwnProperty(e[a])||(b=b[e[a]]={});return b[e[e.length-1]]}var k={},e={};runtime.loadClass=function(g){if(!IS_COMPILED_CODE&&!k.hasOwnProperty(g)){var a=g.split("."),b;b=i(a);if(!b&&(b=function(a){var b,d,f,j,g;d=a.replace(".","/")+".js";j=runtime.libraryPaths();runtime.currentDirectory&&j.push(runtime.currentDirectory());for(g=0;!b&&g<j.length;g+=1){f=j[g];if(!e.hasOwnProperty(f))if((b=
runtime.readFileSync(j[g]+"/manifest.js","utf8"))&&b.length)try{e[f]=eval(b)}catch(i){e[f]=null,runtime.log("Cannot load manifest for "+f+".")}else e[f]=null;b=null;if((f=e[f])&&f.indexOf&&f.indexOf(d)!==-1)try{b=runtime.readFileSync(j[g]+"/"+d,"utf8")}catch(l){throw runtime.log("Error loading "+a+" "+l),l;}}if(b===void 0)throw"Cannot load class "+a;try{b=eval(a+" = eval(code);")}catch(k){throw runtime.log("Error loading "+a+" "+k),k;}return b}(g),!b||Runtime.getFunctionName(b)!==a[a.length-1]))throw runtime.log("Loaded code is not for "+
a[a.length-1]),"Loaded code is not for "+a[a.length-1];k[g]=true}}})();
(function(i){function k(e){if(e.length){var g=e[0];runtime.readFile(g,"utf8",function(a,b){function h(){var a;(a=eval(b))&&runtime.exit(a)}var c="";runtime.libraryPaths();g.indexOf("/")!==-1&&(c=g.substring(0,g.indexOf("/")));runtime.setCurrentDirectory(c);a?(runtime.log(a),runtime.exit(1)):h.apply(null,e)})}}i=Array.prototype.slice.call(i);runtime.type()==="NodeJSRuntime"?k(process.argv.slice(2)):runtime.type()==="RhinoRuntime"?k(i):k(i.slice(1))})(typeof arguments!=="undefined"&&arguments);
Runtime.prototype.isFile=function(){};Runtime.prototype.getFileSize=function(){};Runtime.prototype.deleteFile=function(){};Runtime.prototype.log=function(){};Runtime.prototype.setTimeout=function(){};Runtime.prototype.libraryPaths=function(){};Runtime.prototype.type=function(){};Runtime.prototype.getDOMImplementation=function(){};Runtime.prototype.getWindow=function(){};var IS_COMPILED_CODE=!0;
Runtime.byteArrayToString=function(g,m){function e(e){var a="",c,b=e.length,d,o,f;for(c=0;c<b;c+=1)d=e[c],128>d?a+=String.fromCharCode(d):(c+=1,o=e[c],224>d?a+=String.fromCharCode((d&31)<<6|o&63):(c+=1,f=e[c],a+=String.fromCharCode((d&15)<<12|(o&63)<<6|f&63)));return a}if("utf8"===m)return e(g);"binary"!==m&&this.log("Unsupported encoding: "+m);return function(e){var a="",c,b=e.length;for(c=0;c<b;c+=1)a+=String.fromCharCode(e[c]&255);return a}(g)};
Runtime.getFunctionName=function(g){return void 0===g.name?(g=/function\s+(\w+)/.exec(g))&&g[1]:g.name};
function BrowserRuntime(g){function m(c,b){var d,a,f;b?f=c:b=c;g?(a=g.ownerDocument,f&&(d=a.createElement("span"),d.className=f,d.appendChild(a.createTextNode(f)),g.appendChild(d),g.appendChild(a.createTextNode(" "))),d=a.createElement("span"),d.appendChild(a.createTextNode(b)),g.appendChild(d),g.appendChild(a.createElement("br"))):console&&console.log(b)}var e=this,k={},a=window.ArrayBuffer&&window.Uint8Array;this.ByteArray=a?function(c){Uint8Array.prototype.slice=function(c,d){void 0===d&&(void 0===
c&&(c=0),d=this.length);var a=this.subarray(c,d),f,h,d=d-c;f=new Uint8Array(new ArrayBuffer(d));for(h=0;h<d;h+=1)f[h]=a[h];return f};return new Uint8Array(new ArrayBuffer(c))}:function(c){var b=[];b.length=c;return b};this.concatByteArrays=a?function(c,b){var d,a=c.length,f=b.length,h=new this.ByteArray(a+f);for(d=0;d<a;d+=1)h[d]=c[d];for(d=0;d<f;d+=1)h[d+a]=b[d];return h}:function(c,b){return c.concat(b)};this.byteArrayFromArray=function(c){return c.slice()};this.byteArrayFromString=function(c,b){if("utf8"===
b){var d=c.length,a,f,h,i=0;for(f=0;f<d;f+=1)h=c.charCodeAt(f),i+=1+(128<h)+(2048<h);a=new e.ByteArray(i);for(f=i=0;f<d;f+=1)h=c.charCodeAt(f),128>h?(a[i]=h,i+=1):2048>h?(a[i]=192|h>>>6,a[i+1]=128|h&63,i+=2):(a[i]=224|h>>>12&15,a[i+1]=128|h>>>6&63,a[i+2]=128|h&63,i+=3);return a}"binary"!==b&&e.log("unknown encoding: "+b);d=c.length;a=new e.ByteArray(d);for(f=0;f<d;f+=1)a[f]=c.charCodeAt(f)&255;return a};this.byteArrayToString=Runtime.byteArrayToString;this.readFile=function(c,b,d){if(k.hasOwnProperty(c))d(null,
k[c]);else{var a=new XMLHttpRequest;a.open("GET",c,!0);a.onreadystatechange=function(){var f;4===a.readyState&&(0===a.status&&!a.responseText?d("File "+c+" is empty."):200===a.status||0===a.status?(f="binary"===b?"undefined"!==typeof VBArray?(new VBArray(a.responseBody)).toArray():e.byteArrayFromString(a.responseText,"binary"):a.responseText,k[c]=f,d(null,f)):d(a.responseText||a.statusText))};a.overrideMimeType&&("binary"!==b?a.overrideMimeType("text/plain; charset="+b):a.overrideMimeType("text/plain; charset=x-user-defined"));
try{a.send(null)}catch(f){d(f.message)}}};this.read=function(c,a,d,o){if(k.hasOwnProperty(c))o(null,k[c].slice(a,a+d));else{var f=new XMLHttpRequest;f.open("GET",c,!0);f.onreadystatechange=function(){var i;4===f.readyState&&(0===f.status&&!f.responseText?o("File "+c+" is empty."):200===f.status||0===f.status?(i="undefined"!==typeof VBArray?(new VBArray(f.responseBody)).toArray():e.byteArrayFromString(f.responseText,"binary"),k[c]=i,o(null,i.slice(a,a+d))):o(f.responseText||f.statusText))};f.overrideMimeType&&
f.overrideMimeType("text/plain; charset=x-user-defined");try{f.send(null)}catch(h){o(h.message)}}};this.readFileSync=function(c,a){var d=new XMLHttpRequest,o;d.open("GET",c,!1);d.overrideMimeType&&("binary"!==a?d.overrideMimeType("text/plain; charset="+a):d.overrideMimeType("text/plain; charset=x-user-defined"));try{if(d.send(null),200===d.status||0===d.status)o=d.responseText}catch(f){}return o};this.writeFile=function(c,a,d){k[c]=a;var o=new XMLHttpRequest;o.open("PUT",c,!0);o.onreadystatechange=
function(){4===o.readyState&&(0===o.status&&!o.responseText?d("File "+c+" is empty."):200<=o.status&&300>o.status||0===o.status?d(null):d("Status "+o.status+": "+o.responseText||o.statusText))};a=a.buffer&&!o.sendAsBinary?a.buffer:e.byteArrayToString(a,"binary");try{o.sendAsBinary?o.sendAsBinary(a):o.send(a)}catch(f){e.log("HUH? "+f+" "+a),d(f.message)}};this.deleteFile=function(c,a){var d=new XMLHttpRequest;d.open("DELETE",c,!0);d.onreadystatechange=function(){4===d.readyState&&(200>d.status&&300<=
d.status?a(d.responseText):a(null))};d.send(null)};this.loadXML=function(a,b){var d=new XMLHttpRequest;d.open("GET",a,!0);d.overrideMimeType&&d.overrideMimeType("text/xml");d.onreadystatechange=function(){4===d.readyState&&(0===d.status&&!d.responseText?b("File "+a+" is empty."):200===d.status||0===d.status?b(null,d.responseXML):b(d.responseText))};try{d.send(null)}catch(o){b(o.message)}};this.isFile=function(a,b){e.getFileSize(a,function(a){b(-1!==a)})};this.getFileSize=function(a,b){var d=new XMLHttpRequest;
d.open("HEAD",a,!0);d.onreadystatechange=function(){if(4===d.readyState){var a=d.getResponseHeader("Content-Length");a?b(parseInt(a,10)):b(-1)}};d.send(null)};this.log=m;this.setTimeout=function(a,b){setTimeout(function(){a()},b)};this.libraryPaths=function(){return["lib"]};this.setCurrentDirectory=function(){};this.type=function(){return"BrowserRuntime"};this.getDOMImplementation=function(){return window.document.implementation};this.exit=function(a){m("Calling exit with code "+a+", but exit() is not implemented.")};
this.getWindow=function(){return window}}
function NodeJSRuntime(){var g=require("fs"),m="";this.ByteArray=function(e){return new Buffer(e)};this.byteArrayFromArray=function(e){var k=new Buffer(e.length),a,c=e.length;for(a=0;a<c;a+=1)k[a]=e[a];return k};this.concatByteArrays=function(e,k){var a=new Buffer(e.length+k.length);e.copy(a,0,0);k.copy(a,e.length,0);return a};this.byteArrayFromString=function(e,k){return new Buffer(e,k)};this.byteArrayToString=function(e,k){return e.toString(k)};this.readFile=function(e,k,a){"binary"!==k?g.readFile(e,
k,a):g.readFile(e,null,a)};this.writeFile=function(e,k,a){g.writeFile(e,k,"binary",function(c){a(c||null)})};this.deleteFile=g.unlink;this.read=function(e,k,a,c){m&&(e=m+"/"+e);g.open(e,"r+",666,function(b,d){if(b)c(b);else{var o=new Buffer(a);g.read(d,o,0,a,k,function(a){g.close(d);c(a,o)})}})};this.readFileSync=function(e,k){return!k?"":g.readFileSync(e,k)};this.loadXML=function(){throw"Not implemented.";};this.isFile=function(e,k){m&&(e=m+"/"+e);g.stat(e,function(a,c){k(!a&&c.isFile())})};this.getFileSize=
function(e,k){m&&(e=m+"/"+e);g.stat(e,function(a,c){a?k(-1):k(c.size)})};this.log=function(e){process.stderr.write(e+"\n")};this.setTimeout=function(e,k){setTimeout(function(){e()},k)};this.libraryPaths=function(){return[__dirname]};this.setCurrentDirectory=function(e){m=e};this.currentDirectory=function(){return m};this.type=function(){return"NodeJSRuntime"};this.getDOMImplementation=function(){return null};this.exit=process.exit;this.getWindow=function(){return null}}
function RhinoRuntime(){var g=this,m=Packages.javax.xml.parsers.DocumentBuilderFactory.newInstance(),e,k,a="";m.setValidating(!1);m.setNamespaceAware(!0);m.setExpandEntityReferences(!1);m.setSchema(null);k=Packages.org.xml.sax.EntityResolver({resolveEntity:function(a,b){var d=new Packages.java.io.FileReader(b);return new Packages.org.xml.sax.InputSource(d)}});e=m.newDocumentBuilder();e.setEntityResolver(k);this.ByteArray=function(a){return[a]};this.byteArrayFromArray=function(a){return a};this.byteArrayFromString=
function(a){var b=[],d,o=a.length;for(d=0;d<o;d+=1)b[d]=a.charCodeAt(d)&255;return b};this.byteArrayToString=Runtime.byteArrayToString;this.concatByteArrays=function(a,b){return a.concat(b)};this.loadXML=function(a,b){var d=new Packages.java.io.File(a),o;try{o=e.parse(d)}catch(f){print(f);b(f);return}b(null,o)};this.readFile=function(a,b,d){var o=new Packages.java.io.File(a),f="binary"===b?"latin1":b;o.isFile()?(a=readFile(a,f),"binary"===b&&(a=g.byteArrayFromString(a,"binary")),d(null,a)):d(a+" is not a file.")};
this.writeFile=function(a,b,d){var a=new Packages.java.io.FileOutputStream(a),o,f=b.length;for(o=0;o<f;o+=1)a.write(b[o]);a.close();d(null)};this.deleteFile=function(a,b){(new Packages.java.io.File(a))["delete"]()?b(null):b("Could not delete "+a)};this.read=function(c,b,d,o){a&&(c=a+"/"+c);var f;f=c;var h="binary";(new Packages.java.io.File(f)).isFile()?("binary"===h&&(h="latin1"),f=readFile(f,h)):f=null;f?o(null,this.byteArrayFromString(f.substring(b,b+d),"binary")):o("Cannot read "+c)};this.readFileSync=
function(a,b){return!b?"":readFile(a,b)};this.isFile=function(c,b){a&&(c=a+"/"+c);var d=new Packages.java.io.File(c);b(d.isFile())};this.getFileSize=function(c,b){a&&(c=a+"/"+c);var d=new Packages.java.io.File(c);b(d.length())};this.log=print;this.setTimeout=function(a){a()};this.libraryPaths=function(){return["lib"]};this.setCurrentDirectory=function(c){a=c};this.currentDirectory=function(){return a};this.type=function(){return"RhinoRuntime"};this.getDOMImplementation=function(){return e.getDOMImplementation()};
this.exit=quit;this.getWindow=function(){return null}}var runtime=function(){return"undefined"!==typeof window?new BrowserRuntime(window.document.getElementById("logoutput")):"undefined"!==typeof require?new NodeJSRuntime:new RhinoRuntime}();
(function(){function g(e){var a=e[0],c;c=eval("if (typeof "+a+" === 'undefined') {eval('"+a+" = {};');}"+a);for(a=1;a<e.length-1;a+=1)c.hasOwnProperty(e[a])||(c=c[e[a]]={});return c[e[e.length-1]]}var m={},e={};runtime.loadClass=function(k){function a(a){var a=a.replace(".","/")+".js",b=runtime.libraryPaths(),c,h,i;runtime.currentDirectory&&b.push(runtime.currentDirectory());for(c=0;c<b.length;c+=1){h=b[c];if(!e.hasOwnProperty(h))if((i=runtime.readFileSync(b[c]+"/manifest.js","utf8"))&&i.length)try{e[h]=
eval(i)}catch(j){e[h]=null,runtime.log("Cannot load manifest for "+h+".")}else e[h]=null;if((h=e[h])&&h.indexOf&&-1!==h.indexOf(a))return b[c]+"/"+a}return null}if(!IS_COMPILED_CODE&&!m.hasOwnProperty(k)){var c=k.split("."),b;b=g(c);if(!b&&(b=function(c){var b,f;f=a(c);if(!f)throw c+" is not listed in any manifest.js.";try{b=runtime.readFileSync(f,"utf8")}catch(e){throw runtime.log("Error loading "+c+" "+e),e;}if(void 0===b)throw"Cannot load class "+c;try{b=eval(c+" = eval(code);")}catch(i){throw runtime.log("Error loading "+
c+" "+i),i;}return b}(k),!b||Runtime.getFunctionName(b)!==c[c.length-1]))throw runtime.log("Loaded code is not for "+c[c.length-1]),"Loaded code is not for "+c[c.length-1];m[k]=!0}}})();
(function(g){function m(e){if(e.length){var g=e[0];runtime.readFile(g,"utf8",function(a,c){function b(){var a;(a=eval(c))&&runtime.exit(a)}var d="";runtime.libraryPaths();-1!==g.indexOf("/")&&(d=g.substring(0,g.indexOf("/")));runtime.setCurrentDirectory(d);a?(runtime.log(a),runtime.exit(1)):b.apply(null,e)})}}g=Array.prototype.slice.call(g);"NodeJSRuntime"===runtime.type()?m(process.argv.slice(2)):"RhinoRuntime"===runtime.type()?m(g):m(g.slice(1))})("undefined"!==typeof arguments&&arguments);
// Input 2
core.Base64=function(){function i(a){var b=[],f,c=a.length;for(f=0;f<c;f+=1)b[f]=a.charCodeAt(f)&255;return b}function k(a){var b,f="",c,d=a.length-2;for(c=0;c<d;c+=3)b=a[c]<<16|a[c+1]<<8|a[c+2],f+=u[b>>>18],f+=u[b>>>12&63],f+=u[b>>>6&63],f+=u[b&63];c===d+1?(b=a[c]<<4,f+=u[b>>>6],f+=u[b&63],f+="=="):c===d&&(b=a[c]<<10|a[c+1]<<2,f+=u[b>>>12],f+=u[b>>>6&63],f+=u[b&63],f+="=");return f}function e(a){var a=a.replace(/[^A-Za-z0-9+\/]+/g,""),b=[],f=a.length%4,c,d=a.length,h;for(c=0;c<d;c+=4)h=(n[a.charAt(c)]||
0)<<18|(n[a.charAt(c+1)]||0)<<12|(n[a.charAt(c+2)]||0)<<6|(n[a.charAt(c+3)]||0),b.push(h>>16,h>>8&255,h&255);b.length-=[0,0,2,1][f];return b}function g(a){var b=[],f,c=a.length,d;for(f=0;f<c;f+=1)d=a[f],d<128?b.push(d):d<2048?b.push(192|d>>>6,128|d&63):b.push(224|d>>>12&15,128|d>>>6&63,128|d&63);return b}function a(a){var b=[],f,c=a.length,d,h,j;for(f=0;f<c;f+=1)d=a[f],d<128?b.push(d):(f+=1,h=a[f],d<224?b.push((d&31)<<6|h&63):(f+=1,j=a[f],b.push((d&15)<<12|(h&63)<<6|j&63)));return b}function b(a){return k(i(a))}
function h(a){return String.fromCharCode.apply(String,e(a))}function c(b){return a(i(b))}function d(b){return String.fromCharCode.apply(String,a(b))}function f(a,b,f){for(var c="",d,h,j;b<f;b+=1)d=a.charCodeAt(b)&255,d<128?c+=String.fromCharCode(d):(b+=1,h=a.charCodeAt(b)&255,d<224?c+=String.fromCharCode((d&31)<<6|h&63):(b+=1,j=a.charCodeAt(b)&255,c+=String.fromCharCode((d&15)<<12|(h&63)<<6|j&63)));return c}function j(a,b){function c(){var e=j+d;if(e>a.length)e=a.length;h+=f(a,j,e);j=e;e=j===a.length;
b(h,e)&&!e&&runtime.setTimeout(c,0)}var d=1E5,h="",j=0;a.length<d?b(f(a,0,a.length),true):(typeof a!=="string"&&(a=a.slice()),c())}function p(a){return g(i(a))}function m(a){return String.fromCharCode.apply(String,g(a))}function l(a){return String.fromCharCode.apply(String,g(i(a)))}var u="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";(function(){var a=[],b,f="A".charCodeAt(0),c="a".charCodeAt(0),d="0".charCodeAt(0);for(b=0;b<26;b+=1)a.push(f+b);for(b=0;b<26;b+=1)a.push(c+b);for(b=
0;b<10;b+=1)a.push(d+b);a.push("+".charCodeAt(0));a.push("/".charCodeAt(0));return a})();var n=function(a){var b={},f,c;for(f=0,c=a.length;f<c;f+=1)b[a.charAt(f)]=f;return b}(u),q,r,y,C;(y=runtime.getWindow()&&runtime.getWindow().btoa)?q=function(a){return y(l(a))}:(y=b,q=function(a){return k(p(a))});(C=runtime.getWindow()&&runtime.getWindow().atob)?r=function(a){a=C(a);return f(a,0,a.length)}:(C=h,r=function(a){return d(e(a))});return function(){this.convertByteArrayToBase64=this.convertUTF8ArrayToBase64=
k;this.convertBase64ToByteArray=this.convertBase64ToUTF8Array=e;this.convertUTF16ArrayToByteArray=this.convertUTF16ArrayToUTF8Array=g;this.convertByteArrayToUTF16Array=this.convertUTF8ArrayToUTF16Array=a;this.convertUTF8StringToBase64=b;this.convertBase64ToUTF8String=h;this.convertUTF8StringToUTF16Array=c;this.convertByteArrayToUTF16String=this.convertUTF8ArrayToUTF16String=d;this.convertUTF8StringToUTF16String=j;this.convertUTF16StringToByteArray=this.convertUTF16StringToUTF8Array=p;this.convertUTF16ArrayToUTF8String=
m;this.convertUTF16StringToUTF8String=l;this.convertUTF16StringToBase64=q;this.convertBase64ToUTF16String=r;this.fromBase64=h;this.toBase64=b;this.atob=C;this.btoa=y;this.utob=l;this.btou=j;this.encode=q;this.encodeURI=function(a){return q(a).replace(/[+\/]/g,function(a){return a==="+"?"-":"_"}).replace(/\\=+$/,"")};this.decode=function(a){return r(a.replace(/[\-_]/g,function(a){return a==="-"?"+":"/"}))}}}();
core.Base64=function(){function g(a){var c=[],b,d=a.length;for(b=0;b<d;b+=1)c[b]=a.charCodeAt(b)&255;return c}function m(a){var b,c="",d,f=a.length-2;for(d=0;d<f;d+=3)b=a[d]<<16|a[d+1]<<8|a[d+2],c+=x[b>>>18],c+=x[b>>>12&63],c+=x[b>>>6&63],c+=x[b&63];d===f+1?(b=a[d]<<4,c+=x[b>>>6],c+=x[b&63],c+="=="):d===f&&(b=a[d]<<10|a[d+1]<<2,c+=x[b>>>12],c+=x[b>>>6&63],c+=x[b&63],c+="=");return c}function e(a){var a=a.replace(/[^A-Za-z0-9+\/]+/g,""),c=[],b=a.length%4,d,f=a.length,e;for(d=0;d<f;d+=4)e=(p[a.charAt(d)]||
0)<<18|(p[a.charAt(d+1)]||0)<<12|(p[a.charAt(d+2)]||0)<<6|(p[a.charAt(d+3)]||0),c.push(e>>16,e>>8&255,e&255);c.length-=[0,0,2,1][b];return c}function k(a){var c=[],b,d=a.length,f;for(b=0;b<d;b+=1)f=a[b],128>f?c.push(f):2048>f?c.push(192|f>>>6,128|f&63):c.push(224|f>>>12&15,128|f>>>6&63,128|f&63);return c}function a(a){var c=[],b,d=a.length,f,e,l;for(b=0;b<d;b+=1)f=a[b],128>f?c.push(f):(b+=1,e=a[b],224>f?c.push((f&31)<<6|e&63):(b+=1,l=a[b],c.push((f&15)<<12|(e&63)<<6|l&63)));return c}function c(a){return m(g(a))}
function b(a){return String.fromCharCode.apply(String,e(a))}function d(c){return a(g(c))}function o(c){for(var c=a(c),b="",d=0;d<c.length;)b+=String.fromCharCode.apply(String,c.slice(d,d+45E3)),d+=45E3;return b}function f(a,c,b){var d="",f,e,l;for(l=c;l<b;l+=1)c=a.charCodeAt(l)&255,128>c?d+=String.fromCharCode(c):(l+=1,f=a.charCodeAt(l)&255,224>c?d+=String.fromCharCode((c&31)<<6|f&63):(l+=1,e=a.charCodeAt(l)&255,d+=String.fromCharCode((c&15)<<12|(f&63)<<6|e&63)));return d}function h(a,c){function b(){var l=
j+d;l>a.length&&(l=a.length);e+=f(a,j,l);j=l;l=j===a.length;c(e,l)&&!l&&runtime.setTimeout(b,0)}var d=1E5,e="",j=0;a.length<d?c(f(a,0,a.length),!0):("string"!==typeof a&&(a=a.slice()),b())}function i(a){return k(g(a))}function j(a){return String.fromCharCode.apply(String,k(a))}function n(a){return String.fromCharCode.apply(String,k(g(a)))}var x="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";(function(){var a=[],c;for(c=0;26>c;c+=1)a.push(65+c);for(c=0;26>c;c+=1)a.push(97+c);for(c=
0;10>c;c+=1)a.push(48+c);a.push(43);a.push(47);return a})();var p=function(a){var c={},b,d;for(b=0,d=a.length;b<d;b+=1)c[a.charAt(b)]=b;return c}(x),t,r,z,s;(z=runtime.getWindow()&&runtime.getWindow().btoa)?t=function(a){return z(n(a))}:(z=c,t=function(a){return m(i(a))});(s=runtime.getWindow()&&runtime.getWindow().atob)?r=function(a){a=s(a);return f(a,0,a.length)}:(s=b,r=function(a){return o(e(a))});return function(){this.convertByteArrayToBase64=this.convertUTF8ArrayToBase64=m;this.convertBase64ToByteArray=
this.convertBase64ToUTF8Array=e;this.convertUTF16ArrayToByteArray=this.convertUTF16ArrayToUTF8Array=k;this.convertByteArrayToUTF16Array=this.convertUTF8ArrayToUTF16Array=a;this.convertUTF8StringToBase64=c;this.convertBase64ToUTF8String=b;this.convertUTF8StringToUTF16Array=d;this.convertByteArrayToUTF16String=this.convertUTF8ArrayToUTF16String=o;this.convertUTF8StringToUTF16String=h;this.convertUTF16StringToByteArray=this.convertUTF16StringToUTF8Array=i;this.convertUTF16ArrayToUTF8String=j;this.convertUTF16StringToUTF8String=
n;this.convertUTF16StringToBase64=t;this.convertBase64ToUTF16String=r;this.fromBase64=b;this.toBase64=c;this.atob=s;this.btoa=z;this.utob=n;this.btou=h;this.encode=t;this.encodeURI=function(a){return t(a).replace(/[+\/]/g,function(a){return"+"===a?"-":"_"}).replace(/\\=+$/,"")};this.decode=function(a){return r(a.replace(/[\-_]/g,function(a){return"-"===a?"+":"/"}))}}}();
// Input 3
core.RawDeflate=function(){function i(){this.dl=this.fc=0}function k(){this.extra_bits=this.static_tree=this.dyn_tree=null;this.max_code=this.max_length=this.elems=this.extra_base=0}function e(a,b,f,c){this.good_length=a;this.max_lazy=b;this.nice_length=f;this.max_chain=c}function g(){this.next=null;this.len=0;this.ptr=Array(a);this.off=0}var a=8192,b,h,c,d,f=null,j,p,m,l,u,n,q,r,y,C,s,v,E,B,z,G,o,x,t,w,L,Q,R,X,A,J,H,S,K,F,D,U,M,I,O,T,N,$,Y,oa,da,ea,V,fa,pa,aa,ga,Z,ha,ia,qa,ra=[0,0,0,0,0,0,0,0,1,
1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],ba=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],Ha=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],va=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],ja;ja=[new e(0,0,0,0),new e(4,4,8,4),new e(4,5,16,8),new e(4,6,32,32),new e(4,4,16,16),new e(8,16,32,32),new e(8,16,128,128),new e(8,32,128,256),new e(32,128,258,1024),new e(32,258,258,4096)];var ka=function(d){f[p+j++]=d;if(p+j==a&&j!=0){var e;b!=null?(d=b,b=b.next):d=new g;d.next=null;d.len=
d.off=0;h==null?h=c=d:c=c.next=d;d.len=j-p;for(e=0;e<d.len;e++)d.ptr[e]=f[p+e];j=p=0}},la=function(b){b&=65535;p+j<a-2?(f[p+j++]=b&255,f[p+j++]=b>>>8):(ka(b&255),ka(b>>>8))},ma=function(){s=(s<<5^l[o+3-1]&255)&8191;v=q[32768+s];q[o&32767]=v;q[32768+s]=o},W=function(a,b){P(b[a].fc,b[a].dl)},wa=function(a,b,f){return a[b].fc<a[f].fc||a[b].fc==a[f].fc&&N[b]<=N[f]},xa=function(a,b,f){var c;for(c=0;c<f&&qa<ia.length;c++)a[b+c]=ia.charCodeAt(qa++)&255;return c},ya=function(a){var b=L,f=o,c,d=G,h=o>32506?
o-32506:0,j=o+258,e=l[f+d-1],g=l[f+d];G>=X&&(b>>=2);do if(c=a,!(l[c+d]!=g||l[c+d-1]!=e||l[c]!=l[f]||l[++c]!=l[f+1])){f+=2;c++;do;while(l[++f]==l[++c]&&l[++f]==l[++c]&&l[++f]==l[++c]&&l[++f]==l[++c]&&l[++f]==l[++c]&&l[++f]==l[++c]&&l[++f]==l[++c]&&l[++f]==l[++c]&&f<j);c=258-(j-f);f=j-258;if(c>d){x=a;d=c;if(c>=258)break;e=l[f+d-1];g=l[f+d]}}while((a=q[a&32767])>h&&--b!=0);return d},sa=function(){var a,b,f=65536-w-o;if(f==-1)f--;else if(o>=65274){for(a=0;a<32768;a++)l[a]=l[a+32768];x-=32768;o-=32768;
C-=32768;for(a=0;a<8192;a++)b=q[32768+a],q[32768+a]=b>=32768?b-32768:0;for(a=0;a<32768;a++)b=q[a],q[a]=b>=32768?b-32768:0;f+=32768}t||(a=xa(l,o+w,f),a<=0?t=true:w+=a)},Ia=function(a,b,f){var c;if(!d){if(!t){y=r=0;var e,g;if(S[0].dl==0){F.dyn_tree=A;F.static_tree=H;F.extra_bits=ra;F.extra_base=257;F.elems=286;F.max_length=15;F.max_code=0;D.dyn_tree=J;D.static_tree=S;D.extra_bits=ba;D.extra_base=0;D.elems=30;D.max_length=15;D.max_code=0;U.dyn_tree=K;U.static_tree=null;U.extra_bits=Ha;U.extra_base=0;
U.elems=19;U.max_length=7;for(g=e=U.max_code=0;g<28;g++){oa[g]=e;for(c=0;c<1<<ra[g];c++)$[e++]=g}$[e-1]=g;for(g=e=0;g<16;g++){da[g]=e;for(c=0;c<1<<ba[g];c++)Y[e++]=g}for(e>>=7;g<30;g++){da[g]=e<<7;for(c=0;c<1<<ba[g]-7;c++)Y[256+e++]=g}for(c=0;c<=15;c++)M[c]=0;for(c=0;c<=143;)H[c++].dl=8,M[8]++;for(;c<=255;)H[c++].dl=9,M[9]++;for(;c<=279;)H[c++].dl=7,M[7]++;for(;c<=287;)H[c++].dl=8,M[8]++;za(H,287);for(c=0;c<30;c++)S[c].dl=5,S[c].fc=Aa(c,5);Ba()}for(c=0;c<8192;c++)q[32768+c]=0;Q=ja[R].max_lazy;X=ja[R].good_length;
L=ja[R].max_chain;C=o=0;w=xa(l,0,65536);if(w<=0)t=true,w=0;else{for(t=false;w<262&&!t;)sa();for(c=s=0;c<2;c++)s=(s<<5^l[c]&255)&8191}h=null;p=j=0;R<=3?(G=2,z=0):(z=2,B=0);m=false}d=true;if(w==0)return m=true,0}if((c=Ca(a,b,f))==f)return f;if(m)return c;if(R<=3)for(;w!=0&&h==null;){ma();v!=0&&o-v<=32506&&(z=ya(v),z>w&&(z=w));if(z>=3)if(g=ca(o-x,z-3),w-=z,z<=Q){z--;do o++,ma();while(--z!=0);o++}else o+=z,z=0,s=l[o]&255,s=(s<<5^l[o+1]&255)&8191;else g=ca(0,l[o]&255),w--,o++;g&&(na(0),C=o);for(;w<262&&
!t;)sa()}else for(;w!=0&&h==null;){ma();G=z;E=x;z=2;v!=0&&G<Q&&o-v<=32506&&(z=ya(v),z>w&&(z=w),z==3&&o-x>4096&&z--);if(G>=3&&z<=G){g=ca(o-1-E,G-3);w-=G-1;G-=2;do o++,ma();while(--G!=0);B=0;z=2;o++;g&&(na(0),C=o)}else B!=0?ca(0,l[o-1]&255)&&(na(0),C=o):B=1,o++,w--;for(;w<262&&!t;)sa()}w==0&&(B!=0&&ca(0,l[o-1]&255),na(1),m=true);return c+Ca(a,c+b,f-c)},Ca=function(a,c,d){var e,g,q;for(e=0;h!=null&&e<d;){g=d-e;if(g>h.len)g=h.len;for(q=0;q<g;q++)a[c+e+q]=h.ptr[h.off+q];h.off+=g;h.len-=g;e+=g;if(h.len==
0)g=h,h=h.next,g.next=b,b=g}if(e==d)return e;if(p<j){g=d-e;g>j-p&&(g=j-p);for(q=0;q<g;q++)a[c+e+q]=f[p+q];p+=g;e+=g;j==p&&(j=p=0)}return e},Ba=function(){var a;for(a=0;a<286;a++)A[a].fc=0;for(a=0;a<30;a++)J[a].fc=0;for(a=0;a<19;a++)K[a].fc=0;A[256].fc=1;aa=V=fa=pa=Z=ha=0;ga=1},ta=function(a,b){for(var c=I[b],f=b<<1;f<=O;){f<O&&wa(a,I[f+1],I[f])&&f++;if(wa(a,c,I[f]))break;I[b]=I[f];b=f;f<<=1}I[b]=c},za=function(a,b){var c=Array(16),f=0,d;for(d=1;d<=15;d++)f=f+M[d-1]<<1,c[d]=f;for(f=0;f<=b;f++)if(d=
a[f].dl,d!=0)a[f].fc=Aa(c[d]++,d)},ua=function(a){var b=a.dyn_tree,c=a.static_tree,f=a.elems,d,e=-1,h=f;O=0;T=573;for(d=0;d<f;d++)b[d].fc!=0?(I[++O]=e=d,N[d]=0):b[d].dl=0;for(;O<2;)d=I[++O]=e<2?++e:0,b[d].fc=1,N[d]=0,Z--,c!=null&&(ha-=c[d].dl);a.max_code=e;for(d=O>>1;d>=1;d--)ta(b,d);do d=I[1],I[1]=I[O--],ta(b,1),c=I[1],I[--T]=d,I[--T]=c,b[h].fc=b[d].fc+b[c].fc,N[h]=N[d]>N[c]+1?N[d]:N[c]+1,b[d].dl=b[c].dl=h,I[1]=h++,ta(b,1);while(O>=2);I[--T]=I[1];h=a.dyn_tree;d=a.extra_bits;var f=a.extra_base,c=
a.max_code,g=a.max_length,j=a.static_tree,q,t,o,r,i=0;for(t=0;t<=15;t++)M[t]=0;h[I[T]].dl=0;for(a=T+1;a<573;a++)if(q=I[a],t=h[h[q].dl].dl+1,t>g&&(t=g,i++),h[q].dl=t,!(q>c))M[t]++,o=0,q>=f&&(o=d[q-f]),r=h[q].fc,Z+=r*(t+o),j!=null&&(ha+=r*(j[q].dl+o));if(i!=0){do{for(t=g-1;M[t]==0;)t--;M[t]--;M[t+1]+=2;M[g]--;i-=2}while(i>0);for(t=g;t!=0;t--)for(q=M[t];q!=0;)if(d=I[--a],!(d>c)){if(h[d].dl!=t)Z+=(t-h[d].dl)*h[d].fc,h[d].fc=t;q--}}za(b,e)},Da=function(a,b){var c,f=-1,d,e=a[0].dl,h=0,g=7,j=4;e==0&&(g=
138,j=3);a[b+1].dl=65535;for(c=0;c<=b;c++)d=e,e=a[c+1].dl,++h<g&&d==e||(h<j?K[d].fc+=h:d!=0?(d!=f&&K[d].fc++,K[16].fc++):h<=10?K[17].fc++:K[18].fc++,h=0,f=d,e==0?(g=138,j=3):d==e?(g=6,j=3):(g=7,j=4))},Ea=function(a,b){var c,f=-1,d,e=a[0].dl,h=0,g=7,j=4;e==0&&(g=138,j=3);for(c=0;c<=b;c++)if(d=e,e=a[c+1].dl,!(++h<g&&d==e)){if(h<j){do W(d,K);while(--h!=0)}else d!=0?(d!=f&&(W(d,K),h--),W(16,K),P(h-3,2)):h<=10?(W(17,K),P(h-3,3)):(W(18,K),P(h-11,7));h=0;f=d;e==0?(g=138,j=3):d==e?(g=6,j=3):(g=7,j=4)}},na=
function(a){var b,c,f,d;d=o-C;ea[pa]=aa;ua(F);ua(D);Da(A,F.max_code);Da(J,D.max_code);ua(U);for(f=18;f>=3;f--)if(K[va[f]].dl!=0)break;Z+=3*(f+1)+14;b=Z+3+7>>3;c=ha+3+7>>3;c<=b&&(b=c);if(d+4<=b&&C>=0){P(0+a,3);Fa();la(d);la(~d);for(f=0;f<d;f++)ka(l[C+f])}else if(c==b)P(2+a,3),Ga(H,S);else{P(4+a,3);d=F.max_code+1;b=D.max_code+1;f+=1;P(d-257,5);P(b-1,5);P(f-4,4);for(c=0;c<f;c++)P(K[va[c]].dl,3);Ea(A,d-1);Ea(J,b-1);Ga(A,J)}Ba();a!=0&&Fa()},ca=function(a,b){n[V++]=b;a==0?A[b].fc++:(a--,A[$[b]+256+1].fc++,
J[(a<256?Y[a]:Y[256+(a>>7)])&255].fc++,u[fa++]=a,aa|=ga);ga<<=1;(V&7)==0&&(ea[pa++]=aa,aa=0,ga=1);if(R>2&&(V&4095)==0){var c=V*8,f=o-C,d;for(d=0;d<30;d++)c+=J[d].fc*(5+ba[d]);c>>=3;if(fa<parseInt(V/2,10)&&c<parseInt(f/2,10))return true}return V==8191||fa==8192},Ga=function(a,b){var c,f=0,d=0,h=0,e=0,g,j;if(V!=0){do(f&7)==0&&(e=ea[h++]),c=n[f++]&255,(e&1)==0?W(c,a):(g=$[c],W(g+256+1,a),j=ra[g],j!=0&&(c-=oa[g],P(c,j)),c=u[d++],g=(c<256?Y[c]:Y[256+(c>>7)])&255,W(g,b),j=ba[g],j!=0&&(c-=da[g],P(c,j))),
e>>=1;while(f<V)}W(256,a)},P=function(a,c){y>16-c?(r|=a<<y,la(r),r=a>>16-y,y+=c-16):(r|=a<<y,y+=c)},Aa=function(a,c){var b=0;do b|=a&1,a>>=1,b<<=1;while(--c>0);return b>>1},Fa=function(){y>8?la(r):y>0&&ka(r);y=r=0};this.deflate=function(e,g){var j,o;ia=e;qa=0;typeof g=="undefined"&&(g=6);(j=g)?j<1?j=1:j>9&&(j=9):j=6;R=j;t=d=false;if(f==null){b=h=c=null;f=Array(a);l=Array(65536);u=Array(8192);n=Array(32832);q=Array(65536);A=Array(573);for(j=0;j<573;j++)A[j]=new i;J=Array(61);for(j=0;j<61;j++)J[j]=
new i;H=Array(288);for(j=0;j<288;j++)H[j]=new i;S=Array(30);for(j=0;j<30;j++)S[j]=new i;K=Array(39);for(j=0;j<39;j++)K[j]=new i;F=new k;D=new k;U=new k;M=Array(16);I=Array(573);N=Array(573);$=Array(256);Y=Array(512);oa=Array(29);da=Array(30);ea=Array(1024)}for(var r=Array(1024),w=[];(j=Ia(r,0,r.length))>0;){var x=Array(j);for(o=0;o<j;o++)x[o]=String.fromCharCode(r[o]);w[w.length]=x.join("")}ia=null;return w.join("")}};
core.RawDeflate=function(){function g(){this.dl=this.fc=0}function m(){this.extra_bits=this.static_tree=this.dyn_tree=null;this.max_code=this.max_length=this.elems=this.extra_base=0}function e(a,c,b,d){this.good_length=a;this.max_lazy=c;this.nice_length=b;this.max_chain=d}function k(){this.next=null;this.len=0;this.ptr=[];this.ptr.length=a;this.off=0}var a=8192,c,b,d,o,f=null,h,i,j,n,x,p,t,r,z,s,q,u,E,C,w,G,l,v,y,B,M,F,R,S,A,K,I,P,L,H,D,U,N,J,V,aa,T,ba,Q,$,W,ea,X,ia,pa,ca,fa,Y,da,ja,qa,ra=[0,0,0,
0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],ga=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],Ha=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],va=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],ka;ka=[new e(0,0,0,0),new e(4,4,8,4),new e(4,5,16,8),new e(4,6,32,32),new e(4,4,16,16),new e(8,16,32,32),new e(8,16,128,128),new e(8,32,128,256),new e(32,128,258,1024),new e(32,258,258,4096)];var la=function(l){f[i+h++]=l;if(i+h===a){var e;if(0!==h){null!==c?(l=c,c=c.next):l=new k;
l.next=null;l.len=l.off=0;null===b?b=d=l:d=d.next=l;l.len=h-i;for(e=0;e<l.len;e++)l.ptr[e]=f[i+e];h=i=0}}},ma=function(c){c&=65535;i+h<a-2?(f[i+h++]=c&255,f[i+h++]=c>>>8):(la(c&255),la(c>>>8))},na=function(){q=(q<<5^n[l+3-1]&255)&8191;u=t[32768+q];t[l&32767]=u;t[32768+q]=l},O=function(a,c){z>16-c?(r|=a<<z,ma(r),r=a>>16-z,z+=c-16):(r|=a<<z,z+=c)},Z=function(a,c){O(c[a].fc,c[a].dl)},wa=function(a,c,b){return a[c].fc<a[b].fc||a[c].fc===a[b].fc&&T[c]<=T[b]},xa=function(a,c,b){var d;for(d=0;d<b&&qa<ja.length;d++)a[c+
d]=ja.charCodeAt(qa++)&255;return d},sa=function(){var a,c,b=65536-B-l;if(-1===b)b--;else if(65274<=l){for(a=0;32768>a;a++)n[a]=n[a+32768];v-=32768;l-=32768;s-=32768;for(a=0;8192>a;a++)c=t[32768+a],t[32768+a]=32768<=c?c-32768:0;for(a=0;32768>a;a++)c=t[a],t[a]=32768<=c?c-32768:0;b+=32768}y||(a=xa(n,l+B,b),0>=a?y=!0:B+=a)},ya=function(a){var c=M,b=l,d,f=G,e=32506<l?l-32506:0,j=l+258,i=n[b+f-1],q=n[b+f];G>=S&&(c>>=2);do if(d=a,!(n[d+f]!==q||n[d+f-1]!==i||n[d]!==n[b]||n[++d]!==n[b+1])){b+=2;d++;do++b;
while(n[b]===n[++d]&&n[++b]===n[++d]&&n[++b]===n[++d]&&n[++b]===n[++d]&&n[++b]===n[++d]&&n[++b]===n[++d]&&n[++b]===n[++d]&&n[++b]===n[++d]&&b<j);d=258-(j-b);b=j-258;if(d>f){v=a;f=d;if(258<=d)break;i=n[b+f-1];q=n[b+f]}}while((a=t[a&32767])>e&&0!==--c);return f},ha=function(a,c){p[X++]=c;0===a?A[c].fc++:(a--,A[ba[c]+256+1].fc++,K[(256>a?Q[a]:Q[256+(a>>7)])&255].fc++,x[ia++]=a,ca|=fa);fa<<=1;0===(X&7)&&(ea[pa++]=ca,ca=0,fa=1);if(2<R&&0===(X&4095)){var b=8*X,d=l-s,f;for(f=0;30>f;f++)b+=K[f].fc*(5+ga[f]);
b>>=3;if(ia<parseInt(X/2,10)&&b<parseInt(d/2,10))return!0}return 8191===X||8192===ia},ta=function(a,c){for(var b=J[c],d=c<<1;d<=V;){d<V&&wa(a,J[d+1],J[d])&&d++;if(wa(a,b,J[d]))break;J[c]=J[d];c=d;d<<=1}J[c]=b},za=function(a,c){var b=0;do b|=a&1,a>>=1,b<<=1;while(0<--c);return b>>1},Aa=function(a,c){var b=[];b.length=16;var d=0,f;for(f=1;15>=f;f++)d=d+N[f-1]<<1,b[f]=d;for(d=0;d<=c;d++)f=a[d].dl,0!==f&&(a[d].fc=za(b[f]++,f))},ua=function(a){var c=a.dyn_tree,b=a.static_tree,d=a.elems,f,l=-1,e=d;V=0;
aa=573;for(f=0;f<d;f++)0!==c[f].fc?(J[++V]=l=f,T[f]=0):c[f].dl=0;for(;2>V;)f=J[++V]=2>l?++l:0,c[f].fc=1,T[f]=0,Y--,null!==b&&(da-=b[f].dl);a.max_code=l;for(f=V>>1;1<=f;f--)ta(c,f);do f=J[1],J[1]=J[V--],ta(c,1),b=J[1],J[--aa]=f,J[--aa]=b,c[e].fc=c[f].fc+c[b].fc,T[e]=T[f]>T[b]+1?T[f]:T[b]+1,c[f].dl=c[b].dl=e,J[1]=e++,ta(c,1);while(2<=V);J[--aa]=J[1];e=a.dyn_tree;f=a.extra_bits;var d=a.extra_base,b=a.max_code,j=a.max_length,i=a.static_tree,q,h,o,s,v=0;for(h=0;15>=h;h++)N[h]=0;e[J[aa]].dl=0;for(a=aa+
1;573>a;a++)q=J[a],h=e[e[q].dl].dl+1,h>j&&(h=j,v++),e[q].dl=h,q>b||(N[h]++,o=0,q>=d&&(o=f[q-d]),s=e[q].fc,Y+=s*(h+o),null!==i&&(da+=s*(i[q].dl+o)));if(0!==v){do{for(h=j-1;0===N[h];)h--;N[h]--;N[h+1]+=2;N[j]--;v-=2}while(0<v);for(h=j;0!==h;h--)for(q=N[h];0!==q;)f=J[--a],f>b||(e[f].dl!==h&&(Y+=(h-e[f].dl)*e[f].fc,e[f].fc=h),q--)}Aa(c,l)},Ba=function(a,c){var b,d=-1,f,l=a[0].dl,e=0,h=7,j=4;0===l&&(h=138,j=3);a[c+1].dl=65535;for(b=0;b<=c;b++)f=l,l=a[b+1].dl,++e<h&&f===l||(e<j?L[f].fc+=e:0!==f?(f!==d&&
L[f].fc++,L[16].fc++):10>=e?L[17].fc++:L[18].fc++,e=0,d=f,0===l?(h=138,j=3):f===l?(h=6,j=3):(h=7,j=4))},Ca=function(){8<z?ma(r):0<z&&la(r);z=r=0},Da=function(a,c){var b,d=0,f=0,l=0,e=0,h,j;if(0!==X){do 0===(d&7)&&(e=ea[l++]),b=p[d++]&255,0===(e&1)?Z(b,a):(h=ba[b],Z(h+256+1,a),j=ra[h],0!==j&&(b-=$[h],O(b,j)),b=x[f++],h=(256>b?Q[b]:Q[256+(b>>7)])&255,Z(h,c),j=ga[h],0!==j&&(b-=W[h],O(b,j))),e>>=1;while(d<X)}Z(256,a)},Ea=function(a,c){var b,d=-1,f,l=a[0].dl,e=0,h=7,j=4;0===l&&(h=138,j=3);for(b=0;b<=c;b++)if(f=
l,l=a[b+1].dl,!(++e<h&&f===l)){if(e<j){do Z(f,L);while(0!==--e)}else 0!==f?(f!==d&&(Z(f,L),e--),Z(16,L),O(e-3,2)):10>=e?(Z(17,L),O(e-3,3)):(Z(18,L),O(e-11,7));e=0;d=f;0===l?(h=138,j=3):f===l?(h=6,j=3):(h=7,j=4)}},Fa=function(){var a;for(a=0;286>a;a++)A[a].fc=0;for(a=0;30>a;a++)K[a].fc=0;for(a=0;19>a;a++)L[a].fc=0;A[256].fc=1;ca=X=ia=pa=Y=da=0;fa=1},oa=function(a){var c,b,d,f;f=l-s;ea[pa]=ca;ua(H);ua(D);Ba(A,H.max_code);Ba(K,D.max_code);ua(U);for(d=18;3<=d&&!(0!==L[va[d]].dl);d--);Y+=3*(d+1)+14;c=
Y+3+7>>3;b=da+3+7>>3;b<=c&&(c=b);if(f+4<=c&&0<=s){O(0+a,3);Ca();ma(f);ma(~f);for(d=0;d<f;d++)la(n[s+d])}else if(b===c)O(2+a,3),Da(I,P);else{O(4+a,3);f=H.max_code+1;c=D.max_code+1;d+=1;O(f-257,5);O(c-1,5);O(d-4,4);for(b=0;b<d;b++)O(L[va[b]].dl,3);Ea(A,f-1);Ea(K,c-1);Da(A,K)}Fa();0!==a&&Ca()},Ga=function(a,d,l){var e,j,q;for(e=0;null!==b&&e<l;){j=l-e;j>b.len&&(j=b.len);for(q=0;q<j;q++)a[d+e+q]=b.ptr[b.off+q];b.off+=j;b.len-=j;e+=j;0===b.len&&(j=b,b=b.next,j.next=c,c=j)}if(e===l)return e;if(i<h){j=l-
e;j>h-i&&(j=h-i);for(q=0;q<j;q++)a[d+e+q]=f[i+q];i+=j;e+=j;h===i&&(h=i=0)}return e},Ia=function(a,c,d){var f;if(!o){if(!y){z=r=0;var e,g;if(0===P[0].dl){H.dyn_tree=A;H.static_tree=I;H.extra_bits=ra;H.extra_base=257;H.elems=286;H.max_length=15;H.max_code=0;D.dyn_tree=K;D.static_tree=P;D.extra_bits=ga;D.extra_base=0;D.elems=30;D.max_length=15;D.max_code=0;U.dyn_tree=L;U.static_tree=null;U.extra_bits=Ha;U.extra_base=0;U.elems=19;U.max_length=7;for(g=e=U.max_code=0;28>g;g++){$[g]=e;for(f=0;f<1<<ra[g];f++)ba[e++]=
g}ba[e-1]=g;for(g=e=0;16>g;g++){W[g]=e;for(f=0;f<1<<ga[g];f++)Q[e++]=g}for(e>>=7;30>g;g++){W[g]=e<<7;for(f=0;f<1<<ga[g]-7;f++)Q[256+e++]=g}for(f=0;15>=f;f++)N[f]=0;for(f=0;143>=f;)I[f++].dl=8,N[8]++;for(;255>=f;)I[f++].dl=9,N[9]++;for(;279>=f;)I[f++].dl=7,N[7]++;for(;287>=f;)I[f++].dl=8,N[8]++;Aa(I,287);for(f=0;30>f;f++)P[f].dl=5,P[f].fc=za(f,5);Fa()}for(f=0;8192>f;f++)t[32768+f]=0;F=ka[R].max_lazy;S=ka[R].good_length;M=ka[R].max_chain;s=l=0;B=xa(n,0,65536);if(0>=B)y=!0,B=0;else{for(y=!1;262>B&&!y;)sa();
for(f=q=0;2>f;f++)q=(q<<5^n[f]&255)&8191}b=null;i=h=0;3>=R?(G=2,w=0):(w=2,C=0);j=!1}o=!0;if(0===B)return j=!0,0}if((f=Ga(a,c,d))===d)return d;if(j)return f;if(3>=R)for(;0!==B&&null===b;){na();0!==u&&32506>=l-u&&(w=ya(u),w>B&&(w=B));if(3<=w)if(g=ha(l-v,w-3),B-=w,w<=F){w--;do l++,na();while(0!==--w);l++}else l+=w,w=0,q=n[l]&255,q=(q<<5^n[l+1]&255)&8191;else g=ha(0,n[l]&255),B--,l++;g&&(oa(0),s=l);for(;262>B&&!y;)sa()}else for(;0!==B&&null===b;){na();G=w;E=v;w=2;0!==u&&G<F&&32506>=l-u&&(w=ya(u),w>B&&
(w=B),3===w&&4096<l-v&&w--);if(3<=G&&w<=G){g=ha(l-1-E,G-3);B-=G-1;G-=2;do l++,na();while(0!==--G);C=0;w=2;l++;g&&(oa(0),s=l)}else 0!==C?ha(0,n[l-1]&255)&&(oa(0),s=l):C=1,l++,B--;for(;262>B&&!y;)sa()}0===B&&(0!==C&&ha(0,n[l-1]&255),oa(1),j=!0);return f+Ga(a,f+c,d-f)};this.deflate=function(e,l){var j,h;ja=e;qa=0;"undefined"===typeof l&&(l=6);(j=l)?1>j?j=1:9<j&&(j=9):j=6;R=j;y=o=!1;if(null===f){c=b=d=null;f=[];f.length=a;n=[];n.length=65536;x=[];x.length=8192;p=[];p.length=32832;t=[];t.length=65536;
A=[];A.length=573;for(j=0;573>j;j++)A[j]=new g;K=[];K.length=61;for(j=0;61>j;j++)K[j]=new g;I=[];I.length=288;for(j=0;288>j;j++)I[j]=new g;P=[];P.length=30;for(j=0;30>j;j++)P[j]=new g;L=[];L.length=39;for(j=0;39>j;j++)L[j]=new g;H=new m;D=new m;U=new m;N=[];N.length=16;J=[];J.length=573;T=[];T.length=573;ba=[];ba.length=256;Q=[];Q.length=512;$=[];$.length=29;W=[];W.length=30;ea=[];ea.length=1024}for(var q=Array(1024),i=[];0<(j=Ia(q,0,q.length));){var s=[];s.length=j;for(h=0;h<j;h++)s[h]=String.fromCharCode(q[h]);
i[i.length]=s.join("")}ja=null;return i.join("")}};
// Input 4
core.ByteArray=function(i){this.pos=0;this.data=i;this.readUInt32LE=function(){var i=this.data,e=this.pos+=4;return i[--e]<<24|i[--e]<<16|i[--e]<<8|i[--e]};this.readUInt16LE=function(){var i=this.data,e=this.pos+=2;return i[--e]<<8|i[--e]}};
core.ByteArray=function(g){this.pos=0;this.data=g;this.readUInt32LE=function(){var g=this.data,e=this.pos+=4;return g[--e]<<24|g[--e]<<16|g[--e]<<8|g[--e]};this.readUInt16LE=function(){var g=this.data,e=this.pos+=2;return g[--e]<<8|g[--e]}};
// Input 5
core.ByteArrayWriter=function(i){var k=this,e=new runtime.ByteArray(0);this.appendByteArrayWriter=function(g){e=runtime.concatByteArrays(e,g.getByteArray())};this.appendByteArray=function(g){e=runtime.concatByteArrays(e,g)};this.appendArray=function(g){e=runtime.concatByteArrays(e,runtime.byteArrayFromArray(g))};this.appendUInt16LE=function(e){k.appendArray([e&255,e>>8&255])};this.appendUInt32LE=function(e){k.appendArray([e&255,e>>8&255,e>>16&255,e>>24&255])};this.appendString=function(g){e=runtime.concatByteArrays(e,
runtime.byteArrayFromString(g,i))};this.getLength=function(){return e.length};this.getByteArray=function(){return e}};
core.ByteArrayWriter=function(g){var m=this,e=new runtime.ByteArray(0);this.appendByteArrayWriter=function(g){e=runtime.concatByteArrays(e,g.getByteArray())};this.appendByteArray=function(g){e=runtime.concatByteArrays(e,g)};this.appendArray=function(g){e=runtime.concatByteArrays(e,runtime.byteArrayFromArray(g))};this.appendUInt16LE=function(e){m.appendArray([e&255,e>>8&255])};this.appendUInt32LE=function(e){m.appendArray([e&255,e>>8&255,e>>16&255,e>>24&255])};this.appendString=function(k){e=runtime.concatByteArrays(e,
runtime.byteArrayFromString(k,g))};this.getLength=function(){return e.length};this.getByteArray=function(){return e}};
// Input 6
core.RawInflate=function(){var i,k,e=null,g,a,b,h,c,d,f,j,p,m,l,u,n,q,r=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535],y=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],C=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,99,99],s=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],v=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],E=[16,17,18,
0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],B=function(){this.list=this.next=null},z=function(){this.n=this.b=this.e=0;this.t=null},G=function(a,c,b,f,d,j){this.BMAX=16;this.N_MAX=288;this.status=0;this.root=null;this.m=0;var e=Array(this.BMAX+1),h,g,q,t,o,r,i,w=Array(this.BMAX+1),x,m,n,p=new z,l=Array(this.BMAX);t=Array(this.N_MAX);var v,y=Array(this.BMAX+1),k,s,C;C=this.root=null;for(o=0;o<e.length;o++)e[o]=0;for(o=0;o<w.length;o++)w[o]=0;for(o=0;o<l.length;o++)l[o]=null;for(o=0;o<t.length;o++)t[o]=
0;for(o=0;o<y.length;o++)y[o]=0;h=c>256?a[256]:this.BMAX;x=a;m=0;o=c;do e[x[m]]++,m++;while(--o>0);if(e[0]==c)this.root=null,this.status=this.m=0;else{for(r=1;r<=this.BMAX;r++)if(e[r]!=0)break;i=r;j<r&&(j=r);for(o=this.BMAX;o!=0;o--)if(e[o]!=0)break;q=o;j>o&&(j=o);for(k=1<<r;r<o;r++,k<<=1)if((k-=e[r])<0){this.status=2;this.m=j;return}if((k-=e[o])<0)this.status=2,this.m=j;else{e[o]+=k;y[1]=r=0;x=e;m=1;for(n=2;--o>0;)y[n++]=r+=x[m++];x=a;o=m=0;do if((r=x[m++])!=0)t[y[r]++]=o;while(++o<c);c=y[q];y[0]=
o=0;x=t;m=0;t=-1;v=w[0]=0;n=null;for(s=0;i<=q;i++)for(a=e[i];a-- >0;){for(;i>v+w[1+t];){v+=w[1+t];t++;s=(s=q-v)>j?j:s;if((g=1<<(r=i-v))>a+1){g-=a+1;for(n=i;++r<s;){if((g<<=1)<=e[++n])break;g-=e[n]}}v+r>h&&v<h&&(r=h-v);s=1<<r;w[1+t]=r;n=Array(s);for(g=0;g<s;g++)n[g]=new z;C=C==null?this.root=new B:C.next=new B;C.next=null;C.list=n;l[t]=n;if(t>0)y[t]=o,p.b=w[t],p.e=16+r,p.t=n,r=(o&(1<<v)-1)>>v-w[t],l[t-1][r].e=p.e,l[t-1][r].b=p.b,l[t-1][r].n=p.n,l[t-1][r].t=p.t}p.b=i-v;m>=c?p.e=99:x[m]<b?(p.e=x[m]<
256?16:15,p.n=x[m++]):(p.e=d[x[m]-b],p.n=f[x[m++]-b]);g=1<<i-v;for(r=o>>v;r<s;r+=g)n[r].e=p.e,n[r].b=p.b,n[r].n=p.n,n[r].t=p.t;for(r=1<<i-1;(o&r)!=0;r>>=1)o^=r;for(o^=r;(o&(1<<v)-1)!=y[t];)v-=w[t],t--}this.m=w[1];this.status=k!=0&&q!=1?1:0}}},o=function(a){for(;h<a;)b|=(n.length==q?-1:n[q++])<<h,h+=8},x=function(a){return b&r[a]},t=function(a){b>>=a;h-=a},w=function(a,b,d){var e,h,g;if(d==0)return 0;for(g=0;;){o(l);h=p.list[x(l)];for(e=h.e;e>16;){if(e==99)return-1;t(h.b);e-=16;o(e);h=h.t[x(e)];e=
h.e}t(h.b);if(e==16)k&=32767,a[b+g++]=i[k++]=h.n;else{if(e==15)break;o(e);f=h.n+x(e);t(e);o(u);h=m.list[x(u)];for(e=h.e;e>16;){if(e==99)return-1;t(h.b);e-=16;o(e);h=h.t[x(e)];e=h.e}t(h.b);o(e);j=k-h.n-x(e);for(t(e);f>0&&g<d;)f--,j&=32767,k&=32767,a[b+g++]=i[k++]=i[j++]}if(g==d)return d}c=-1;return g},L,Q=function(a,c,b){var f,d,e,h,j,g,q,r=Array(316);for(f=0;f<r.length;f++)r[f]=0;o(5);g=257+x(5);t(5);o(5);q=1+x(5);t(5);o(4);f=4+x(4);t(4);if(g>286||q>30)return-1;for(d=0;d<f;d++)o(3),r[E[d]]=x(3),t(3);
for(;d<19;d++)r[E[d]]=0;l=7;d=new G(r,19,19,null,null,l);if(d.status!=0)return-1;p=d.root;l=d.m;h=g+q;for(f=e=0;f<h;)if(o(l),j=p.list[x(l)],d=j.b,t(d),d=j.n,d<16)r[f++]=e=d;else if(d==16){o(2);d=3+x(2);t(2);if(f+d>h)return-1;for(;d-- >0;)r[f++]=e}else{d==17?(o(3),d=3+x(3),t(3)):(o(7),d=11+x(7),t(7));if(f+d>h)return-1;for(;d-- >0;)r[f++]=0;e=0}l=9;d=new G(r,g,257,y,C,l);if(l==0)d.status=1;if(d.status!=0)return-1;p=d.root;l=d.m;for(f=0;f<q;f++)r[f]=r[f+g];u=6;d=new G(r,q,0,s,v,u);m=d.root;u=d.m;return u==
0&&g>257?-1:d.status!=0?-1:w(a,c,b)};this.inflate=function(r,z){i==null&&(i=Array(65536));h=b=k=0;c=-1;d=false;f=j=0;p=null;n=r;q=0;var B=new runtime.ByteArray(z);a:{var E,H;for(E=0;E<z;){if(d&&c==-1)break;if(f>0){if(c!=0)for(;f>0&&E<z;)f--,j&=32767,k&=32767,B[0+E++]=i[k++]=i[j++];else{for(;f>0&&E<z;)f--,k&=32767,o(8),B[0+E++]=i[k++]=x(8),t(8);f==0&&(c=-1)}if(E==z)break}if(c==-1){if(d)break;o(1);x(1)!=0&&(d=true);t(1);o(2);c=x(2);t(2);p=null;f=0}switch(c){case 0:H=B;var S=0+E,K=z-E,F=void 0,F=h&7;
t(F);o(16);F=x(16);t(16);o(16);if(F!=(~b&65535))H=-1;else{t(16);f=F;for(F=0;f>0&&F<K;)f--,k&=32767,o(8),H[S+F++]=i[k++]=x(8),t(8);f==0&&(c=-1);H=F}break;case 1:if(p!=null)H=w(B,0+E,z-E);else b:{H=B;S=0+E;K=z-E;if(e==null){for(var D=void 0,F=Array(288),D=void 0,D=0;D<144;D++)F[D]=8;for(;D<256;D++)F[D]=9;for(;D<280;D++)F[D]=7;for(;D<288;D++)F[D]=8;a=7;D=new G(F,288,257,y,C,a);if(D.status!=0){alert("HufBuild error: "+D.status);H=-1;break b}e=D.root;a=D.m;for(D=0;D<30;D++)F[D]=5;L=5;D=new G(F,30,0,s,
v,L);if(D.status>1){e=null;alert("HufBuild error: "+D.status);H=-1;break b}g=D.root;L=D.m}p=e;m=g;l=a;u=L;H=w(H,S,K)}break;case 2:H=p!=null?w(B,0+E,z-E):Q(B,0+E,z-E);break;default:H=-1}if(H==-1)break a;E+=H}}n=null;return B}};
core.RawInflate=function(){var g,m,e=null,k,a,c,b,d,o,f,h,i,j,n,x,p,t,r=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535],z=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],s=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,99,99],q=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],u=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],E=[16,17,18,
0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],C=function(){this.list=this.next=null},w=function(){this.n=this.b=this.e=0;this.t=null},G=function(a,c,b,f,d,e){this.BMAX=16;this.N_MAX=288;this.status=0;this.root=null;this.m=0;var j=Array(this.BMAX+1),l,h,q,i,g,s,o,v=Array(this.BMAX+1),n,y,u,t=new w,k=Array(this.BMAX);i=Array(this.N_MAX);var p,z=Array(this.BMAX+1),B,r,x;x=this.root=null;for(g=0;g<j.length;g++)j[g]=0;for(g=0;g<v.length;g++)v[g]=0;for(g=0;g<k.length;g++)k[g]=null;for(g=0;g<i.length;g++)i[g]=
0;for(g=0;g<z.length;g++)z[g]=0;l=256<c?a[256]:this.BMAX;n=a;y=0;g=c;do j[n[y]]++,y++;while(0<--g);if(j[0]==c)this.root=null,this.status=this.m=0;else{for(s=1;s<=this.BMAX&&!(0!=j[s]);s++);o=s;e<s&&(e=s);for(g=this.BMAX;0!=g&&!(0!=j[g]);g--);q=g;e>g&&(e=g);for(B=1<<s;s<g;s++,B<<=1)if(0>(B-=j[s])){this.status=2;this.m=e;return}if(0>(B-=j[g]))this.status=2,this.m=e;else{j[g]+=B;z[1]=s=0;n=j;y=1;for(u=2;0<--g;)z[u++]=s+=n[y++];n=a;g=y=0;do if(0!=(s=n[y++]))i[z[s]++]=g;while(++g<c);c=z[q];z[0]=g=0;n=
i;y=0;i=-1;p=v[0]=0;u=null;for(r=0;o<=q;o++)for(a=j[o];0<a--;){for(;o>p+v[1+i];){p+=v[1+i];i++;r=(r=q-p)>e?e:r;if((h=1<<(s=o-p))>a+1){h-=a+1;for(u=o;++s<r&&!((h<<=1)<=j[++u]);)h-=j[u]}p+s>l&&p<l&&(s=l-p);r=1<<s;v[1+i]=s;u=Array(r);for(h=0;h<r;h++)u[h]=new w;x=null==x?this.root=new C:x.next=new C;x.next=null;x.list=u;k[i]=u;0<i&&(z[i]=g,t.b=v[i],t.e=16+s,t.t=u,s=(g&(1<<p)-1)>>p-v[i],k[i-1][s].e=t.e,k[i-1][s].b=t.b,k[i-1][s].n=t.n,k[i-1][s].t=t.t)}t.b=o-p;y>=c?t.e=99:n[y]<b?(t.e=256>n[y]?16:15,t.n=
n[y++]):(t.e=d[n[y]-b],t.n=f[n[y++]-b]);h=1<<o-p;for(s=g>>p;s<r;s+=h)u[s].e=t.e,u[s].b=t.b,u[s].n=t.n,u[s].t=t.t;for(s=1<<o-1;0!=(g&s);s>>=1)g^=s;for(g^=s;(g&(1<<p)-1)!=z[i];)p-=v[i],i--}this.m=v[1];this.status=0!=B&&1!=q?1:0}}},l=function(a){for(;b<a;)c|=(p.length==t?-1:p[t++])<<b,b+=8},v=function(a){return c&r[a]},y=function(a){c>>=a;b-=a},B=function(a,c,b){var e,s,q;if(0==b)return 0;for(q=0;;){l(n);s=i.list[v(n)];for(e=s.e;16<e;){if(99==e)return-1;y(s.b);e-=16;l(e);s=s.t[v(e)];e=s.e}y(s.b);if(16==
e)m&=32767,a[c+q++]=g[m++]=s.n;else{if(15==e)break;l(e);f=s.n+v(e);y(e);l(x);s=j.list[v(x)];for(e=s.e;16<e;){if(99==e)return-1;y(s.b);e-=16;l(e);s=s.t[v(e)];e=s.e}y(s.b);l(e);h=m-s.n-v(e);for(y(e);0<f&&q<b;)f--,h&=32767,m&=32767,a[c+q++]=g[m++]=g[h++]}if(q==b)return b}d=-1;return q},M,F=function(a,c,b){var f,d,e,h,g,o,t,p=Array(316);for(f=0;f<p.length;f++)p[f]=0;l(5);o=257+v(5);y(5);l(5);t=1+v(5);y(5);l(4);f=4+v(4);y(4);if(286<o||30<t)return-1;for(d=0;d<f;d++)l(3),p[E[d]]=v(3),y(3);for(;19>d;d++)p[E[d]]=
0;n=7;d=new G(p,19,19,null,null,n);if(0!=d.status)return-1;i=d.root;n=d.m;h=o+t;for(f=e=0;f<h;)if(l(n),g=i.list[v(n)],d=g.b,y(d),d=g.n,16>d)p[f++]=e=d;else if(16==d){l(2);d=3+v(2);y(2);if(f+d>h)return-1;for(;0<d--;)p[f++]=e}else{17==d?(l(3),d=3+v(3),y(3)):(l(7),d=11+v(7),y(7));if(f+d>h)return-1;for(;0<d--;)p[f++]=0;e=0}n=9;d=new G(p,o,257,z,s,n);0==n&&(d.status=1);if(0!=d.status)return-1;i=d.root;n=d.m;for(f=0;f<t;f++)p[f]=p[f+o];x=6;d=new G(p,t,0,q,u,x);j=d.root;x=d.m;return 0==x&&257<o||0!=d.status?
-1:B(a,c,b)};this.inflate=function(r,w){null==g&&(g=Array(65536));b=c=m=0;d=-1;o=!1;f=h=0;i=null;p=r;t=0;var C=new runtime.ByteArray(w);a:{var E,I;for(E=0;E<w&&!(o&&-1==d);){if(0<f){if(0!=d)for(;0<f&&E<w;)f--,h&=32767,m&=32767,C[0+E++]=g[m++]=g[h++];else{for(;0<f&&E<w;)f--,m&=32767,l(8),C[0+E++]=g[m++]=v(8),y(8);0==f&&(d=-1)}if(E==w)break}if(-1==d){if(o)break;l(1);0!=v(1)&&(o=!0);y(1);l(2);d=v(2);y(2);i=null;f=0}switch(d){case 0:I=C;var P=0+E,L=w-E,H=void 0,H=b&7;y(H);l(16);H=v(16);y(16);l(16);if(H!=
(~c&65535))I=-1;else{y(16);f=H;for(H=0;0<f&&H<L;)f--,m&=32767,l(8),I[P+H++]=g[m++]=v(8),y(8);0==f&&(d=-1);I=H}break;case 1:if(null!=i)I=B(C,0+E,w-E);else b:{I=C;P=0+E;L=w-E;if(null==e){for(var D=void 0,H=Array(288),D=void 0,D=0;144>D;D++)H[D]=8;for(;256>D;D++)H[D]=9;for(;280>D;D++)H[D]=7;for(;288>D;D++)H[D]=8;a=7;D=new G(H,288,257,z,s,a);if(0!=D.status){alert("HufBuild error: "+D.status);I=-1;break b}e=D.root;a=D.m;for(D=0;30>D;D++)H[D]=5;M=5;D=new G(H,30,0,q,u,M);if(1<D.status){e=null;alert("HufBuild error: "+
D.status);I=-1;break b}k=D.root;M=D.m}i=e;j=k;n=a;x=M;I=B(I,P,L)}break;case 2:I=null!=i?B(C,0+E,w-E):F(C,0+E,w-E);break;default:I=-1}if(-1==I)break a;E+=I}}p=null;return C}};
// Input 7
core.Cursor=function(i,k){function e(a,e){for(var c=e;c&&c!==a;)c=c.parentNode;return c||e}function g(){var b,h,c;if(a.parentNode){h=0;for(b=a.parentNode.firstChild;b&&b!==a;)h+=1,b=b.nextSibling;if(a.previousSibling&&a.previousSibling.nodeType===3&&a.nextSibling&&a.nextSibling.nodeType===3)c=a.nextSibling,a.previousSibling.appendData(c.nodeValue);for(b=0;b<i.rangeCount;b+=1){var d=i.getRangeAt(b),f=h,j=void 0,g=void 0,j=a.parentNode,g=e(a,d.startContainer);e(a,d.endContainer);g===a?d.setStart(j,
f):g===j&&d.startOffset>f&&d.setStart(j,d.startOffset-1);d.endContainer===a?d.setEnd(j,f):d.endContainer===j&&d.endOffset>f&&d.setEnd(j,d.endOffset-1)}if(c){for(b=0;b<i.rangeCount;b+=1){var d=i.getRangeAt(b),f=a.previousSibling,j=c,g=h,m=f.length-j.length;d.startContainer===j?d.setStart(f,m+d.startOffset):d.startContainer===f.parentNode&&d.startOffset===g&&d.setStart(f,m);d.endContainer===j?d.setEnd(f,m+d.endOffset):d.endContainer===f.parentNode&&d.endOffset===g&&d.setEnd(f,m)}c.parentNode.removeChild(c)}a.parentNode.removeChild(a)}}
var a;a=k.createElementNS("urn:webodf:names:cursor","cursor");this.getNode=function(){return a};this.updateToSelection=function(){g();if(i.focusNode){var b=i.focusNode,e=i.focusOffset;if(b.nodeType===3){var c,d,f,j;j=b.parentNode;e===0?j.insertBefore(a,b):e===b.length?j.appendChild(a):(c=b.length,d=b.nextSibling,f=k.createTextNode(b.substringData(e,c)),b.deleteData(e,c),d?j.insertBefore(f,d):j.appendChild(f),j.insertBefore(a,f))}else if(b.nodeType!==9){for(c=b.firstChild;c&&e;)c=c.nextSibling,e-=
1;b.insertBefore(a,c)}}};this.remove=function(){g()}};
core.Cursor=function(g,m){function e(a,b){for(var d=b;d&&d!==a;)d=d.parentNode;return d||b}function k(){var c,b,d;if(a.parentNode){b=0;for(c=a.parentNode.firstChild;c&&c!==a;)b+=1,c=c.nextSibling;a.previousSibling&&3===a.previousSibling.nodeType&&a.nextSibling&&3===a.nextSibling.nodeType&&(d=a.nextSibling,a.previousSibling.appendData(d.nodeValue));for(c=0;c<g.rangeCount;c+=1){var o=g.getRangeAt(c),f=b,h=void 0,i=void 0,h=a.parentNode,i=e(a,o.startContainer);e(a,o.endContainer);i===a?o.setStart(h,
f):i===h&&o.startOffset>f&&o.setStart(h,o.startOffset-1);o.endContainer===a?o.setEnd(h,f):o.endContainer===h&&o.endOffset>f&&o.setEnd(h,o.endOffset-1)}if(d){for(c=0;c<g.rangeCount;c+=1){var o=g.getRangeAt(c),f=a.previousSibling,h=d,i=b,j=f.length-h.length;o.startContainer===h?o.setStart(f,j+o.startOffset):o.startContainer===f.parentNode&&o.startOffset===i&&o.setStart(f,j);o.endContainer===h?o.setEnd(f,j+o.endOffset):o.endContainer===f.parentNode&&o.endOffset===i&&o.setEnd(f,j)}d.parentNode.removeChild(d)}a.parentNode.removeChild(a)}}
var a;a=m.createElementNS("urn:webodf:names:cursor","cursor");this.getNode=function(){return a};this.updateToSelection=function(){k();if(g.focusNode){var c=g.focusNode,b=g.focusOffset;if(3===c.nodeType){var d,e,f,h;h=c.parentNode;0===b?h.insertBefore(a,c):b===c.length?h.appendChild(a):(d=c.length,e=c.nextSibling,f=m.createTextNode(c.substringData(b,d)),c.deleteData(b,d),e?h.insertBefore(f,e):h.appendChild(f),h.insertBefore(a,f))}else if(9!==c.nodeType){for(d=c.firstChild;d&&b;)d=d.nextSibling,b-=
1;c.insertBefore(a,d)}}};this.remove=function(){k()}};
// Input 8
core.UnitTest=function(){};core.UnitTest.prototype.setUp=function(){};core.UnitTest.prototype.tearDown=function(){};core.UnitTest.prototype.description=function(){};core.UnitTest.prototype.tests=function(){};core.UnitTest.prototype.asyncTests=function(){};
core.UnitTestRunner=function(){function i(a){g+=1;runtime.log("fail",a)}function k(a,b){var e;try{if(a.length!==b.length)return false;for(e=0;e<a.length;e+=1)if(a[e]!==b[e])return false}catch(c){return false}return true}function e(a,b,e){(typeof b!=="string"||typeof e!=="string")&&runtime.log("WARN: shouldBe() expects string arguments");var c,d;try{d=eval(b)}catch(f){c=f}a=eval(e);c?i(b+" should be "+a+". Threw exception "+c):(a===0?d===a&&1/d===1/a:d===a||(typeof a==="number"&&isNaN(a)?typeof d===
"number"&&isNaN(d):Object.prototype.toString.call(a)===Object.prototype.toString.call([])&&k(d,a)))?runtime.log("pass",b+" is "+e):typeof d===typeof a?i(b+" should be "+a+". Was "+(d===0&&1/d<0?"-0":String(d))+"."):i(b+" should be "+a+" (of type "+typeof a+"). Was "+d+" (of type "+typeof d+").")}var g=0;this.shouldBeNull=function(a,b){e(a,b,"null")};this.shouldBeNonNull=function(a,b){var e,c;try{c=eval(b)}catch(d){e=d}e?i(b+" should be non-null. Threw exception "+e):c!==null?runtime.log("pass",b+
" is non-null."):i(b+" should be non-null. Was "+c)};this.shouldBe=e;this.countFailedTests=function(){return g}};
core.UnitTester=function(){var i=0,k={};this.runTests=function(e,g){function a(e){if(e.length===0)k[b]=f,i+=c.countFailedTests(),g();else{p=e[0];var j=Runtime.getFunctionName(p);runtime.log("Running "+j);l=c.countFailedTests();d.setUp();p(function(){d.tearDown();f[j]=l===c.countFailedTests();a(e.slice(1))})}}var b=Runtime.getFunctionName(e),h,c=new core.UnitTestRunner,d=new e(c),f={},j,p,m,l;if(b.hasOwnProperty(k))runtime.log("Test "+b+" has already run.");else{runtime.log("Running "+b+": "+d.description());
m=d.tests();for(j=0;j<m.length;j+=1)p=m[j],h=Runtime.getFunctionName(p),runtime.log("Running "+h),l=c.countFailedTests(),d.setUp(),p(),d.tearDown(),f[h]=l===c.countFailedTests();a(d.asyncTests())}};this.countFailedTests=function(){return i};this.results=function(){return k}};
core.UnitTestRunner=function(){function g(a){k+=1;runtime.log("fail",a)}function m(a,c){var b;try{if(a.length!==c.length)return!1;for(b=0;b<a.length;b+=1)if(a[b]!==c[b])return!1}catch(d){return!1}return!0}function e(a,c,b){("string"!==typeof c||"string"!==typeof b)&&runtime.log("WARN: shouldBe() expects string arguments");var d,e;try{e=eval(c)}catch(f){d=f}a=eval(b);d?g(c+" should be "+a+". Threw exception "+d):(0===a?e===a&&1/e===1/a:e===a||("number"===typeof a&&isNaN(a)?"number"===typeof e&&isNaN(e):
Object.prototype.toString.call(a)===Object.prototype.toString.call([])&&m(e,a)))?runtime.log("pass",c+" is "+b):typeof e===typeof a?g(c+" should be "+a+". Was "+(0===e&&0>1/e?"-0":""+e)+"."):g(c+" should be "+a+" (of type "+typeof a+"). Was "+e+" (of type "+typeof e+").")}var k=0;this.shouldBeNull=function(a,c){e(a,c,"null")};this.shouldBeNonNull=function(a,c){var b,d;try{d=eval(c)}catch(e){b=e}b?g(c+" should be non-null. Threw exception "+b):null!==d?runtime.log("pass",c+" is non-null."):g(c+" should be non-null. Was "+
d)};this.shouldBe=e;this.countFailedTests=function(){return k}};
core.UnitTester=function(){var g=0,m={};this.runTests=function(e,k){function a(b){if(0===b.length)m[c]=f,g+=d.countFailedTests(),k();else{i=b[0];var e=Runtime.getFunctionName(i);runtime.log("Running "+e);n=d.countFailedTests();o.setUp();i(function(){o.tearDown();f[e]=n===d.countFailedTests();a(b.slice(1))})}}var c=Runtime.getFunctionName(e),b,d=new core.UnitTestRunner,o=new e(d),f={},h,i,j,n;if(c.hasOwnProperty(m))runtime.log("Test "+c+" has already run.");else{runtime.log("Running "+c+": "+o.description());
j=o.tests();for(h=0;h<j.length;h+=1)i=j[h],b=Runtime.getFunctionName(i),runtime.log("Running "+b),n=d.countFailedTests(),o.setUp(),i(),o.tearDown(),f[b]=n===d.countFailedTests();a(o.asyncTests())}};this.countFailedTests=function(){return g};this.results=function(){return m}};
// Input 9
core.PointWalker=function(i){function k(a){for(var c=-1;a;)a=a.previousSibling,c+=1;return c}var e=i,g=null,a=i&&i.firstChild,b=0;this.setPoint=function(h,c){e=h;b=c;if(e.nodeType===3)g=a=null;else{for(a=e.firstChild;c;)c-=1,a=a.nextSibling;g=a?a.previousSibling:e.lastChild}};this.stepForward=function(){var h;if(e.nodeType===3&&(h=typeof e.nodeValue.length==="number"?e.nodeValue.length:e.nodeValue.length(),b<h))return b+=1,true;if(a)return a.nodeType===1?(e=a,g=null,a=e.firstChild,b=0):a.nodeType===
3?(e=a,a=g=null,b=0):(g=a,a=a.nextSibling,b+=1),true;return e!==i?(g=e,a=g.nextSibling,e=e.parentNode,b=k(g)+1,true):false};this.stepBackward=function(){if(e.nodeType===3&&b>0)return b-=1,true;if(g)return g.nodeType===1?(e=g,g=e.lastChild,a=null,b=k(g)+1):g.nodeType===3?(e=g,a=g=null,b=typeof e.nodeValue.length==="number"?e.nodeValue.length:e.nodeValue.length()):(a=g,g=g.previousSibling,b-=1),true;return e!==i?(a=e,g=a.previousSibling,e=e.parentNode,b=k(a),true):false};this.node=function(){return e};
this.position=function(){return b};this.precedingSibling=function(){return g};this.followingSibling=function(){return a}};
core.PointWalker=function(g){function m(a){for(var c=-1;a;)a=a.previousSibling,c+=1;return c}var e=g,k=null,a=g&&g.firstChild,c=0;this.setPoint=function(b,d){e=b;c=d;if(3===e.nodeType)k=a=null;else{for(a=e.firstChild;d;)d-=1,a=a.nextSibling;k=a?a.previousSibling:e.lastChild}};this.stepForward=function(){var b;if(3===e.nodeType&&(b="number"===typeof e.nodeValue.length?e.nodeValue.length:e.nodeValue.length(),c<b))return c+=1,!0;if(a)return 1===a.nodeType?(e=a,k=null,a=e.firstChild,c=0):3===a.nodeType?
(e=a,a=k=null,c=0):(k=a,a=a.nextSibling,c+=1),!0;return e!==g?(k=e,a=k.nextSibling,e=e.parentNode,c=m(k)+1,!0):!1};this.stepBackward=function(){if(3===e.nodeType&&0<c)return c-=1,!0;if(k)return 1===k.nodeType?(e=k,k=e.lastChild,a=null,c=m(k)+1):3===k.nodeType?(e=k,a=k=null,c="number"===typeof e.nodeValue.length?e.nodeValue.length:e.nodeValue.length()):(a=k,k=k.previousSibling,c-=1),!0;return e!==g?(a=e,k=a.previousSibling,e=e.parentNode,c=m(a),!0):!1};this.node=function(){return e};this.position=
function(){return c};this.precedingSibling=function(){return k};this.followingSibling=function(){return a}};
// Input 10
core.Async=function(){this.forEach=function(i,k,e){function g(a){h!==b&&(a?(h=b,e(a)):(h+=1,h===b&&e(null)))}var a,b=i.length,h=0;for(a=0;a<b;a+=1)k(i[a],g)}};
core.Async=function(){this.forEach=function(g,m,e){function k(a){b!==c&&(a?(b=c,e(a)):(b+=1,b===c&&e(null)))}var a,c=g.length,b=0;for(a=0;a<c;a+=1)m(g[a],k)}};
// Input 11
runtime.loadClass("core.RawInflate");runtime.loadClass("core.ByteArray");runtime.loadClass("core.ByteArrayWriter");
core.Zip=function(i,k){function e(a){var c=[0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,
runtime.loadClass("core.RawInflate");runtime.loadClass("core.ByteArray");runtime.loadClass("core.ByteArrayWriter");runtime.loadClass("core.Base64");
core.Zip=function(g,m){function e(a){var c=[0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,
853044451,1172266101,3705015759,2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,
4240017532,1658658271,366619977,2362670323,4224994405,1303535960,984961486,2747007092,3569037538,1256170817,1037604311,2765210733,3554079995,1131014506,879679996,2909243462,3663771856,1141124467,855842277,2852801631,3708648649,1342533948,654459306,3188396048,3373015174,1466479909,544179635,3110523913,3462522015,1591671054,702138776,2966460450,3352799412,1504918807,783551873,3082640443,3233442989,3988292384,2596254646,62317068,1957810842,3939845945,2647816111,81470997,1943803523,3814918930,2489596804,
225274430,2053790376,3826175755,2466906013,167816743,2097651377,4027552580,2265490386,503444072,1762050814,4150417245,2154129355,426522225,1852507879,4275313526,2312317920,282753626,1742555852,4189708143,2394877945,397917763,1622183637,3604390888,2714866558,953729732,1340076626,3518719985,2797360999,1068828381,1219638859,3624741850,2936675148,906185462,1090812512,3747672003,2825379669,829329135,1181335161,3412177804,3160834842,628085408,1382605366,3423369109,3138078467,570562233,1426400815,3317316542,
2998733608,733239954,1555261956,3268935591,3050360625,752459403,1541320221,2607071920,3965973030,1969922972,40735498,2617837225,3943577151,1913087877,83908371,2512341634,3803740692,2075208622,213261112,2463272603,3855990285,2094854071,198958881,2262029012,4057260610,1759359992,534414190,2176718541,4139329115,1873836001,414664567,2282248934,4279200368,1711684554,285281116,2405801727,4167216745,1634467795,376229701,2685067896,3608007406,1308918612,956543938,2808555105,3495958263,1231636301,1047427035,
2932959818,3654703836,1088359270,936918E3,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117],b=0,f,d=a.length,e=0,e=0;b^=-1;for(f=0;f<d;f+=1)e=(b^a[f])&255,e=c[e],b=b>>>8^e;return b^-1}function g(a){return new Date((a>>25&127)+1980,(a>>21&15)-1,a>>16&31,a>>11&15,a>>5&63,(a&31)<<1)}function a(a){var c=a.getFullYear();return c<1980?0:c-
1980<<25|a.getMonth()+1<<21|a.getDate()<<16|a.getHours()<<11|a.getMinutes()<<5|a.getSeconds()>>1}function b(a,c){var b,f,d,e,j,h,i,m=this;this.load=function(c){if(m.data!==void 0)c(null,m.data);else{var d=j+34+b+f+256;d+i>p&&(d=p-i);runtime.read(a,i,d,function(b,f){if(b)c(b,f);else a:{var d=f,g=new core.ByteArray(d),o=g.readUInt32LE(),r;if(o!==67324752)c("File entry signature is wrong."+o.toString()+" "+d.length.toString(),null);else{g.pos+=22;o=g.readUInt16LE();r=g.readUInt16LE();g.pos+=o+r;if(e){d=
d.slice(g.pos,g.pos+j);if(j!==d.length){c("The amount of compressed bytes read was "+d.length.toString()+" instead of "+j.toString()+" for "+m.filename+" in "+a+".",null);break a}d=l(d,h)}else d=d.slice(g.pos,g.pos+h);h!==d.length?c("The amount of bytes read was "+d.length.toString()+" instead of "+h.toString()+" for "+m.filename+" in "+a+".",null):(m.data=d,c(null,d))}}})}};this.set=function(a,c,b,f){m.filename=a;m.data=c;m.compressed=b;m.date=f};this.error=null;if(c)c.readUInt32LE()!==33639248?
this.error="Central directory entry has wrong signature at position "+(c.pos-4).toString()+' for file "'+a+'": '+c.data.length.toString():(c.pos+=6,e=c.readUInt16LE(),this.date=g(c.readUInt32LE()),c.readUInt32LE(),j=c.readUInt32LE(),h=c.readUInt32LE(),b=c.readUInt16LE(),f=c.readUInt16LE(),d=c.readUInt16LE(),c.pos+=8,i=c.readUInt32LE(),this.filename=runtime.byteArrayToString(c.data.slice(c.pos,c.pos+b),"utf8"),c.pos+=b+f+d)}function h(a,c){if(a.length!==22)c("Central directory length should be 22.",
u);else{var f=new core.ByteArray(a),d;d=f.readUInt32LE();d!==101010256?c("Central directory signature is wrong: "+d.toString(),u):f.readUInt16LE()!==0?c("Zip files with non-zero disk numbers are not supported.",u):f.readUInt16LE()!==0?c("Zip files with non-zero disk numbers are not supported.",u):(d=f.readUInt16LE(),m=f.readUInt16LE(),d!==m?c("Number of entries is inconsistent.",u):(d=f.readUInt32LE(),f=f.readUInt16LE(),f=p-22-d,runtime.read(i,f,p-f,function(a,f){a:{var d=new core.ByteArray(f),e,
g;j=[];for(e=0;e<m;e+=1){g=new b(i,d);if(g.error){c(g.error,u);break a}j[j.length]=g}c(null,u)}})))}}function c(c){var b=new core.ByteArrayWriter("utf8"),f=0;b.appendArray([80,75,3,4,20,0,0,0,0,0]);if(c.data)f=c.data.length;b.appendUInt32LE(a(c.date));b.appendUInt32LE(e(c.data));b.appendUInt32LE(f);b.appendUInt32LE(f);b.appendUInt16LE(c.filename.length);b.appendUInt16LE(0);b.appendString(c.filename);c.data&&b.appendByteArray(c.data);return b}function d(c,b){var f=new core.ByteArrayWriter("utf8"),
d=0;f.appendArray([80,75,1,2,20,0,20,0,0,0,0,0]);if(c.data)d=c.data.length;f.appendUInt32LE(a(c.date));f.appendUInt32LE(e(c.data));f.appendUInt32LE(d);f.appendUInt32LE(d);f.appendUInt16LE(c.filename.length);f.appendArray([0,0,0,0,0,0,0,0,0,0,0,0]);f.appendUInt32LE(b);f.appendString(c.filename);return f}function f(a,c){if(a===j.length)c(null);else{var b=j[a];b.data!==void 0?f(a+1,c):b.load(function(b){b?c(b):f(a+1,c)})}}var j,p,m,l=(new core.RawInflate).inflate,u=this;this.load=function(a,c){var b=
null,f,d;for(d=0;d<j.length;d+=1)if(f=j[d],f.filename===a){b=f;break}b?b.data?c(null,b.data):b.load(c):c(a+" not found.",null)};this.save=function(a,c,f,d){var e,g;for(e=0;e<j.length;e+=1)if(g=j[e],g.filename===a){g.set(a,c,f,d);return}g=new b(i);g.set(a,c,f,d);j.push(g)};this.write=function(a){f(0,function(b){if(b)a(b);else{var b=new core.ByteArrayWriter("utf8"),f,e,g,h=[0];for(f=0;f<j.length;f+=1)b.appendByteArrayWriter(c(j[f])),h.push(b.getLength());g=b.getLength();for(f=0;f<j.length;f+=1)e=j[f],
b.appendByteArrayWriter(d(e,h[f]));f=b.getLength()-g;b.appendArray([80,75,5,6,0,0,0,0]);b.appendUInt16LE(j.length);b.appendUInt16LE(j.length);b.appendUInt32LE(f);b.appendUInt32LE(g);b.appendArray([0,0]);runtime.writeFile(i,b.getByteArray(),a)}})};this.getEntries=function(){return j.slice()};p=-1;k===null?j=[]:runtime.getFileSize(i,function(a){p=a;p<0?k("File '"+i+"' cannot be read.",u):runtime.read(i,p-22,22,function(a,c){a||k===null?k(a,u):h(c,k)})})};
2932959818,3654703836,1088359270,936918E3,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117],b,f,d=a.length,e=0,e=0;b=-1;for(f=0;f<d;f+=1)e=(b^a[f])&255,e=c[e],b=b>>>8^e;return b^-1}function k(a){return new Date((a>>25&127)+1980,(a>>21&15)-1,a>>16&31,a>>11&15,a>>5&63,(a&31)<<1)}function a(a){var c=a.getFullYear();return 1980>c?0:c-1980<<
25|a.getMonth()+1<<21|a.getDate()<<16|a.getHours()<<11|a.getMinutes()<<5|a.getSeconds()>>1}function c(a,c){var b,f,d,e,j,h,l,g=this;this.load=function(c){if(void 0!==g.data)c(null,g.data);else{var d=j+34+b+f+256;d+l>n&&(d=n-l);runtime.read(a,l,d,function(b,f){if(b)c(b,f);else a:{var d=f,l=new core.ByteArray(d),i=l.readUInt32LE(),q;if(67324752!==i)c("File entry signature is wrong."+i.toString()+" "+d.length.toString(),null);else{l.pos+=22;i=l.readUInt16LE();q=l.readUInt16LE();l.pos+=i+q;if(e){d=d.slice(l.pos,
l.pos+j);if(j!==d.length){c("The amount of compressed bytes read was "+d.length.toString()+" instead of "+j.toString()+" for "+g.filename+" in "+a+".",null);break a}d=p(d,h)}else d=d.slice(l.pos,l.pos+h);h!==d.length?c("The amount of bytes read was "+d.length.toString()+" instead of "+h.toString()+" for "+g.filename+" in "+a+".",null):(g.data=d,c(null,d))}}})}};this.set=function(a,c,b,d){g.filename=a;g.data=c;g.compressed=b;g.date=d};this.error=null;c&&(33639248!==c.readUInt32LE()?this.error="Central directory entry has wrong signature at position "+
(c.pos-4).toString()+' for file "'+a+'": '+c.data.length.toString():(c.pos+=6,e=c.readUInt16LE(),this.date=k(c.readUInt32LE()),c.readUInt32LE(),j=c.readUInt32LE(),h=c.readUInt32LE(),b=c.readUInt16LE(),f=c.readUInt16LE(),d=c.readUInt16LE(),c.pos+=8,l=c.readUInt32LE(),this.filename=runtime.byteArrayToString(c.data.slice(c.pos,c.pos+b),"utf8"),c.pos+=b+f+d))}function b(a,b){if(22!==a.length)b("Central directory length should be 22.",t);else{var d=new core.ByteArray(a),f;f=d.readUInt32LE();101010256!==
f?b("Central directory signature is wrong: "+f.toString(),t):0!==d.readUInt16LE()?b("Zip files with non-zero disk numbers are not supported.",t):0!==d.readUInt16LE()?b("Zip files with non-zero disk numbers are not supported.",t):(f=d.readUInt16LE(),x=d.readUInt16LE(),f!==x?b("Number of entries is inconsistent.",t):(f=d.readUInt32LE(),d=d.readUInt16LE(),d=n-22-f,runtime.read(g,d,n-d,function(a,d){a:{var f=new core.ByteArray(d),e,l;j=[];for(e=0;e<x;e+=1){l=new c(g,f);if(l.error){b(l.error,t);break a}j[j.length]=
l}b(null,t)}})))}}function d(a,c){var b=null,d,f;for(f=0;f<j.length;f+=1)if(d=j[f],d.filename===a){b=d;break}b?b.data?c(null,b.data):b.load(c):c(a+" not found.",null)}function o(a,c){d(a,function(a,b){if(a)return c(a,null);b=runtime.byteArrayToString(b,"utf8");c(null,b)})}function f(c){var b=new core.ByteArrayWriter("utf8"),d=0;b.appendArray([80,75,3,4,20,0,0,0,0,0]);c.data&&(d=c.data.length);b.appendUInt32LE(a(c.date));b.appendUInt32LE(e(c.data));b.appendUInt32LE(d);b.appendUInt32LE(d);b.appendUInt16LE(c.filename.length);
b.appendUInt16LE(0);b.appendString(c.filename);c.data&&b.appendByteArray(c.data);return b}function h(c,b){var d=new core.ByteArrayWriter("utf8"),f=0;d.appendArray([80,75,1,2,20,0,20,0,0,0,0,0]);c.data&&(f=c.data.length);d.appendUInt32LE(a(c.date));d.appendUInt32LE(e(c.data));d.appendUInt32LE(f);d.appendUInt32LE(f);d.appendUInt16LE(c.filename.length);d.appendArray([0,0,0,0,0,0,0,0,0,0,0,0]);d.appendUInt32LE(b);d.appendString(c.filename);return d}function i(a,c){if(a===j.length)c(null);else{var b=j[a];
void 0!==b.data?i(a+1,c):b.load(function(b){b?c(b):i(a+1,c)})}}var j,n,x,p=(new core.RawInflate).inflate,t=this,r=new core.Base64;this.load=d;this.save=function(a,b,d,f){var e,h;for(e=0;e<j.length;e+=1)if(h=j[e],h.filename===a){h.set(a,b,d,f);return}h=new c(g);h.set(a,b,d,f);j.push(h)};this.write=function(a){i(0,function(c){if(c)a(c);else{var c=new core.ByteArrayWriter("utf8"),b,d,e,i=[0];for(b=0;b<j.length;b+=1)c.appendByteArrayWriter(f(j[b])),i.push(c.getLength());e=c.getLength();for(b=0;b<j.length;b+=
1)d=j[b],c.appendByteArrayWriter(h(d,i[b]));b=c.getLength()-e;c.appendArray([80,75,5,6,0,0,0,0]);c.appendUInt16LE(j.length);c.appendUInt16LE(j.length);c.appendUInt32LE(b);c.appendUInt32LE(e);c.appendArray([0,0]);runtime.writeFile(g,c.getByteArray(),a)}})};this.loadContentXmlAsFragments=function(a,c){o(a,function(a,b){if(a)return c.rootElementReady(a);c.rootElementReady(null,b,!0)})};this.loadAsString=o;this.loadAsDOM=function(a,c){o(a,function(a,b){a?c(a,null):(b=(new DOMParser).parseFromString(b,
"text/xml"),c(null,b))})};this.loadAsDataURL=function(a,c,b){d(a,function(a,d){if(a)return b(a,null);var f=0,e;c||(c=80===d[1]&&78===d[2]&&71===d[3]?"image/png":255===d[0]&&216===d[1]&&255===d[2]?"image/jpeg":71===d[0]&&73===d[1]&&70===d[2]?"image/gif":"");for(e="data:"+c+";base64,";f<d.length;)e+=r.convertUTF8ArrayToBase64(d.slice(f,Math.min(f+45E3,d.length))),f+=45E3;b(null,e)})};this.getEntries=function(){return j.slice()};n=-1;null===m?j=[]:runtime.getFileSize(g,function(a){n=a;0>n?m("File '"+
g+"' cannot be read.",t):runtime.read(g,n-22,22,function(a,c){a||null===m?m(a,t):b(c,m)})})};
// Input 12
xmldom.LSSerializerFilter=function(){};
// Input 13
typeof Object.create!=="function"&&(Object.create=function(i){var k=function(){};k.prototype=i;return new k});
xmldom.LSSerializer=function(){function i(e,g){var a="",b=Object.create(e),h=k.filter?k.filter.acceptNode(g):1,c;if(h===1){c="";var d=g.attributes,f,j,p,m="",l;if(d){if(b[g.namespaceURI]!==g.prefix)b[g.namespaceURI]=g.prefix;c+="<"+g.nodeName;f=d.length;for(j=0;j<f;j+=1)if(p=d.item(j),p.namespaceURI!=="http://www.w3.org/2000/xmlns/"&&(l=k.filter?k.filter.acceptNode(p):1,l===1)){if(p.namespaceURI){l=p.prefix;var u=p.namespaceURI;b.hasOwnProperty(u)?l=b[u]+":":(b[u]!==l&&(b[u]=l),l+=":")}else l="";
m+=" "+(l+p.localName+'="'+p.nodeValue+'"')}for(j in b)b.hasOwnProperty(j)&&((l=b[j])?l!=="xmlns"&&(c+=" xmlns:"+b[j]+'="'+j+'"'):c+=' xmlns="'+j+'"');c+=m+">"}a+=c}if(h===1||h===3){for(c=g.firstChild;c;)a+=i(b,c),c=c.nextSibling;g.nodeValue&&(a+=g.nodeValue)}h===1&&(b="",g.nodeType===1&&(b+="</"+g.nodeName+">"),a+=b);return a}var k=this;this.filter=null;this.writeToString=function(e,g){if(!e)return"";var a;if(g){a=g;var b={},h;for(h in a)a.hasOwnProperty(h)&&(b[a[h]]=h);a=b}else a={};return i(a,
"function"!==typeof Object.create&&(Object.create=function(g){var m=function(){};m.prototype=g;return new m});
xmldom.LSSerializer=function(){function g(e,k){var a="",c=Object.create(e),b=m.filter?m.filter.acceptNode(k):1,d;if(1===b){d="";var o=k.attributes,f,h,i,j="",n;if(o){c[k.namespaceURI]!==k.prefix&&(c[k.namespaceURI]=k.prefix);d+="<"+k.nodeName;f=o.length;for(h=0;h<f;h+=1)if(i=o.item(h),"http://www.w3.org/2000/xmlns/"!==i.namespaceURI&&(n=m.filter?m.filter.acceptNode(i):1,1===n)){if(i.namespaceURI){n=i.prefix;var x=i.namespaceURI;c.hasOwnProperty(x)?n=c[x]+":":(c[x]!==n&&(c[x]=n),n+=":")}else n="";
j+=" "+(n+i.localName+'="'+i.nodeValue+'"')}for(h in c)c.hasOwnProperty(h)&&((n=c[h])?"xmlns"!==n&&(d+=" xmlns:"+c[h]+'="'+h+'"'):d+=' xmlns="'+h+'"');d+=j+">"}a+=d}if(1===b||3===b){for(d=k.firstChild;d;)a+=g(c,d),d=d.nextSibling;k.nodeValue&&(a+=k.nodeValue)}1===b&&(c="",1===k.nodeType&&(c+="</"+k.nodeName+">"),a+=c);return a}var m=this;this.filter=null;this.writeToString=function(e,k){if(!e)return"";var a;if(k){a=k;var c={},b;for(b in a)a.hasOwnProperty(b)&&(c[a[b]]=b);a=c}else a={};return g(a,
e)}};
// Input 14
xmldom.RelaxNGParser=function(){function i(a,c){this.message=function(){c&&(a+=c.nodeType===1?" Element ":" Node ",a+=c.nodeName,c.nodeValue&&(a+=" with value '"+c.nodeValue+"'"),a+=".");return a}}function k(a){if(a.e.length<=2)return a;var c={name:a.name,e:a.e.slice(0,2)};return k({name:a.name,e:[c].concat(a.e.slice(2))})}function e(a){var a=a.split(":",2),b="",d;a.length===1?a=["",a[0]]:b=a[0];for(d in c)c[d]===b&&(a[0]=d);return a}function g(a,c){var j;var f;for(var b=0,d,h,i=a.name;a.e&&b<a.e.length;)if(d=
a.e[b],d.name==="ref"){h=c[d.a.name];if(!h)throw d.a.name+" was not defined.";d=a.e.slice(b+1);a.e=a.e.slice(0,b);a.e=a.e.concat(h.e);a.e=a.e.concat(d)}else b+=1,g(d,c);d=a.e;if(i==="choice"&&(!d||!d[1]||d[1].name==="empty"))!d||!d[0]||d[0].name==="empty"?(delete a.e,a.name="empty"):(d[1]=d[0],d[0]={name:"empty"});if(i==="group"||i==="interleave")if(d[0].name==="empty")d[1].name==="empty"?(delete a.e,a.name="empty"):(i=a.name=d[1].name,a.names=d[1].names,f=a.e=d[1].e,d=f);else if(d[1].name==="empty")i=
a.name=d[0].name,a.names=d[0].names,j=a.e=d[0].e,d=j;if(i==="oneOrMore"&&d[0].name==="empty")delete a.e,a.name="empty";if(i==="attribute"){h=a.names?a.names.length:0;for(var k,q=a.localnames=[h],r=a.namespaces=[h],b=0;b<h;b+=1)k=e(a.names[b]),r[b]=k[0],q[b]=k[1]}if(i==="interleave")if(d[0].name==="interleave")d[1].name==="interleave"?a.e=d[0].e.concat(d[1].e):a.e=[d[1]].concat(d[0].e);else if(d[1].name==="interleave")a.e=[d[0]].concat(d[1].e)}function a(c,b){for(var d=0,e;c.e&&d<c.e.length;)e=c.e[d],
e.name==="elementref"?(e.id=e.id||0,c.e[d]=b[e.id]):e.name!=="element"&&a(e,b),d+=1}var b=this,h,c={"http://www.w3.org/XML/1998/namespace":"xml"},d;d=function(a,b,g){var h=[],i,u,n=a.localName,q=[];i=a.attributes;var r=n,y=q,C={},s,v;for(s=0;s<i.length;s+=1)if(v=i.item(s),v.namespaceURI){if(v.namespaceURI==="http://www.w3.org/2000/xmlns/")c[v.value]=v.localName}else{v.localName==="name"&&(r==="element"||r==="attribute")&&y.push(v.value);if(v.localName==="name"||v.localName==="combine"||v.localName===
"type"){var E=v,B;B=v.value;B=B.replace(/^\s\s*/,"");for(var z=/\s/,G=B.length-1;z.test(B.charAt(G));)G-=1;B=B.slice(0,G+1);E.value=B}C[v.localName]=v.value}i=C;i.combine=i.combine||void 0;a=a.firstChild;r=h;y=q;for(C="";a;){if(a.nodeType===1&&a.namespaceURI==="http://relaxng.org/ns/structure/1.0"){if(s=d(a,b,r))s.name==="name"?y.push(c[s.a.ns]+":"+s.text):s.name==="choice"&&s.names&&s.names.length&&(y=y.concat(s.names),delete s.names),r.push(s)}else a.nodeType===3&&(C+=a.nodeValue);a=a.nextSibling}a=
C;n!=="value"&&n!=="param"&&(a=/^\s*([\s\S]*\S)?\s*$/.exec(a)[1]);if(n==="value"&&i.type===void 0)i.type="token",i.datatypeLibrary="";if((n==="attribute"||n==="element")&&i.name!==void 0)u=e(i.name),h=[{name:"name",text:u[1],a:{ns:u[0]}}].concat(h),delete i.name;if(n==="name"||n==="nsName"||n==="value"){if(i.ns===void 0)i.ns=""}else delete i.ns;if(n==="name")u=e(a),i.ns=u[0],a=u[1];if(h.length>1&&(n==="define"||n==="oneOrMore"||n==="zeroOrMore"||n==="optional"||n==="list"||n==="mixed"))h=[{name:"group",
e:k({name:"group",e:h}).e}];h.length>2&&n==="element"&&(h=[h[0]].concat({name:"group",e:k({name:"group",e:h.slice(1)}).e}));h.length===1&&n==="attribute"&&h.push({name:"text",text:a});if(h.length===1&&(n==="choice"||n==="group"||n==="interleave"))n=h[0].name,q=h[0].names,i=h[0].a,a=h[0].text,h=h[0].e;else if(h.length>2&&(n==="choice"||n==="group"||n==="interleave"))h=k({name:n,e:h}).e;n==="mixed"&&(n="interleave",h=[h[0],{name:"text"}]);n==="optional"&&(n="choice",h=[h[0],{name:"empty"}]);n==="zeroOrMore"&&
(n="choice",h=[{name:"oneOrMore",e:[h[0]]},{name:"empty"}]);if(n==="define"&&i.combine){a:{r=i.combine;y=i.name;C=h;for(s=0;g&&s<g.length;s+=1)if(v=g[s],v.name==="define"&&v.a&&v.a.name===y){v.e=[{name:r,e:v.e.concat(C)}];g=v;break a}g=null}if(g)return}g={name:n};if(h&&h.length>0)g.e=h;for(u in i)if(i.hasOwnProperty(u)){g.a=i;break}if(a!==void 0)g.text=a;if(q&&q.length>0)g.names=q;if(n==="element")g.id=b.length,b.push(g),g={name:"elementref",id:g.id};return g};this.parseRelaxNGDOM=function(f,e){var k=
[],m=d(f&&f.documentElement,k,void 0),l,u,n={};for(l=0;l<m.e.length;l+=1)u=m.e[l],u.name==="define"?n[u.a.name]=u:u.name==="start"&&(h=u);if(!h)return[new i("No Relax NG start element was found.")];g(h,n);for(l in n)n.hasOwnProperty(l)&&g(n[l],n);for(l=0;l<k.length;l+=1)g(k[l],n);if(e)b.rootPattern=e(h.e[0],k);a(h,k);for(l=0;l<k.length;l+=1)a(k[l],k);b.start=h;b.elements=k;b.nsmap=c;return null}};
xmldom.RelaxNGParser=function(){function g(a,c){this.message=function(){c&&(a+=1===c.nodeType?" Element ":" Node ",a+=c.nodeName,c.nodeValue&&(a+=" with value '"+c.nodeValue+"'"),a+=".");return a}}function m(a){if(2>=a.e.length)return a;var c={name:a.name,e:a.e.slice(0,2)};return m({name:a.name,e:[c].concat(a.e.slice(2))})}function e(a){var a=a.split(":",2),c="",b;1===a.length?a=["",a[0]]:c=a[0];for(b in d)d[b]===c&&(a[0]=b);return a}function k(a,c){for(var b=0,d,g,o=a.name;a.e&&b<a.e.length;)if(d=
a.e[b],"ref"===d.name){g=c[d.a.name];if(!g)throw d.a.name+" was not defined.";d=a.e.slice(b+1);a.e=a.e.slice(0,b);a.e=a.e.concat(g.e);a.e=a.e.concat(d)}else b+=1,k(d,c);d=a.e;if("choice"===o&&(!d||!d[1]||"empty"===d[1].name))!d||!d[0]||"empty"===d[0].name?(delete a.e,a.name="empty"):(d[1]=d[0],d[0]={name:"empty"});if("group"===o||"interleave"===o)"empty"===d[0].name?"empty"===d[1].name?(delete a.e,a.name="empty"):(o=a.name=d[1].name,a.names=d[1].names,d=a.e=d[1].e):"empty"===d[1].name&&(o=a.name=
d[0].name,a.names=d[0].names,d=a.e=d[0].e);"oneOrMore"===o&&"empty"===d[0].name&&(delete a.e,a.name="empty");if("attribute"===o){g=a.names?a.names.length:0;for(var p,t=a.localnames=[g],r=a.namespaces=[g],b=0;b<g;b+=1)p=e(a.names[b]),r[b]=p[0],t[b]=p[1]}"interleave"===o&&("interleave"===d[0].name?"interleave"===d[1].name?a.e=d[0].e.concat(d[1].e):a.e=[d[1]].concat(d[0].e):"interleave"===d[1].name&&(a.e=[d[0]].concat(d[1].e)))}function a(c,d){for(var b=0,e;c.e&&b<c.e.length;)e=c.e[b],"elementref"===
e.name?(e.id=e.id||0,c.e[b]=d[e.id]):"element"!==e.name&&a(e,d),b+=1}var c=this,b,d={"http://www.w3.org/XML/1998/namespace":"xml"},o;o=function(a,c,b){var g=[],n,k,p=a.localName,t=[];n=a.attributes;var r=p,z=t,s={},q,u;for(q=0;q<n.length;q+=1)if(u=n.item(q),u.namespaceURI)"http://www.w3.org/2000/xmlns/"===u.namespaceURI&&(d[u.value]=u.localName);else{"name"===u.localName&&("element"===r||"attribute"===r)&&z.push(u.value);if("name"===u.localName||"combine"===u.localName||"type"===u.localName){var E=
u,C;C=u.value;C=C.replace(/^\s\s*/,"");for(var w=/\s/,G=C.length-1;w.test(C.charAt(G));)G-=1;C=C.slice(0,G+1);E.value=C}s[u.localName]=u.value}n=s;n.combine=n.combine||void 0;a=a.firstChild;r=g;z=t;for(s="";a;){if(1===a.nodeType&&"http://relaxng.org/ns/structure/1.0"===a.namespaceURI){if(q=o(a,c,r))"name"===q.name?z.push(d[q.a.ns]+":"+q.text):"choice"===q.name&&q.names&&q.names.length&&(z=z.concat(q.names),delete q.names),r.push(q)}else 3===a.nodeType&&(s+=a.nodeValue);a=a.nextSibling}a=s;"value"!==
p&&"param"!==p&&(a=/^\s*([\s\S]*\S)?\s*$/.exec(a)[1]);"value"===p&&void 0===n.type&&(n.type="token",n.datatypeLibrary="");if(("attribute"===p||"element"===p)&&void 0!==n.name)k=e(n.name),g=[{name:"name",text:k[1],a:{ns:k[0]}}].concat(g),delete n.name;"name"===p||"nsName"===p||"value"===p?void 0===n.ns&&(n.ns=""):delete n.ns;"name"===p&&(k=e(a),n.ns=k[0],a=k[1]);if(1<g.length&&("define"===p||"oneOrMore"===p||"zeroOrMore"===p||"optional"===p||"list"===p||"mixed"===p))g=[{name:"group",e:m({name:"group",
e:g}).e}];2<g.length&&"element"===p&&(g=[g[0]].concat({name:"group",e:m({name:"group",e:g.slice(1)}).e}));1===g.length&&"attribute"===p&&g.push({name:"text",text:a});if(1===g.length&&("choice"===p||"group"===p||"interleave"===p))p=g[0].name,t=g[0].names,n=g[0].a,a=g[0].text,g=g[0].e;else if(2<g.length&&("choice"===p||"group"===p||"interleave"===p))g=m({name:p,e:g}).e;"mixed"===p&&(p="interleave",g=[g[0],{name:"text"}]);"optional"===p&&(p="choice",g=[g[0],{name:"empty"}]);"zeroOrMore"===p&&(p="choice",
g=[{name:"oneOrMore",e:[g[0]]},{name:"empty"}]);if("define"===p&&n.combine){a:{r=n.combine;z=n.name;s=g;for(q=0;b&&q<b.length;q+=1)if(u=b[q],"define"===u.name&&u.a&&u.a.name===z){u.e=[{name:r,e:u.e.concat(s)}];b=u;break a}b=null}if(b)return}b={name:p};g&&0<g.length&&(b.e=g);for(k in n)if(n.hasOwnProperty(k)){b.a=n;break}void 0!==a&&(b.text=a);t&&0<t.length&&(b.names=t);"element"===p&&(b.id=c.length,c.push(b),b={name:"elementref",id:b.id});return b};this.parseRelaxNGDOM=function(f,e){var i=[],j=o(f&&
f.documentElement,i,void 0),n,m,p={};for(n=0;n<j.e.length;n+=1)m=j.e[n],"define"===m.name?p[m.a.name]=m:"start"===m.name&&(b=m);if(!b)return[new g("No Relax NG start element was found.")];k(b,p);for(n in p)p.hasOwnProperty(n)&&k(p[n],p);for(n=0;n<i.length;n+=1)k(i[n],p);e&&(c.rootPattern=e(b.e[0],i));a(b,i);for(n=0;n<i.length;n+=1)a(i[n],i);c.start=b;c.elements=i;c.nsmap=d;return null}};
// Input 15
runtime.loadClass("xmldom.RelaxNGParser");
xmldom.RelaxNG=function(){function i(a){return function(){var c;return function(){c===void 0&&(c=a());return c}}()}function k(a,c){return function(){var b={},d=0;return function(f){var e=f.hash||f.toString(),g;g=b[e];if(g!==void 0)return g;b[e]=g=c(f);g.hash=a+d.toString();d+=1;return g}}()}function e(a){return function(){var c={};return function(b){var d,f;f=c[b.localName];if(f===void 0)c[b.localName]=f={};else if(d=f[b.namespaceURI],d!==void 0)return d;return f[b.namespaceURI]=d=a(b)}}()}function g(a,
c,b){return function(){var d={},f=0;return function(e,g){var h=c&&c(e,g),j,i;if(h!==void 0)return h;h=e.hash||e.toString();j=g.hash||g.toString();i=d[h];if(i===void 0)d[h]=i={};else if(h=i[j],h!==void 0)return h;i[j]=h=b(e,g);h.hash=a+f.toString();f+=1;return h}}()}function a(c,b){b.p1.type==="choice"?a(c,b.p1):c[b.p1.hash]=b.p1;b.p2.type==="choice"?a(c,b.p2):c[b.p2.hash]=b.p2}function b(a,c){return{type:"element",nc:a,nullable:false,textDeriv:function(){return s},startTagOpenDeriv:function(b){return a.contains(b)?
l(c,v):s},attDeriv:function(){return s},startTagCloseDeriv:function(){return this}}}function h(){return{type:"list",nullable:false,hash:"list",textDeriv:function(){return v}}}function c(a,b,d,e){if(b===s)return s;if(e>=d.length)return b;e===0&&(e=0);for(var g=d.item(e);g.namespaceURI===f;){e+=1;if(e>=d.length)return b;g=d.item(e)}return g=c(a,b.attDeriv(a,d.item(e)),d,e+1)}function d(a,c,b){b.e[0].a?(a.push(b.e[0].text),c.push(b.e[0].a.ns)):d(a,c,b.e[0]);b.e[1].a?(a.push(b.e[1].text),c.push(b.e[1].a.ns)):
d(a,c,b.e[1])}var f="http://www.w3.org/2000/xmlns/",j,p,m,l,u,n,q,r,y,C,s={type:"notAllowed",nullable:false,hash:"notAllowed",textDeriv:function(){return s},startTagOpenDeriv:function(){return s},attDeriv:function(){return s},startTagCloseDeriv:function(){return s},endTagDeriv:function(){return s}},v={type:"empty",nullable:true,hash:"empty",textDeriv:function(){return s},startTagOpenDeriv:function(){return s},attDeriv:function(){return s},startTagCloseDeriv:function(){return v},endTagDeriv:function(){return s}},
E={type:"text",nullable:true,hash:"text",textDeriv:function(){return E},startTagOpenDeriv:function(){return s},attDeriv:function(){return s},startTagCloseDeriv:function(){return E},endTagDeriv:function(){return s}},B,z,G;j=g("choice",function(a,c){if(a===s)return c;if(c===s)return a;if(a===c)return a},function(c,b){var d={},f;a(d,{p1:c,p2:b});b=c=void 0;for(f in d)d.hasOwnProperty(f)&&(c===void 0?c=d[f]:b=b===void 0?d[f]:j(b,d[f]));return function(a,c){return{type:"choice",p1:a,p2:c,nullable:a.nullable||
c.nullable,textDeriv:function(b,d){return j(a.textDeriv(b,d),c.textDeriv(b,d))},startTagOpenDeriv:e(function(b){return j(a.startTagOpenDeriv(b),c.startTagOpenDeriv(b))}),attDeriv:function(b,d){return j(a.attDeriv(b,d),c.attDeriv(b,d))},startTagCloseDeriv:i(function(){return j(a.startTagCloseDeriv(),c.startTagCloseDeriv())}),endTagDeriv:i(function(){return j(a.endTagDeriv(),c.endTagDeriv())})}}(c,b)});p=function(a,c,b){return function(){var d={},f=0;return function(e,g){var h=c&&c(e,g),j,i;if(h!==
void 0)return h;h=e.hash||e.toString();j=g.hash||g.toString();h<j&&(i=h,h=j,j=i,i=e,e=g,g=i);i=d[h];if(i===void 0)d[h]=i={};else if(h=i[j],h!==void 0)return h;i[j]=h=b(e,g);h.hash=a+f.toString();f+=1;return h}}()}("interleave",function(a,c){if(a===s||c===s)return s;if(a===v)return c;if(c===v)return a},function(a,c){return{type:"interleave",p1:a,p2:c,nullable:a.nullable&&c.nullable,textDeriv:function(b,d){return j(p(a.textDeriv(b,d),c),p(a,c.textDeriv(b,d)))},startTagOpenDeriv:e(function(b){return j(B(function(a){return p(a,
c)},a.startTagOpenDeriv(b)),B(function(c){return p(a,c)},c.startTagOpenDeriv(b)))}),attDeriv:function(b,d){return j(p(a.attDeriv(b,d),c),p(a,c.attDeriv(b,d)))},startTagCloseDeriv:i(function(){return p(a.startTagCloseDeriv(),c.startTagCloseDeriv())})}});m=g("group",function(a,c){if(a===s||c===s)return s;if(a===v)return c;if(c===v)return a},function(a,c){return{type:"group",p1:a,p2:c,nullable:a.nullable&&c.nullable,textDeriv:function(b,d){var f=m(a.textDeriv(b,d),c);return a.nullable?j(f,c.textDeriv(b,
d)):f},startTagOpenDeriv:function(b){var d=B(function(a){return m(a,c)},a.startTagOpenDeriv(b));return a.nullable?j(d,c.startTagOpenDeriv(b)):d},attDeriv:function(b,d){return j(m(a.attDeriv(b,d),c),m(a,c.attDeriv(b,d)))},startTagCloseDeriv:i(function(){return m(a.startTagCloseDeriv(),c.startTagCloseDeriv())})}});l=g("after",function(a,c){if(a===s||c===s)return s},function(a,c){return{type:"after",p1:a,p2:c,nullable:false,textDeriv:function(b,d){return l(a.textDeriv(b,d),c)},startTagOpenDeriv:e(function(b){return B(function(a){return l(a,
c)},a.startTagOpenDeriv(b))}),attDeriv:function(b,d){return l(a.attDeriv(b,d),c)},startTagCloseDeriv:i(function(){return l(a.startTagCloseDeriv(),c)}),endTagDeriv:i(function(){return a.nullable?c:s})}});u=k("oneormore",function(a){return a===s?s:{type:"oneOrMore",p:a,nullable:a.nullable,textDeriv:function(c,b){return m(a.textDeriv(c,b),j(this,v))},startTagOpenDeriv:function(c){var b=this;return B(function(a){return m(a,j(b,v))},a.startTagOpenDeriv(c))},attDeriv:function(c,b){return m(a.attDeriv(c,
b),j(this,v))},startTagCloseDeriv:i(function(){return u(a.startTagCloseDeriv())})}});q=g("attribute",void 0,function(a,c){return{type:"attribute",nullable:false,nc:a,p:c,attDeriv:function(b,d){return a.contains(d)&&(c.nullable&&/^\s+$/.test(d.nodeValue)||c.textDeriv(b,d.nodeValue).nullable)?v:s},startTagCloseDeriv:function(){return s}}});n=k("value",function(a){return{type:"value",nullable:false,value:a,textDeriv:function(c,b){return b===a?v:s},attDeriv:function(){return s},startTagCloseDeriv:function(){return this}}});
y=k("data",function(a){return{type:"data",nullable:false,dataType:a,textDeriv:function(){return v},attDeriv:function(){return s},startTagCloseDeriv:function(){return this}}});B=function x(a,c){if(c.type==="after")return l(c.p1,a(c.p2));else if(c.type==="choice")return j(x(a,c.p1),x(a,c.p2));return c};z=function(a,b,d){for(var f=d.currentNode,b=b.startTagOpenDeriv(f),b=c(a,b,f.attributes,0),e=b=b.startTagCloseDeriv(),f=d.currentNode,b=d.firstChild(),g=0,h=[];b;)b.nodeType===1?h.push(b):b.nodeType===
3&&!/^\s*$/.test(b.nodeValue)&&(h.push(b.nodeValue),g+=1),b=d.nextSibling();h.length===0&&(h=[""]);g=e;for(e=0;g!==s&&e<h.length;e+=1)b=h[e],typeof b==="string"?g=/^\s*$/.test(b)?j(g,g.textDeriv(a,b)):g.textDeriv(a,b):(d.currentNode=b,g=z(a,g,d));d.currentNode=f;return b=g.endTagDeriv()};r=function(a){var c,b,f;if(a.name==="name")return c=a.text,b=a.a.ns,{name:c,ns:b,hash:"{"+b+"}"+c,contains:function(a){return a.namespaceURI===b&&a.localName===c}};else if(a.name==="choice"){c=[];b=[];d(c,b,a);a=
"";for(f=0;f<c.length;f+=1)a+="{"+b[f]+"}"+c[f]+",";return{hash:a,contains:function(a){var d;for(d=0;d<c.length;d+=1)if(c[d]===a.localName&&b[d]===a.namespaceURI)return true;return false}}}return{hash:"anyName",contains:function(){return true}}};C=function t(a,c){var d,f;if(a.name==="elementref"){d=a.id||0;a=c[d];if(a.name!==void 0){var e=a;d=c[e.id]={hash:"element"+e.id.toString()};e=b(r(e.e[0]),C(e.e[1],c));for(f in e)e.hasOwnProperty(f)&&(d[f]=e[f]);f=d}else f=a;return f}switch(a.name){case "empty":return v;
case "notAllowed":return s;case "text":return E;case "choice":return j(t(a.e[0],c),t(a.e[1],c));case "interleave":d=t(a.e[0],c);for(f=1;f<a.e.length;f+=1)d=p(d,t(a.e[f],c));return d;case "group":return m(t(a.e[0],c),t(a.e[1],c));case "oneOrMore":return u(t(a.e[0],c));case "attribute":return q(r(a.e[0]),t(a.e[1],c));case "value":return n(a.text);case "data":return d=a.a&&a.a.type,d===void 0&&(d=""),y(d);case "list":return h()}throw"No support for "+a.name;};this.makePattern=function(a,c){var b={},
d;for(d in c)c.hasOwnProperty(d)&&(b[d]=c[d]);return d=C(a,b)};this.validate=function(a,c){var b;a.currentNode=a.root;b=z(null,G,a);b.nullable?c(null):(runtime.log("Error in Relax NG validation: "+b),c(["Error in Relax NG validation: "+b]))};this.init=function(a){G=a}};
xmldom.RelaxNG=function(){function g(a){return function(){var c;return function(){void 0===c&&(c=a());return c}}()}function m(a,c){return function(){var b={},d=0;return function(f){var e=f.hash||f.toString(),g;g=b[e];if(void 0!==g)return g;b[e]=g=c(f);g.hash=a+d.toString();d+=1;return g}}()}function e(a){return function(){var c={};return function(b){var d,f;f=c[b.localName];if(void 0===f)c[b.localName]=f={};else if(d=f[b.namespaceURI],void 0!==d)return d;return f[b.namespaceURI]=d=a(b)}}()}function k(a,
c,b){return function(){var d={},f=0;return function(e,g){var h=c&&c(e,g),j,i;if(void 0!==h)return h;h=e.hash||e.toString();j=g.hash||g.toString();i=d[h];if(void 0===i)d[h]=i={};else if(h=i[j],void 0!==h)return h;i[j]=h=b(e,g);h.hash=a+f.toString();f+=1;return h}}()}function a(c,b){"choice"===b.p1.type?a(c,b.p1):c[b.p1.hash]=b.p1;"choice"===b.p2.type?a(c,b.p2):c[b.p2.hash]=b.p2}function c(a,c){return{type:"element",nc:a,nullable:!1,textDeriv:function(){return q},startTagOpenDeriv:function(b){return a.contains(b)?
n(c,u):q},attDeriv:function(){return q},startTagCloseDeriv:function(){return this}}}function b(){return{type:"list",nullable:!1,hash:"list",textDeriv:function(){return u}}}function d(a,c,b,e){if(c===q)return q;if(e>=b.length)return c;0===e&&(e=0);for(var g=b.item(e);g.namespaceURI===f;){e+=1;if(e>=b.length)return c;g=b.item(e)}return g=d(a,c.attDeriv(a,b.item(e)),b,e+1)}function o(a,c,b){b.e[0].a?(a.push(b.e[0].text),c.push(b.e[0].a.ns)):o(a,c,b.e[0]);b.e[1].a?(a.push(b.e[1].text),c.push(b.e[1].a.ns)):
o(a,c,b.e[1])}var f="http://www.w3.org/2000/xmlns/",h,i,j,n,x,p,t,r,z,s,q={type:"notAllowed",nullable:!1,hash:"notAllowed",textDeriv:function(){return q},startTagOpenDeriv:function(){return q},attDeriv:function(){return q},startTagCloseDeriv:function(){return q},endTagDeriv:function(){return q}},u={type:"empty",nullable:!0,hash:"empty",textDeriv:function(){return q},startTagOpenDeriv:function(){return q},attDeriv:function(){return q},startTagCloseDeriv:function(){return u},endTagDeriv:function(){return q}},
E={type:"text",nullable:!0,hash:"text",textDeriv:function(){return E},startTagOpenDeriv:function(){return q},attDeriv:function(){return q},startTagCloseDeriv:function(){return E},endTagDeriv:function(){return q}},C,w,G;h=k("choice",function(a,b){if(a===q)return b;if(b===q||a===b)return a},function(b,c){var d={},f;a(d,{p1:b,p2:c});c=b=void 0;for(f in d)d.hasOwnProperty(f)&&(void 0===b?b=d[f]:c=void 0===c?d[f]:h(c,d[f]));return function(a,b){return{type:"choice",p1:a,p2:b,nullable:a.nullable||b.nullable,
textDeriv:function(c,d){return h(a.textDeriv(c,d),b.textDeriv(c,d))},startTagOpenDeriv:e(function(c){return h(a.startTagOpenDeriv(c),b.startTagOpenDeriv(c))}),attDeriv:function(c,d){return h(a.attDeriv(c,d),b.attDeriv(c,d))},startTagCloseDeriv:g(function(){return h(a.startTagCloseDeriv(),b.startTagCloseDeriv())}),endTagDeriv:g(function(){return h(a.endTagDeriv(),b.endTagDeriv())})}}(b,c)});i=function(a,b,c){return function(){var d={},f=0;return function(e,g){var h=b&&b(e,g),j,i;if(void 0!==h)return h;
h=e.hash||e.toString();j=g.hash||g.toString();h<j&&(i=h,h=j,j=i,i=e,e=g,g=i);i=d[h];if(void 0===i)d[h]=i={};else if(h=i[j],void 0!==h)return h;i[j]=h=c(e,g);h.hash=a+f.toString();f+=1;return h}}()}("interleave",function(a,b){if(a===q||b===q)return q;if(a===u)return b;if(b===u)return a},function(a,b){return{type:"interleave",p1:a,p2:b,nullable:a.nullable&&b.nullable,textDeriv:function(c,d){return h(i(a.textDeriv(c,d),b),i(a,b.textDeriv(c,d)))},startTagOpenDeriv:e(function(c){return h(C(function(a){return i(a,
b)},a.startTagOpenDeriv(c)),C(function(b){return i(a,b)},b.startTagOpenDeriv(c)))}),attDeriv:function(c,d){return h(i(a.attDeriv(c,d),b),i(a,b.attDeriv(c,d)))},startTagCloseDeriv:g(function(){return i(a.startTagCloseDeriv(),b.startTagCloseDeriv())})}});j=k("group",function(a,b){if(a===q||b===q)return q;if(a===u)return b;if(b===u)return a},function(a,b){return{type:"group",p1:a,p2:b,nullable:a.nullable&&b.nullable,textDeriv:function(c,d){var f=j(a.textDeriv(c,d),b);return a.nullable?h(f,b.textDeriv(c,
d)):f},startTagOpenDeriv:function(c){var d=C(function(a){return j(a,b)},a.startTagOpenDeriv(c));return a.nullable?h(d,b.startTagOpenDeriv(c)):d},attDeriv:function(c,d){return h(j(a.attDeriv(c,d),b),j(a,b.attDeriv(c,d)))},startTagCloseDeriv:g(function(){return j(a.startTagCloseDeriv(),b.startTagCloseDeriv())})}});n=k("after",function(a,b){if(a===q||b===q)return q},function(a,b){return{type:"after",p1:a,p2:b,nullable:!1,textDeriv:function(c,d){return n(a.textDeriv(c,d),b)},startTagOpenDeriv:e(function(c){return C(function(a){return n(a,
b)},a.startTagOpenDeriv(c))}),attDeriv:function(c,d){return n(a.attDeriv(c,d),b)},startTagCloseDeriv:g(function(){return n(a.startTagCloseDeriv(),b)}),endTagDeriv:g(function(){return a.nullable?b:q})}});x=m("oneormore",function(a){return a===q?q:{type:"oneOrMore",p:a,nullable:a.nullable,textDeriv:function(b,c){return j(a.textDeriv(b,c),h(this,u))},startTagOpenDeriv:function(b){var c=this;return C(function(a){return j(a,h(c,u))},a.startTagOpenDeriv(b))},attDeriv:function(b,c){return j(a.attDeriv(b,
c),h(this,u))},startTagCloseDeriv:g(function(){return x(a.startTagCloseDeriv())})}});t=k("attribute",void 0,function(a,b){return{type:"attribute",nullable:!1,nc:a,p:b,attDeriv:function(c,d){return a.contains(d)&&(b.nullable&&/^\s+$/.test(d.nodeValue)||b.textDeriv(c,d.nodeValue).nullable)?u:q},startTagCloseDeriv:function(){return q}}});p=m("value",function(a){return{type:"value",nullable:!1,value:a,textDeriv:function(b,c){return c===a?u:q},attDeriv:function(){return q},startTagCloseDeriv:function(){return this}}});
z=m("data",function(a){return{type:"data",nullable:!1,dataType:a,textDeriv:function(){return u},attDeriv:function(){return q},startTagCloseDeriv:function(){return this}}});C=function v(a,b){return"after"===b.type?n(b.p1,a(b.p2)):"choice"===b.type?h(v(a,b.p1),v(a,b.p2)):b};w=function(a,b,c){for(var f=c.currentNode,b=b.startTagOpenDeriv(f),b=d(a,b,f.attributes,0),e=b=b.startTagCloseDeriv(),f=c.currentNode,b=c.firstChild(),g=[],j;b;)1===b.nodeType?g.push(b):3===b.nodeType&&!/^\s*$/.test(b.nodeValue)&&
g.push(b.nodeValue),b=c.nextSibling();0===g.length&&(g=[""]);j=e;for(e=0;j!==q&&e<g.length;e+=1)b=g[e],"string"===typeof b?j=/^\s*$/.test(b)?h(j,j.textDeriv(a,b)):j.textDeriv(a,b):(c.currentNode=b,j=w(a,j,c));c.currentNode=f;return b=j.endTagDeriv()};r=function(a){var b,c,d;if("name"===a.name)return b=a.text,c=a.a.ns,{name:b,ns:c,hash:"{"+c+"}"+b,contains:function(a){return a.namespaceURI===c&&a.localName===b}};if("choice"===a.name){b=[];c=[];o(b,c,a);a="";for(d=0;d<b.length;d+=1)a+="{"+c[d]+"}"+
b[d]+",";return{hash:a,contains:function(a){var d;for(d=0;d<b.length;d+=1)if(b[d]===a.localName&&c[d]===a.namespaceURI)return!0;return!1}}}return{hash:"anyName",contains:function(){return!0}}};s=function y(a,d){var f,e;if("elementref"===a.name){f=a.id||0;a=d[f];if(void 0!==a.name){var g=a;f=d[g.id]={hash:"element"+g.id.toString()};g=c(r(g.e[0]),s(g.e[1],d));for(e in g)g.hasOwnProperty(e)&&(f[e]=g[e]);e=f}else e=a;return e}switch(a.name){case "empty":return u;case "notAllowed":return q;case "text":return E;
case "choice":return h(y(a.e[0],d),y(a.e[1],d));case "interleave":f=y(a.e[0],d);for(e=1;e<a.e.length;e+=1)f=i(f,y(a.e[e],d));return f;case "group":return j(y(a.e[0],d),y(a.e[1],d));case "oneOrMore":return x(y(a.e[0],d));case "attribute":return t(r(a.e[0]),y(a.e[1],d));case "value":return p(a.text);case "data":return f=a.a&&a.a.type,void 0===f&&(f=""),z(f);case "list":return b()}throw"No support for "+a.name;};this.makePattern=function(a,b){var c={},d;for(d in b)b.hasOwnProperty(d)&&(c[d]=b[d]);return d=
s(a,c)};this.validate=function(a,b){var c;a.currentNode=a.root;c=w(null,G,a);c.nullable?b(null):(runtime.log("Error in Relax NG validation: "+c),b(["Error in Relax NG validation: "+c]))};this.init=function(a){G=a}};
// Input 16
runtime.loadClass("xmldom.RelaxNGParser");
xmldom.RelaxNG2=function(){function i(a,b){this.message=function(){b&&(a+=b.nodeType===1?" Element ":" Node ",a+=b.nodeName,b.nodeValue&&(a+=" with value '"+b.nodeValue+"'"),a+=".");return a}}function k(c,b,f,e){return c.name==="empty"?null:a(c,b,f,e)}function e(a,d){if(a.e.length!==2)throw"Element with wrong # of elements: "+a.e.length;h+=1;for(var f=d.currentNode,e=f?f.nodeType:0,g=null;e>1;){if(e!==8&&(e!==3||!/^\s+$/.test(d.currentNode.nodeValue)))return h-=1,[new i("Not allowed node of type "+
e+".")];e=(f=d.nextSibling())?f.nodeType:0}if(!f)return h-=1,[new i("Missing element "+a.names)];if(a.names&&a.names.indexOf(b[f.namespaceURI]+":"+f.localName)===-1)return h-=1,[new i("Found "+f.nodeName+" instead of "+a.names+".",f)];if(d.firstChild()){for(g=k(a.e[1],d,f);d.nextSibling();)if(e=d.currentNode.nodeType,(!d.currentNode||!(d.currentNode.nodeType===3&&/^\s+$/.test(d.currentNode.nodeValue)))&&e!==8)return h-=1,[new i("Spurious content.",d.currentNode)];if(d.parentNode()!==f)return h-=1,
[new i("Implementation error.")]}else g=k(a.e[1],d,f);h-=1;d.nextSibling();return g}var g,a,b,h=0;a=function(b,d,f,g){var h=b.name,m=null;if(h==="text")a:{for(var l=(b=d.currentNode)?b.nodeType:0;b!==f&&l!==3;){if(l===1){m=[new i("Element not allowed here.",b)];break a}l=(b=d.nextSibling())?b.nodeType:0}d.nextSibling();m=null}else if(h==="data")m=null;else if(h==="value")g!==b.text&&(m=[new i("Wrong value, should be '"+b.text+"', not '"+g+"'",f)]);else if(h==="list")m=null;else if(h==="attribute")a:{if(b.e.length!==
2)throw"Attribute with wrong # of elements: "+b.e.length;h=b.localnames.length;for(m=0;m<h;m+=1){g=f.getAttributeNS(b.namespaces[m],b.localnames[m]);g===""&&!f.hasAttributeNS(b.namespaces[m],b.localnames[m])&&(g=void 0);if(l!==void 0&&g!==void 0){m=[new i("Attribute defined too often.",f)];break a}l=g}m=l===void 0?[new i("Attribute not found: "+b.names,f)]:k(b.e[1],d,f,l)}else if(h==="element")m=e(b,d,f);else if(h==="oneOrMore"){g=0;do l=d.currentNode,h=a(b.e[0],d,f),g+=1;while(!h&&l!==d.currentNode);
g>1?(d.currentNode=l,m=null):m=h}else if(h==="choice"){if(b.e.length!==2)throw"Choice with wrong # of options: "+b.e.length;l=d.currentNode;if(b.e[0].name==="empty"){if(h=a(b.e[1],d,f,g))d.currentNode=l;m=null}else{if(h=k(b.e[0],d,f,g))d.currentNode=l,h=a(b.e[1],d,f,g);m=h}}else if(h==="group"){if(b.e.length!==2)throw"Group with wrong # of members: "+b.e.length;m=a(b.e[0],d,f)||a(b.e[1],d,f)}else if(h==="interleave")a:{for(var l=b.e.length,g=[l],u=l,n,q,r,y;u>0;){n=0;q=d.currentNode;for(m=0;m<l;m+=
1)if(r=d.currentNode,g[m]!==true&&g[m]!==r)y=b.e[m],(h=a(y,d,f))?(d.currentNode=r,g[m]===void 0&&(g[m]=false)):r===d.currentNode||y.name==="oneOrMore"||y.name==="choice"&&(y.e[0].name==="oneOrMore"||y.e[1].name==="oneOrMore")?(n+=1,g[m]=r):(n+=1,g[m]=true);if(q===d.currentNode&&n===u)break;if(n===0){for(m=0;m<l;m+=1)if(g[m]===false){m=[new i("Interleave does not match.",f)];break a}break}for(m=u=0;m<l;m+=1)g[m]!==true&&(u+=1)}m=null}else throw h+" not allowed in nonEmptyPattern.";return m};this.validate=
function(a,b){a.currentNode=a.root;var f=k(g.e[0],a,a.root);b(f)};this.init=function(a,d){g=a;b=d}};
xmldom.RelaxNG2=function(){function g(a,c){this.message=function(){c&&(a+=1===c.nodeType?" Element ":" Node ",a+=c.nodeName,c.nodeValue&&(a+=" with value '"+c.nodeValue+"'"),a+=".");return a}}function m(b,c,e,f){return"empty"===b.name?null:a(b,c,e,f)}function e(a,d){if(2!==a.e.length)throw"Element with wrong # of elements: "+a.e.length;for(var e=d.currentNode,f=e?e.nodeType:0,h=null;1<f;){if(8!==f&&(3!==f||!/^\s+$/.test(d.currentNode.nodeValue)))return[new g("Not allowed node of type "+f+".")];f=
(e=d.nextSibling())?e.nodeType:0}if(!e)return[new g("Missing element "+a.names)];if(a.names&&-1===a.names.indexOf(c[e.namespaceURI]+":"+e.localName))return[new g("Found "+e.nodeName+" instead of "+a.names+".",e)];if(d.firstChild()){for(h=m(a.e[1],d,e);d.nextSibling();)if(f=d.currentNode.nodeType,(!d.currentNode||!(3===d.currentNode.nodeType&&/^\s+$/.test(d.currentNode.nodeValue)))&&8!==f)return[new g("Spurious content.",d.currentNode)];if(d.parentNode()!==e)return[new g("Implementation error.")]}else h=
m(a.e[1],d,e);d.nextSibling();return h}var k,a,c;a=function(b,c,o,f){var h=b.name,i=null;if("text"===h)a:{for(var j=(b=c.currentNode)?b.nodeType:0;b!==o&&3!==j;){if(1===j){i=[new g("Element not allowed here.",b)];break a}j=(b=c.nextSibling())?b.nodeType:0}c.nextSibling();i=null}else if("data"===h)i=null;else if("value"===h)f!==b.text&&(i=[new g("Wrong value, should be '"+b.text+"', not '"+f+"'",o)]);else if("list"===h)i=null;else if("attribute"===h)a:{if(2!==b.e.length)throw"Attribute with wrong # of elements: "+
b.e.length;h=b.localnames.length;for(i=0;i<h;i+=1){f=o.getAttributeNS(b.namespaces[i],b.localnames[i]);""===f&&!o.hasAttributeNS(b.namespaces[i],b.localnames[i])&&(f=void 0);if(void 0!==j&&void 0!==f){i=[new g("Attribute defined too often.",o)];break a}j=f}i=void 0===j?[new g("Attribute not found: "+b.names,o)]:m(b.e[1],c,o,j)}else if("element"===h)i=e(b,c,o);else if("oneOrMore"===h){f=0;do j=c.currentNode,h=a(b.e[0],c,o),f+=1;while(!h&&j!==c.currentNode);1<f?(c.currentNode=j,i=null):i=h}else if("choice"===
h){if(2!==b.e.length)throw"Choice with wrong # of options: "+b.e.length;j=c.currentNode;if("empty"===b.e[0].name){if(h=a(b.e[1],c,o,f))c.currentNode=j;i=null}else{if(h=m(b.e[0],c,o,f))c.currentNode=j,h=a(b.e[1],c,o,f);i=h}}else if("group"===h){if(2!==b.e.length)throw"Group with wrong # of members: "+b.e.length;i=a(b.e[0],c,o)||a(b.e[1],c,o)}else if("interleave"===h)a:{for(var j=b.e.length,f=[j],n=j,k,p,t,r;0<n;){k=0;p=c.currentNode;for(i=0;i<j;i+=1)t=c.currentNode,!0!==f[i]&&f[i]!==t&&(r=b.e[i],(h=
a(r,c,o))?(c.currentNode=t,void 0===f[i]&&(f[i]=!1)):t===c.currentNode||"oneOrMore"===r.name||"choice"===r.name&&("oneOrMore"===r.e[0].name||"oneOrMore"===r.e[1].name)?(k+=1,f[i]=t):(k+=1,f[i]=!0));if(p===c.currentNode&&k===n)break;if(0===k){for(i=0;i<j;i+=1)if(!1===f[i]){i=[new g("Interleave does not match.",o)];break a}break}for(i=n=0;i<j;i+=1)!0!==f[i]&&(n+=1)}i=null}else throw h+" not allowed in nonEmptyPattern.";return i};this.validate=function(a,c){a.currentNode=a.root;var e=m(k.e[0],a,a.root);
c(e)};this.init=function(a,d){k=a;c=d}};
// Input 17
xmldom.OperationalTransformInterface=function(){};xmldom.OperationalTransformInterface.prototype.retain=function(){};xmldom.OperationalTransformInterface.prototype.insertCharacters=function(){};xmldom.OperationalTransformInterface.prototype.insertElementStart=function(){};xmldom.OperationalTransformInterface.prototype.insertElementEnd=function(){};xmldom.OperationalTransformInterface.prototype.deleteCharacters=function(){};xmldom.OperationalTransformInterface.prototype.deleteElementStart=function(){};
xmldom.OperationalTransformInterface.prototype.deleteElementEnd=function(){};xmldom.OperationalTransformInterface.prototype.replaceAttributes=function(){};xmldom.OperationalTransformInterface.prototype.updateAttributes=function(){};
// Input 18
xmldom.OperationalTransformDOM=function(){this.retain=function(){};this.insertCharacters=function(){};this.insertElementStart=function(){};this.insertElementEnd=function(){};this.deleteCharacters=function(){};this.deleteElementStart=function(){};this.deleteElementEnd=function(){};this.replaceAttributes=function(){};this.updateAttributes=function(){};this.atEnd=function(){return true}};
xmldom.OperationalTransformDOM=function(){this.retain=function(){};this.insertCharacters=function(){};this.insertElementStart=function(){};this.insertElementEnd=function(){};this.deleteCharacters=function(){};this.deleteElementStart=function(){};this.deleteElementEnd=function(){};this.replaceAttributes=function(){};this.updateAttributes=function(){};this.atEnd=function(){return!0}};
// Input 19
xmldom.XPath=function(){function i(i,e,g){i=i.ownerDocument.evaluate(e,i,g,XPathResult.UNORDERED_NODE_ITERATOR_TYPE,null);e=[];for(g=i.iterateNext();g!==null;)g.nodeType===1&&e.push(g),g=i.iterateNext();return e}xmldom.XPath=function(){this.getODFElementsWithXPath=i};return xmldom.XPath}();
xmldom.XPath=function(){function g(a,c,b){return-1!==a&&(a<c||-1===c)&&(a<b||-1===b)}function m(a){for(var c=[],b=0,d=a.length,f;b<d;){var e=a,h=d,o=c,k="",u=[],m=e.indexOf("[",b),C=e.indexOf("/",b),w=e.indexOf("=",b);g(C,m,w)?(k=e.substring(b,C),b=C+1):g(m,C,w)?(k=e.substring(b,m),b=i(e,m,u)):g(w,C,m)?(k=e.substring(b,w),b=w):(k=e.substring(b,h),b=h);o.push({location:k,predicates:u});if(b<d&&"="===a[b]){f=a.substring(b+1,d);if(2<f.length&&("'"===f[0]||'"'===f[0]))f=f.slice(1,f.length-1);else try{f=
parseInt(f,10)}catch(G){}b=d}}return{steps:c,value:f}}function e(){}function k(){var a,c=!1;this.setNode=function(c){a=c};this.reset=function(){c=!1};this.next=function(){var b=c?null:a;c=!0;return b}}function a(a,c,b){this.reset=function(){a.reset()};this.next=function(){for(var d=a.next();d&&!(d=d.getAttributeNodeNS(c,b));)d=a.next();return d}}function c(a,c){var b=a.next(),d=null;this.reset=function(){a.reset();b=a.next();d=null};this.next=function(){for(;b;){if(d)if(c&&d.firstChild)d=d.firstChild;
else{for(;!d.nextSibling&&d!==b;)d=d.parentNode;d===b?b=a.next():d=d.nextSibling}else{do(d=b.firstChild)||(b=a.next());while(b&&!d)}if(d&&1===d.nodeType)return d}return null}}function b(a,b){this.reset=function(){a.reset()};this.next=function(){for(var c=a.next();c&&!b(c);)c=a.next();return c}}function d(a,c,d){var c=c.split(":",2),f=d(c[0]),e=c[1];return new b(a,function(a){return a.localName===e&&a.namespaceURI===f})}function o(a,c,d){var f=new k,e=h(f,c,d),g=c.value;return void 0===g?new b(a,function(a){f.setNode(a);
e.reset();return e.next()}):new b(a,function(a){f.setNode(a);e.reset();return(a=e.next())&&a.nodeValue===g})}function f(a,c,b){var d=a.ownerDocument,f=[],f=new k;f.setNode(a);a=m(c);f=h(f,a,b);a=[];for(b=f.next();b;)a.push(b),b=f.next();return f=a}var h,i;i=function(a,c,b){for(var d=c,f=a.length,e=0;d<f;)"]"===a[d]?(e-=1,0>=e&&b.push(m(a.substring(c,d)))):"["===a[d]&&(0>=e&&(c=d+1),e+=1),d+=1;return d};e.prototype.next=function(){};e.prototype.reset=function(){};h=function(b,f,e){var g,h,i,k;for(g=
0;g<f.steps.length;g+=1){i=f.steps[g];h=i.location;""===h?b=new c(b,!1):"@"===h[0]?(k=h.slice(1).split(":",2),b=new a(b,e(k[0]),k[1])):"."!==h&&(b=new c(b,!1),-1!==h.indexOf(":")&&(b=d(b,h,e)));for(h=0;h<i.predicates.length;h+=1)k=i.predicates[h],b=o(b,k,e)}return b};xmldom.XPath=function(){this.getODFElementsWithXPath=f};return xmldom.XPath}();
// Input 20
odf.StyleInfo=function(){function i(e,g){for(var a=k[e.localName],b=a&&a[e.namespaceURI],h=b?b.length:0,c,d,f,a=0;a<h;a+=1)if(c=e.getAttributeNS(b[a].ns,b[a].localname))d=b[a].keygroup,(f=g[d])||(f=g[d]={}),f[c]=1;for(a=e.firstChild;a;)a.nodeType===1&&(b=a,i(b,g)),a=a.nextSibling}var k;this.UsedKeysList=function(e){var g={};this.uses=function(a){var b=a.localName,e=a.getAttributeNS("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0","name")||a.getAttributeNS("urn:oasis:names:tc:opendocument:xmlns:style:1.0",
"name"),a=b==="style"?a.getAttributeNS("urn:oasis:names:tc:opendocument:xmlns:style:1.0","family"):a.namespaceURI==="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"?"data":b;return(a=g[a])?a[e]>0:false};i(e,g)};this.canElementHaveStyle=function(e,g){var a=k[g.localName];return(a=a&&a[g.namespaceURI])&&a.length>0};k=function(e){var j;var g,a,b,h,c,d={},f;for(g in e)if(e.hasOwnProperty(g)){b=e[g];c=b.length;for(a=0;a<c;a+=1)h=b[a],f=d[h.en]=d[h.en]||{},j=f[h.ens]=f[h.ens]||[],f=j,f.push({ns:h.ans,
localname:h.a,keygroup:g})}return d}({text:[{ens:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",en:"tab-stop",ans:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",a:"leader-text-style"},{ens:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",en:"drop-cap",ans:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",a:"style-name"},{ens:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",en:"notes-configuration",ans:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",a:"citation-body-style-name"},{ens:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",
odf.StyleInfo=function(){function g(e,k){for(var a=m[e.localName],c=a&&a[e.namespaceURI],b=c?c.length:0,d,o,f,a=0;a<b;a+=1)if(d=e.getAttributeNS(c[a].ns,c[a].localname))o=c[a].keygroup,(f=k[o])||(f=k[o]={}),f[d]=1;for(a=e.firstChild;a;)1===a.nodeType&&(c=a,g(c,k)),a=a.nextSibling}var m;this.UsedKeysList=function(e){var k={};this.uses=function(a){var c=a.localName,b=a.getAttributeNS("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0","name")||a.getAttributeNS("urn:oasis:names:tc:opendocument:xmlns:style:1.0",
"name"),a="style"===c?a.getAttributeNS("urn:oasis:names:tc:opendocument:xmlns:style:1.0","family"):"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"===a.namespaceURI?"data":c;return(a=k[a])?0<a[b]:!1};g(e,k)};this.canElementHaveStyle=function(e,g){var a=m[g.localName];return(a=a&&a[g.namespaceURI])&&0<a.length};m=function(e){var g,a,c,b,d,o={},f;for(g in e)if(e.hasOwnProperty(g)){c=e[g];d=c.length;for(a=0;a<d;a+=1)b=c[a],f=o[b.en]=o[b.en]||{},f=f[b.ens]=f[b.ens]||[],f.push({ns:b.ans,localname:b.a,
keygroup:g})}return o}({text:[{ens:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",en:"tab-stop",ans:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",a:"leader-text-style"},{ens:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",en:"drop-cap",ans:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",a:"style-name"},{ens:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",en:"notes-configuration",ans:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",a:"citation-body-style-name"},{ens:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",
en:"notes-configuration",ans:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",a:"citation-style-name"},{ens:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",en:"a",ans:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",a:"style-name"},{ens:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",en:"alphabetical-index",ans:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",a:"style-name"},{ens:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",en:"linenumbering-configuration",ans:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",
a:"style-name"},{ens:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",en:"list-level-style-number",ans:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",a:"style-name"},{ens:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",en:"ruby-text",ans:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",a:"style-name"},{ens:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",en:"span",ans:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",a:"style-name"},{ens:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",en:"a",ans:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",
a:"visited-style-name"},{ens:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",en:"text-properties",ans:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",a:"text-line-through-text-style"},{ens:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",en:"alphabetical-index-source",ans:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",a:"main-entry-style-name"},{ens:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",en:"index-entry-bibliography",ans:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",a:"style-name"},
@ -251,81 +258,84 @@ en:"user-field-get",ans:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",a:"dat
a:"data-style-name"},{ens:"urn:oasis:names:tc:opendocument:xmlns:text:1.0",en:"variable-set",ans:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",a:"data-style-name"}],"page-layout":[{ens:"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",en:"notes",ans:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",a:"page-layout-name"},{ens:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",en:"handout-master",ans:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",a:"page-layout-name"},{ens:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",
en:"master-page",ans:"urn:oasis:names:tc:opendocument:xmlns:style:1.0",a:"page-layout-name"}]})};
// Input 21
odf.Style2CSS=function(){function i(a,b){var c={},d,f,e;if(!b)return c;for(d=b.firstChild;d;){d.namespaceURI===p&&d.localName==="style"?e=d.getAttributeNS(p,"family"):d.namespaceURI===m&&d.localName==="list-style"&&(e="list");if(f=e&&d.getAttributeNS&&d.getAttributeNS(p,"name"))c[e]||(c[e]={}),c[e][f]=d;d=d.nextSibling}return c}function k(a,b){if(!b||!a)return null;if(a[b])return a[b];var c,d;for(c in a)if(a.hasOwnProperty(c)&&(d=k(a[c].derivedStyles,b)))return d;return null}function e(a,b,c){var d=
b[a],f,g;if(d)if(f=d.getAttributeNS(p,"parent-style-name"),g=null,f&&(g=k(c,f),!g&&b[f]&&(e(f,b,c),g=b[f],b[f]=null)),g){if(!g.derivedStyles)g.derivedStyles={};g.derivedStyles[a]=d}else c[a]=d}function g(a,b){for(var c in a)a.hasOwnProperty(c)&&(e(c,a,b),a[c]=null)}function a(a,b){var c=u[a],d;if(c===null)return null;d="["+c+'|style-name="'+b+'"]';c==="presentation"&&(c="draw",d='[presentation|style-name="'+b+'"]');return c+"|"+n[a].join(d+","+c+"|")+d}function b(c,d,f){var e=[],g,h;e.push(a(c,d));
for(g in f.derivedStyles)if(f.derivedStyles.hasOwnProperty(g))for(h in d=b(c,g,f.derivedStyles[g]),d)d.hasOwnProperty(h)&&e.push(d[h]);return e}function h(a,b,c){if(!a)return null;for(a=a.firstChild;a;){if(a.namespaceURI===b&&a.localName===c)return b=a;a=a.nextSibling}return null}function c(a,b){var c="",d,f;for(d in b)b.hasOwnProperty(d)&&(d=b[d],(f=a.getAttributeNS(d[0],d[1]))&&(c+=d[2]+":"+f+";"));return c}function d(a,b,c,d){for(var b='text|list[text|style-name="'+b+'"]',c=c.getAttributeNS(m,
"level"),f="",c=c&&parseInt(c,10);c>1;)b+=" > text|list-item > text|list",c-=1;b+=" > list-item:before";try{a.insertRule(b+"{"+d+"}",a.cssRules.length)}catch(e){throw e;}}function f(a,e,g,i){if(e==="list")for(var k=i.firstChild,l,n;k;){if(k.namespaceURI===m)if(l=k,k.localName==="list-level-style-number"){n=l;var t=n.getAttributeNS(p,"num-format"),u=n.getAttributeNS(p,"num-suffix"),L="",L={1:"decimal",a:"lower-latin",A:"upper-latin",i:"lower-roman",I:"upper-roman"},Q="",Q=n.getAttributeNS(p,"num-prefix")||
"";Q+=L.hasOwnProperty(t)?" counter(list, "+L[t]+")":t?"'"+t+"';":" ''";u&&(Q+=" '"+u+"'");n=L="content: "+Q+";";d(a,g,l,n)}else k.localName==="list-level-style-image"?(n="content: none;",d(a,g,l,n)):k.localName==="list-level-style-bullet"&&(n="content: '"+l.getAttributeNS(m,"bullet-char")+"';",d(a,g,l,n));k=k.nextSibling}else{g=b(e,g,i).join(",");l="";if(k=h(i,p,"text-properties")){n="";n+=c(k,q);t=k.getAttributeNS(p,"text-underline-style");t==="solid"&&(n+="text-decoration: underline;");if(t=k.getAttributeNS(p,
"font-name"))(t='"'+t+'"')&&(n+="font-family: "+t+";");l+=n}if(k=h(i,p,"paragraph-properties")){n=k;k="";k+=c(n,y);n=n.getElementsByTagNameNS(p,"background-image");if(n.length>0&&(t=n.item(0).getAttributeNS(j,"href")))k+="background-image: url('odfkit:"+t+"');",n=n.item(0),k+=c(n,r);l+=k}if(k=h(i,p,"graphic-properties"))n="",n+=c(k,C),l+=n;if(k=h(i,p,"table-cell-properties"))n="",n+=c(k,s),l+=n;if(l.length!==0)try{a.insertRule(g+"{"+l+"}",a.cssRules.length)}catch(R){throw R;}}for(var X in i.derivedStyles)i.derivedStyles.hasOwnProperty(X)&&
f(a,e,X,i.derivedStyles[X])}var j="http://www.w3.org/1999/xlink",p="urn:oasis:names:tc:opendocument:xmlns:style:1.0",m="urn:oasis:names:tc:opendocument:xmlns:text:1.0",l={draw:"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",fo:"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",office:"urn:oasis:names:tc:opendocument:xmlns:office:1.0",presentation:"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",style:p,svg:"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",table:"urn:oasis:names:tc:opendocument:xmlns:table:1.0",
text:m,xlink:j},u={graphic:"draw",paragraph:"text",presentation:"presentation",ruby:"text",section:"text",table:"table","table-cell":"table","table-column":"table","table-row":"table",text:"text",list:"text"},n={graphic:"circle,connected,control,custom-shape,ellipse,frame,g,line,measure,page,page-thumbnail,path,polygon,polyline,rect,regular-polygon".split(","),paragraph:"alphabetical-index-entry-template,h,illustration-index-entry-template,index-source-style,object-index-entry-template,p,table-index-entry-template,table-of-content-entry-template,user-index-entry-template".split(","),
odf.Style2CSS=function(){function g(a,b){var c={},d,f,e;if(!b)return c;for(d=b.firstChild;d;){d.namespaceURI===i&&"style"===d.localName?e=d.getAttributeNS(i,"family"):d.namespaceURI===j&&"list-style"===d.localName&&(e="list");if(f=e&&d.getAttributeNS&&d.getAttributeNS(i,"name"))c[e]||(c[e]={}),c[e][f]=d;d=d.nextSibling}return c}function m(a,b){if(!b||!a)return null;if(a[b])return a[b];var c,d;for(c in a)if(a.hasOwnProperty(c)&&(d=m(a[c].derivedStyles,b)))return d;return null}function e(a,b,c){var d=
b[a],f,g;d&&(f=d.getAttributeNS(i,"parent-style-name"),g=null,f&&(g=m(c,f),!g&&b[f]&&(e(f,b,c),g=b[f],b[f]=null)),g?(g.derivedStyles||(g.derivedStyles={}),g.derivedStyles[a]=d):c[a]=d)}function k(a,b){for(var c in a)a.hasOwnProperty(c)&&(e(c,a,b),a[c]=null)}function a(a,b){var c=x[a],d;if(null===c)return null;d="["+c+'|style-name="'+b+'"]';"presentation"===c&&(c="draw",d='[presentation|style-name="'+b+'"]');return c+"|"+p[a].join(d+","+c+"|")+d}function c(b,d,f){var e=[],g,h;e.push(a(b,d));for(g in f.derivedStyles)if(f.derivedStyles.hasOwnProperty(g))for(h in d=
c(b,g,f.derivedStyles[g]),d)d.hasOwnProperty(h)&&e.push(d[h]);return e}function b(a,b,c){if(!a)return null;for(a=a.firstChild;a;){if(a.namespaceURI===b&&a.localName===c)return b=a;a=a.nextSibling}return null}function d(a,b){var c="",d,f;for(d in b)b.hasOwnProperty(d)&&(d=b[d],(f=a.getAttributeNS(d[0],d[1]))&&(c+=d[2]+":"+f+";"));return c}function o(a,b,c,d){b='text|list[text|style-name="'+b+'"]';for(c=(c=c.getAttributeNS(j,"level"))&&parseInt(c,10);1<c;)b+=" > text|list-item > text|list",c-=1;try{a.insertRule(b+
" > list-item:before{"+d+"}",a.cssRules.length)}catch(f){throw f;}}function f(a,e,g,k){if("list"===e)for(var n=k.firstChild,l,m;n;){if(n.namespaceURI===j)if(l=n,"list-level-style-number"===n.localName){m=l;var p=m.getAttributeNS(i,"num-format"),x=m.getAttributeNS(i,"num-suffix"),M="",M={1:"decimal",a:"lower-latin",A:"upper-latin",i:"lower-roman",I:"upper-roman"},F="",F=m.getAttributeNS(i,"num-prefix")||"",F=M.hasOwnProperty(p)?F+(" counter(list, "+M[p]+")"):p?F+("'"+p+"';"):F+" ''";x&&(F+=" '"+x+
"'");m=M="content: "+F+";";o(a,g,l,m)}else"list-level-style-image"===n.localName?(m="content: none;",o(a,g,l,m)):"list-level-style-bullet"===n.localName&&(m="content: '"+l.getAttributeNS(j,"bullet-char")+"';",o(a,g,l,m));n=n.nextSibling}else{g=c(e,g,k).join(",");n="";if(l=b(k,i,"text-properties")){m=""+d(l,t);p=l.getAttributeNS(i,"text-underline-style");"solid"===p&&(m+="text-decoration: underline;");if(p=l.getAttributeNS(i,"font-name"))(p='"'+p+'"')&&(m+="font-family: "+p+";");n+=m}if(l=b(k,i,"paragraph-properties")){m=
l;l=""+d(m,z);m=m.getElementsByTagNameNS(i,"background-image");if(0<m.length&&(p=m.item(0).getAttributeNS(h,"href")))l+="background-image: url('odfkit:"+p+"');",m=m.item(0),l+=d(m,r);n+=l}if(l=b(k,i,"graphic-properties"))l=""+d(l,s),n+=l;if(l=b(k,i,"table-cell-properties"))l=""+d(l,q),n+=l;if(0!==n.length)try{a.insertRule(g+"{"+n+"}",a.cssRules.length)}catch(R){throw R;}}for(var S in k.derivedStyles)k.derivedStyles.hasOwnProperty(S)&&f(a,e,S,k.derivedStyles[S])}var h="http://www.w3.org/1999/xlink",
i="urn:oasis:names:tc:opendocument:xmlns:style:1.0",j="urn:oasis:names:tc:opendocument:xmlns:text:1.0",n={draw:"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",fo:"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",office:"urn:oasis:names:tc:opendocument:xmlns:office:1.0",presentation:"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",style:i,svg:"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",table:"urn:oasis:names:tc:opendocument:xmlns:table:1.0",text:j,xlink:h},x=
{graphic:"draw",paragraph:"text",presentation:"presentation",ruby:"text",section:"text",table:"table","table-cell":"table","table-column":"table","table-row":"table",text:"text",list:"text"},p={graphic:"circle,connected,control,custom-shape,ellipse,frame,g,line,measure,page,page-thumbnail,path,polygon,polyline,rect,regular-polygon".split(","),paragraph:"alphabetical-index-entry-template,h,illustration-index-entry-template,index-source-style,object-index-entry-template,p,table-index-entry-template,table-of-content-entry-template,user-index-entry-template".split(","),
presentation:"caption,circle,connector,control,custom-shape,ellipse,frame,g,line,measure,page-thumbnail,path,polygon,polyline,rect,regular-polygon".split(","),ruby:["ruby","ruby-text"],section:"alphabetical-index,bibliography,illustration-index,index-title,object-index,section,table-of-content,table-index,user-index".split(","),table:["background","table"],"table-cell":"body,covered-table-cell,even-columns,even-rows,first-column,first-row,last-column,last-row,odd-columns,odd-rows,table-cell".split(","),
"table-column":["table-column"],"table-row":["table-row"],text:"a,index-entry-chapter,index-entry-link-end,index-entry-link-start,index-entry-page-number,index-entry-span,index-entry-tab-stop,index-entry-text,index-title-template,linenumbering-configuration,list-level-style-number,list-level-style-bullet,outline-level-style,span".split(","),list:["list-item"]},q=[["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","color","color"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
"background-color","background-color"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","font-weight","font-weight"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","font-style","font-style"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","font-size","font-size"]],r=[[p,"repeat","background-repeat"]],y=[["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","background-color","background-color"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
"table-column":["table-column"],"table-row":["table-row"],text:"a,index-entry-chapter,index-entry-link-end,index-entry-link-start,index-entry-page-number,index-entry-span,index-entry-tab-stop,index-entry-text,index-title-template,linenumbering-configuration,list-level-style-number,list-level-style-bullet,outline-level-style,span".split(","),list:["list-item"]},t=[["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","color","color"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
"background-color","background-color"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","font-weight","font-weight"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","font-style","font-style"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","font-size","font-size"]],r=[[i,"repeat","background-repeat"]],z=[["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","background-color","background-color"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
"text-align","text-align"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","padding-left","padding-left"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","padding-right","padding-right"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","padding-top","padding-top"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","padding-bottom","padding-bottom"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","border-left","border-left"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
"border-right","border-right"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","border-top","border-top"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","border-bottom","border-bottom"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","margin-left","margin-left"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","margin-right","margin-right"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","margin-top","margin-top"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
"margin-bottom","margin-bottom"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","border","border"]],C=[["urn:oasis:names:tc:opendocument:xmlns:drawing:1.0","fill-color","background-color"],["urn:oasis:names:tc:opendocument:xmlns:drawing:1.0","fill","background"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","min-height","min-height"],["urn:oasis:names:tc:opendocument:xmlns:drawing:1.0","stroke","border"],["urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",
"stroke-color","border-color"]],s=[["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","background-color","background-color"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","border-left","border-left"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","border-right","border-right"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","border-top","border-top"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","border-bottom","border-bottom"]];
this.namespaces=l;this.namespaceResolver=function(a){return l[a]||null};this.namespaceResolver.lookupNamespaceURI=this.namespaceResolver;this.style2css=function(a,b,c){for(var d,e,h,j,r;a.cssRules.length;)a.deleteRule(a.cssRules.length-1);d=null;if(b)d=b.ownerDocument;if(c)d=c.ownerDocument;if(d){for(e in l)if(l.hasOwnProperty(e)){j="@namespace "+e+" url("+l[e]+");";try{a.insertRule(j,a.cssRules.length)}catch(q){}}b=i(d,b);d=i(d,c);for(r in u)if(u.hasOwnProperty(r))for(h in c={},g(b[r],c),g(d[r],
c),c)c.hasOwnProperty(h)&&f(a,r,h,c[h])}}};
"margin-bottom","margin-bottom"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","border","border"]],s=[["urn:oasis:names:tc:opendocument:xmlns:drawing:1.0","fill-color","background-color"],["urn:oasis:names:tc:opendocument:xmlns:drawing:1.0","fill","background"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","min-height","min-height"],["urn:oasis:names:tc:opendocument:xmlns:drawing:1.0","stroke","border"],["urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",
"stroke-color","border-color"]],q=[["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","background-color","background-color"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","border-left","border-left"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","border-right","border-right"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","border-top","border-top"],["urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0","border-bottom","border-bottom"]];
this.namespaces=n;this.namespaceResolver=function(a){return n[a]||null};this.namespaceResolver.lookupNamespaceURI=this.namespaceResolver;this.style2css=function(a,b,c){for(var d,e,h,i,j;a.cssRules.length;)a.deleteRule(a.cssRules.length-1);d=null;b&&(d=b.ownerDocument);c&&(d=c.ownerDocument);if(d){for(e in n)if(n.hasOwnProperty(e)){i="@namespace "+e+" url("+n[e]+");";try{a.insertRule(i,a.cssRules.length)}catch(o){}}b=g(d,b);e=g(d,c);c={};for(j in x)if(x.hasOwnProperty(j))for(h in d=c[j]={},k(b[j],
d),k(e[j],d),d)d.hasOwnProperty(h)&&f(a,j,h,d[h])}}};
// Input 22
runtime.loadClass("core.Base64");runtime.loadClass("xmldom.XPath");runtime.loadClass("odf.Style2CSS");
odf.FontLoader=function(){function i(b,e,c,d,f){var g,k=0,m;for(m in b)b.hasOwnProperty(m)&&(k===c&&(g=m),k+=1);if(!g)return f();e.load(b[g].href,function(k,m){if(k)runtime.log(k);else{var n=d,n=document.styleSheets[0],q='@font-face { font-family: "'+g+'"; src: url(data:application/x-font-ttf;charset=binary;base64,'+a.convertUTF8ArrayToBase64(m)+') format("truetype"); }';try{n.insertRule(q,n.cssRules.length)}catch(r){runtime.log("Problem inserting rule in CSS: "+q)}}return i(b,e,c+1,d,f)})}function k(a,
e,c){i(a,e,0,c,function(){})}var e=new odf.Style2CSS,g=new xmldom.XPath,a=new core.Base64;odf.FontLoader=function(){this.loadFonts=function(a,h,c){var d={},f,i,p;if(a){a=g.getODFElementsWithXPath(a,"style:font-face[svg:font-face-src]",e.namespaceResolver);for(f=0;f<a.length;f+=1)i=a[f],p=i.getAttributeNS(e.namespaces.style,"name"),i=g.getODFElementsWithXPath(i,"svg:font-face-src/svg:font-face-uri",e.namespaceResolver),i.length>0&&(i=i[0].getAttributeNS(e.namespaces.xlink,"href"),d[p]={href:i})}k(d,
h,c)}};return odf.FontLoader}();
odf.FontLoader=function(){function g(c,b,d,e,f){var h,i=0,j;for(j in c)c.hasOwnProperty(j)&&(i===d&&(h=j),i+=1);if(!h)return f();b.load(c[h].href,function(i,j){if(i)runtime.log(i);else{var k=e,k=document.styleSheets[0],m='@font-face { font-family: "'+h+'"; src: url(data:application/x-font-ttf;charset=binary;base64,'+a.convertUTF8ArrayToBase64(j)+') format("truetype"); }';try{k.insertRule(m,k.cssRules.length)}catch(r){runtime.log("Problem inserting rule in CSS: "+m)}}return g(c,b,d+1,e,f)})}function m(a,
b,d){g(a,b,0,d,function(){})}var e=new odf.Style2CSS,k=new xmldom.XPath,a=new core.Base64;odf.FontLoader=function(){this.loadFonts=function(a,b,d){var g={},f,h,i;if(a){a=k.getODFElementsWithXPath(a,"style:font-face[svg:font-face-src]",e.namespaceResolver);for(f=0;f<a.length;f+=1)h=a[f],i=h.getAttributeNS(e.namespaces.style,"name"),h=k.getODFElementsWithXPath(h,"svg:font-face-src/svg:font-face-uri",e.namespaceResolver),0<h.length&&(h=h[0].getAttributeNS(e.namespaces.xlink,"href"),g[i]={href:h})}m(g,
b,d)}};return odf.FontLoader}();
// Input 23
runtime.loadClass("core.Base64");runtime.loadClass("core.Zip");runtime.loadClass("xmldom.LSSerializer");runtime.loadClass("odf.StyleInfo");runtime.loadClass("odf.Style2CSS");runtime.loadClass("odf.FontLoader");
odf.OdfContainer=function(){function i(a,b,c){for(a=a?a.firstChild:null;a;){if(a.localName===c&&a.namespaceURI===b)return a;a=a.nextSibling}return null}function k(a){var b,c=p.length;for(b=0;b<c;b+=1)if(a.namespaceURI===f&&a.localName===p[b])return b;return-1}function e(a,b){var d=a.automaticStyles,f;b&&(f=new c.UsedKeysList(b));this.acceptNode=function(a){if(a.namespaceURI==="http://www.w3.org/1999/xhtml")return 3;else if(f&&a.parentNode===d&&a.nodeType===1)return f.uses(a)?1:2;return 1}}function g(a,
b){if(b){var c=k(b),d,f=a.firstChild;if(c!==-1){for(;f;){d=k(f);if(d!==-1&&d>c)break;f=f.nextSibling}a.insertBefore(b,f)}}}function a(a){this.OdfContainer=a}function b(a,b,c){var d=this,f;this.size=0;this.type=null;this.name=a;this.container=b;this.onchange=this.onreadystatechange=this.document=this.url=null;this.EMPTY=0;this.LOADING=1;this.DONE=2;this.state=this.EMPTY;this.load=function(){c.load(a,function(b,c){f=c;d.url=null;if(f){var e=0,g=u[a];g||(g=f[1]===80&&f[2]===78&&f[3]===71?"image/png":
f[0]===255&&f[1]===216&&f[2]===255?"image/jpeg":f[0]===71&&f[1]===73&&f[2]===70?"image/gif":"");for(d.url="data:"+g+";base64,";e<f.length;)d.url+=m.convertUTF8ArrayToBase64(f.slice(e,Math.min(e+45E3,f.length))),e+=45E3}if(d.onchange)d.onchange(d);if(d.onstatereadychange)d.onstatereadychange(d)})};this.abort=function(){}}function h(){this.length=0;this.item=function(){}}var c=new odf.StyleInfo,d=new odf.Style2CSS,f="urn:oasis:names:tc:opendocument:xmlns:office:1.0",j="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0",
p="meta,settings,scripts,font-face-decls,styles,automatic-styles,master-styles,body".split(","),m=new core.Base64,l=new odf.FontLoader,u={};a.prototype=new function(){};a.prototype.constructor=a;a.namespaceURI=f;a.localName="document";b.prototype.load=function(){};b.prototype.getUrl=function(){return this.data?"data:;base64,"+m.toBase64(this.data):null};odf.OdfContainer=function q(c,k){function m(a){for(var b=a.firstChild,c;b;)c=b.nextSibling,b.nodeType===1?m(b):b.nodeType===7&&a.removeChild(b),b=
c}function s(a){var b=A.rootElement.ownerDocument,c;if(a){m(a.documentElement);try{c=b.importNode(a.documentElement,true)}catch(d){}}return c}function p(a){A.state=a;if(A.onchange)A.onchange(A);if(A.onstatereadychange)A.onstatereadychange(A)}function E(a){var a=s(a),b=A.rootElement;!a||a.localName!=="document-styles"||a.namespaceURI!==f?p(q.INVALID):(b.fontFaceDecls=i(a,f,"font-face-decls"),g(b,b.fontFaceDecls),b.styles=i(a,f,"styles"),g(b,b.styles),b.automaticStyles=i(a,f,"automatic-styles"),g(b,
b.automaticStyles),b.masterStyles=i(a,f,"master-styles"),g(b,b.masterStyles),l.loadFonts(b.fontFaceDecls,J,null))}function B(a){var a=s(a),b,c,d;if(!a||a.localName!=="document-content"||a.namespaceURI!==f)p(q.INVALID);else{b=A.rootElement;c=i(a,f,"font-face-decls");if(b.fontFaceDecls&&c)for(d=c.firstChild;d;)b.fontFaceDecls.appendChild(d),d=c.firstChild;else if(c)b.fontFaceDecls=c,g(b,c);c=i(a,f,"automatic-styles");if(b.automaticStyles&&c)for(d=c.firstChild;d;)b.automaticStyles.appendChild(d),d=c.firstChild;
else if(c)b.automaticStyles=c,g(b,c);b.body=i(a,f,"body");g(b,b.body)}}function z(a){var a=s(a),b;if(a&&!(a.localName!=="document-meta"||a.namespaceURI!==f))b=A.rootElement,b.meta=i(a,f,"meta"),g(b,b.meta)}function G(a){var a=s(a),b;if(a&&!(a.localName!=="document-settings"||a.namespaceURI!==f))b=A.rootElement,b.settings=i(a,f,"settings"),g(b,b.settings)}function o(a,b){J.load(a,function(a,c){if(a)b(a,null);else{var d=runtime.byteArrayToString(c,"utf8"),d=(new DOMParser).parseFromString(d,"text/xml");
b(null,d)}})}function x(){o("styles.xml",function(a,b){E(b);A.state!==q.INVALID&&o("content.xml",function(a,b){B(b);A.state!==q.INVALID&&o("meta.xml",function(a,b){z(b);A.state!==q.INVALID&&o("settings.xml",function(a,b){b&&G(b);o("META-INF/manifest.xml",function(a,b){if(b){var c=s(b),d;if(c&&!(c.localName!=="manifest"||c.namespaceURI!==j)){d=A.rootElement;d.manifest=c;for(c=d.manifest.firstChild;c;)c.nodeType===1&&c.localName==="file-entry"&&c.namespaceURI===j&&(u[c.getAttributeNS(j,"full-path")]=
c.getAttributeNS(j,"media-type")),c=c.nextSibling}}A.state!==q.INVALID&&p(q.DONE)})})})})})}function t(a,b){var c="",d;for(d in b)b.hasOwnProperty(d)&&(c+=" xmlns:"+d+'="'+b[d]+'"');return'<?xml version="1.0" encoding="UTF-8"?><office:'+a+" "+c+' office:version="1.2">'}function w(){var a=d.namespaces,b=new xmldom.LSSerializer,c=t("document-meta",a);b.filter=new e(A.rootElement);c+=b.writeToString(A.rootElement.meta,a);c+="</office:document-meta>";return c}function L(){var a=d.namespaces,b=new xmldom.LSSerializer,
c=t("document-settings",a);b.filter=new e(A.rootElement);c+=b.writeToString(A.rootElement.settings,a);c+="</office:document-settings>";return c}function Q(){var a=d.namespaces,b=new xmldom.LSSerializer,c=t("document-styles",a);b.filter=new e(A.rootElement,A.rootElement.masterStyles);c+=b.writeToString(A.rootElement.fontFaceDecls,a);c+=b.writeToString(A.rootElement.styles,a);c+=b.writeToString(A.rootElement.automaticStyles,a);c+=b.writeToString(A.rootElement.masterStyles,a);c+="</office:document-styles>";
return c}function R(){var a=d.namespaces,b=new xmldom.LSSerializer,c=t("document-content",a);b.filter=new e(A.rootElement,A.rootElement.body);c+=b.writeToString(A.rootElement.automaticStyles,a);c+=b.writeToString(A.rootElement.body,a);c+="</office:document-content>";return c}function X(a,b){runtime.loadXML(a,function(a,c){if(a)b(a);else{var d=s(c);!d||d.localName!=="document"||d.namespaceURI!==f?p(q.INVALID):(A.rootElement=d,d.fontFaceDecls=i(d,f,"font-face-decls"),d.styles=i(d,f,"styles"),d.automaticStyles=
i(d,f,"automatic-styles"),d.masterStyles=i(d,f,"master-styles"),d.body=i(d,f,"body"),d.meta=i(d,f,"meta"),p(q.DONE))}})}var A=this,J=null;this.onstatereadychange=k;this.parts=this.rootElement=this.state=this.onchange=null;this.getPart=function(a){return new b(a,A,J)};this.save=function(a){var b;b=runtime.byteArrayFromString(L(),"utf8");J.save("settings.xml",b,true,new Date);b=runtime.byteArrayFromString(w(),"utf8");J.save("meta.xml",b,true,new Date);b=runtime.byteArrayFromString(Q(),"utf8");J.save("styles.xml",
b,true,new Date);b=runtime.byteArrayFromString(R(),"utf8");J.save("content.xml",b,true,new Date);J.write(function(b){a(b)})};this.state=q.LOADING;this.rootElement=function(a){var b=document.createElementNS(a.namespaceURI,a.localName),c,a=new a;for(c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b}(a);this.parts=new h(this);J=new core.Zip(c,function(a,b){J=b;a?X(c,function(b){if(a)J.error=a+"\n"+b,p(q.INVALID)}):x()})};odf.OdfContainer.EMPTY=0;odf.OdfContainer.LOADING=1;odf.OdfContainer.DONE=2;odf.OdfContainer.INVALID=
3;odf.OdfContainer.SAVING=4;odf.OdfContainer.MODIFIED=5;odf.OdfContainer.getContainer=function(a){return new odf.OdfContainer(a,null)};return odf.OdfContainer}();
odf.OdfContainer=function(){function g(a,b,c){for(a=a?a.firstChild:null;a;){if(a.localName===c&&a.namespaceURI===b)return a;a=a.nextSibling}return null}function m(a){var b,c=i.length;for(b=0;b<c;b+=1)if(a.namespaceURI===f&&a.localName===i[b])return b;return-1}function e(a,b){var c=a.automaticStyles,f;b&&(f=new d.UsedKeysList(b));this.acceptNode=function(a){return"http://www.w3.org/1999/xhtml"===a.namespaceURI?3:f&&a.parentNode===c&&1===a.nodeType?f.uses(a)?1:2:1}}function k(a,b){if(b){var c=m(b),
d,f=a.firstChild;if(-1!==c){for(;f;){d=m(f);if(-1!==d&&d>c)break;f=f.nextSibling}a.insertBefore(b,f)}}}function a(a){this.OdfContainer=a}function c(a,b,c){var d=this;this.size=0;this.type=null;this.name=a;this.container=b;this.onchange=this.onreadystatechange=this.document=this.url=null;this.EMPTY=0;this.LOADING=1;this.DONE=2;this.state=this.EMPTY;this.load=function(){c.loadAsDataURL(a,x[a],function(a,b){d.url=b;if(d.onchange)d.onchange(d);if(d.onstatereadychange)d.onstatereadychange(d)})};this.abort=
function(){}}function b(){this.length=0;this.item=function(){}}var d=new odf.StyleInfo,o=new odf.Style2CSS,f="urn:oasis:names:tc:opendocument:xmlns:office:1.0",h="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0",i="meta,settings,scripts,font-face-decls,styles,automatic-styles,master-styles,body".split(","),j=new core.Base64,n=new odf.FontLoader,x={};a.prototype=new function(){};a.prototype.constructor=a;a.namespaceURI=f;a.localName="document";c.prototype.load=function(){};c.prototype.getUrl=function(){return this.data?
"data:;base64,"+j.toBase64(this.data):null};odf.OdfContainer=function t(d,i){function j(a){for(var b=a.firstChild,c;b;)c=b.nextSibling,1===b.nodeType?j(b):7===b.nodeType&&a.removeChild(b),b=c}function q(a){var b=A.rootElement.ownerDocument,c;if(a){j(a.documentElement);try{c=b.importNode(a.documentElement,!0)}catch(d){}}return c}function m(a){A.state=a;if(A.onchange)A.onchange(A);if(A.onstatereadychange)A.onstatereadychange(A)}function E(a){var a=q(a),b=A.rootElement;!a||"document-styles"!==a.localName||
a.namespaceURI!==f?m(t.INVALID):(b.fontFaceDecls=g(a,f,"font-face-decls"),k(b,b.fontFaceDecls),b.styles=g(a,f,"styles"),k(b,b.styles),b.automaticStyles=g(a,f,"automatic-styles"),k(b,b.automaticStyles),b.masterStyles=g(a,f,"master-styles"),k(b,b.masterStyles),n.loadFonts(b.fontFaceDecls,K,null))}function C(a){var a=q(a),b,c,d;if(!a||"document-content"!==a.localName||a.namespaceURI!==f)m(t.INVALID);else{b=A.rootElement;c=g(a,f,"font-face-decls");if(b.fontFaceDecls&&c)for(d=c.firstChild;d;)b.fontFaceDecls.appendChild(d),
d=c.firstChild;else c&&(b.fontFaceDecls=c,k(b,c));c=g(a,f,"automatic-styles");if(b.automaticStyles&&c)for(d=c.firstChild;d;)b.automaticStyles.appendChild(d),d=c.firstChild;else c&&(b.automaticStyles=c,k(b,c));b.body=g(a,f,"body");k(b,b.body)}}function w(a){var a=q(a),b;if(a&&!("document-meta"!==a.localName||a.namespaceURI!==f))b=A.rootElement,b.meta=g(a,f,"meta"),k(b,b.meta)}function G(a){var a=q(a),b;if(a&&!("document-settings"!==a.localName||a.namespaceURI!==f))b=A.rootElement,b.settings=g(a,f,
"settings"),k(b,b.settings)}function l(a,b){K.loadAsDOM(a,b)}function v(){l("styles.xml",function(a,b){E(b);A.state!==t.INVALID&&l("content.xml",function(a,b){C(b);A.state!==t.INVALID&&l("meta.xml",function(a,b){w(b);A.state!==t.INVALID&&l("settings.xml",function(a,b){b&&G(b);l("META-INF/manifest.xml",function(a,b){if(b){var c=q(b),d;if(c&&!("manifest"!==c.localName||c.namespaceURI!==h)){d=A.rootElement;d.manifest=c;for(c=d.manifest.firstChild;c;)1===c.nodeType&&"file-entry"===c.localName&&c.namespaceURI===
h&&(x[c.getAttributeNS(h,"full-path")]=c.getAttributeNS(h,"media-type")),c=c.nextSibling}}A.state!==t.INVALID&&m(t.DONE)})})})})})}function y(a,b){var c="",d;for(d in b)b.hasOwnProperty(d)&&(c+=" xmlns:"+d+'="'+b[d]+'"');return'<?xml version="1.0" encoding="UTF-8"?><office:'+a+" "+c+' office:version="1.2">'}function B(){var a=o.namespaces,b=new xmldom.LSSerializer,c=y("document-meta",a);b.filter=new e(A.rootElement);c+=b.writeToString(A.rootElement.meta,a);return c+"</office:document-meta>"}function M(){var a=
o.namespaces,b=new xmldom.LSSerializer,c=y("document-settings",a);b.filter=new e(A.rootElement);c+=b.writeToString(A.rootElement.settings,a);return c+"</office:document-settings>"}function F(){var a=o.namespaces,b=new xmldom.LSSerializer,c=y("document-styles",a);b.filter=new e(A.rootElement,A.rootElement.masterStyles);c+=b.writeToString(A.rootElement.fontFaceDecls,a);c+=b.writeToString(A.rootElement.styles,a);c+=b.writeToString(A.rootElement.automaticStyles,a);c+=b.writeToString(A.rootElement.masterStyles,
a);return c+"</office:document-styles>"}function R(){var a=o.namespaces,b=new xmldom.LSSerializer,c=y("document-content",a);b.filter=new e(A.rootElement,A.rootElement.body);c+=b.writeToString(A.rootElement.automaticStyles,a);c+=b.writeToString(A.rootElement.body,a);return c+"</office:document-content>"}function S(a,b){runtime.loadXML(a,function(a,c){if(a)b(a);else{var d=q(c);!d||"document"!==d.localName||d.namespaceURI!==f?m(t.INVALID):(A.rootElement=d,d.fontFaceDecls=g(d,f,"font-face-decls"),d.styles=
g(d,f,"styles"),d.automaticStyles=g(d,f,"automatic-styles"),d.masterStyles=g(d,f,"master-styles"),d.body=g(d,f,"body"),d.meta=g(d,f,"meta"),m(t.DONE))}})}var A=this,K=null;this.onstatereadychange=i;this.parts=this.rootElement=this.state=this.onchange=null;this.getPart=function(a){return new c(a,A,K)};this.save=function(a){var b;b=runtime.byteArrayFromString(M(),"utf8");K.save("settings.xml",b,!0,new Date);b=runtime.byteArrayFromString(B(),"utf8");K.save("meta.xml",b,!0,new Date);b=runtime.byteArrayFromString(F(),
"utf8");K.save("styles.xml",b,!0,new Date);b=runtime.byteArrayFromString(R(),"utf8");K.save("content.xml",b,!0,new Date);K.write(function(b){a(b)})};this.state=t.LOADING;this.rootElement=function(a){var b=document.createElementNS(a.namespaceURI,a.localName),c,a=new a;for(c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b}(a);this.parts=new b(this);K=new core.Zip(d,function(a,b){K=b;a?S(d,function(b){a&&(K.error=a+"\n"+b,m(t.INVALID))}):v()})};odf.OdfContainer.EMPTY=0;odf.OdfContainer.LOADING=1;odf.OdfContainer.DONE=
2;odf.OdfContainer.INVALID=3;odf.OdfContainer.SAVING=4;odf.OdfContainer.MODIFIED=5;odf.OdfContainer.getContainer=function(a){return new odf.OdfContainer(a,null)};return odf.OdfContainer}();
// Input 24
odf.Formatting=function(){function i(e){function g(a,e){for(var c=a&&a.firstChild;c&&e;)c=c.nextSibling,e-=1;return c}var a=g(e.startContainer,e.startOffset);g(e.endContainer,e.endOffset);this.next=function(){return a===null?a:null}}var k=new odf.StyleInfo;this.setOdfContainer=function(){};this.isCompletelyBold=function(){return false};this.getAlignment=function(e){this.getParagraphStyles(e)};this.getParagraphStyles=function(e){var g,a,b,h=[];for(g=0;g<e.length;g+=0){a=void 0;b=[];for(a=(new i(e[g])).next();a;)k.canElementHaveStyle("paragraph",
a)&&b.push(a);for(a=0;a<b.length;a+=1)h.indexOf(b[a])===-1&&h.push(b[a])}return h};this.getTextStyles=function(){return[]}};
odf.Formatting=function(){function g(e){function g(a,b){for(var d=a&&a.firstChild;d&&b;)d=d.nextSibling,b-=1;return d}var a=g(e.startContainer,e.startOffset);g(e.endContainer,e.endOffset);this.next=function(){return null===a?a:null}}var m=new odf.StyleInfo;this.setOdfContainer=function(){};this.isCompletelyBold=function(){return!1};this.getAlignment=function(e){this.getParagraphStyles(e)};this.getParagraphStyles=function(e){var k,a,c,b=[];for(k=0;k<e.length;k+=0){a=void 0;c=[];for(a=(new g(e[k])).next();a;)m.canElementHaveStyle("paragraph",
a)&&c.push(a);for(a=0;a<c.length;a+=1)-1===b.indexOf(c[a])&&b.push(c[a])}return b};this.getTextStyles=function(){return[]}};
// Input 25
runtime.loadClass("odf.OdfContainer");runtime.loadClass("odf.Formatting");runtime.loadClass("xmldom.XPath");
odf.OdfCanvas=function(){function i(a,b,c){a.addEventListener?a.addEventListener(b,c,false):a.attachEvent?a.attachEvent("on"+b,c):a["on"+b]=c}function k(a){function b(a,c){for(;c;){if(c===a)return true;c=c.parentNode}return false}function c(){var e=[],g=runtime.getWindow().getSelection(),h,i;for(h=0;h<g.rangeCount;h+=1)i=g.getRangeAt(h),i!==null&&b(a,i.startContainer)&&b(a,i.endContainer)&&e.push(i);if(e.length===d.length){for(g=0;g<e.length;g+=1)if(h=e[g],i=d[g],h=h===i?false:h===null||i===null?
true:h.startContainer!==i.startContainer||h.startOffset!==i.startOffset||h.endContainer!==i.endContainer||h.endOffset!==i.endOffset,h)break;if(g===e.length)return}d=e;var g=Array(e.length),j,k=a.ownerDocument;for(h=0;h<e.length;h+=1)i=e[h],j=k.createRange(),j.setStart(i.startContainer,i.startOffset),j.setEnd(i.endContainer,i.endOffset),g[h]=j;d=g;g=f.length;for(e=0;e<g;e+=1)f[e](a,d)}var d=[],f=[];this.addListener=function(a,b){var c,d=f.length;for(c=0;c<d;c+=1)if(f[c]===b)return;f.push(b)};i(a,"mouseup",
c);i(a,"keyup",c);i(a,"keydown",c)}function e(a){for(a=a.firstChild;a;){if(a.namespaceURI===f&&a.localName==="binary-data")return"data:image/png;base64,"+a.textContent;a=a.nextSibling}return""}function g(a,b,c,d){function f(b){b='draw|image[styleid="'+a+'"] {'+("background-image: url("+b+");")+"}";d.insertRule(b,d.cssRules.length)}c.setAttribute("styleid",a);var g=c.getAttributeNS(m,"href"),h;if(g)try{b.getPartUrl?(g=b.getPartUrl(g),f(g)):(h=b.getPart(g),h.onchange=function(a){f(a.url)},h.load())}catch(i){runtime.log("slight problem: "+
i)}else g=e(c),f(g)}function a(a){var b=a.getElementsByTagName("style"),c=a.getElementsByTagName("head")[0],d="",f,b=b&&b.length>0?b[0].cloneNode(false):a.createElement("style");for(f in h)h.hasOwnProperty(f)&&f&&(d+="@namespace "+f+" url("+h[f]+");\n");b.appendChild(a.createTextNode(d));c.appendChild(b);return b}var b=new odf.Style2CSS,h=b.namespaces,c=h.draw,d=h.fo,f=h.office,j=h.svg,p=h.text,m=h.xlink,l=runtime.getWindow(),u=new xmldom.XPath,n={},q;odf.OdfCanvas=function(f){function e(a){function h(){for(var e=
f;e.firstChild;)e.removeChild(e.firstChild);f.style.display="inline-block";f.style.background="white";e=a.rootElement;f.ownerDocument.importNode(e,true);E.setOdfContainer(a);var i=G;(new odf.Style2CSS).style2css(i.sheet,e.styles,e.automaticStyles);var i=o.sheet,k=a,q=e.body,l,m,s;m=[];for(l=q.firstChild;l&&l!==q;)if(l.namespaceURI===c&&(m[m.length]=l),l.firstChild)l=l.firstChild;else{for(;l&&l!==q&&!l.nextSibling;)l=l.parentNode;if(l&&l.nextSibling)l=l.nextSibling}for(s=0;s<m.length;s+=1){l=m[s];
var v="frame"+String(s),y=i;l.setAttribute("styleid",v);var w=void 0,C=l.getAttributeNS(p,"anchor-type"),x=l.getAttributeNS(j,"x"),z=l.getAttributeNS(j,"y"),B=l.getAttributeNS(j,"width"),O=l.getAttributeNS(j,"height"),T=l.getAttributeNS(d,"min-height"),N=l.getAttributeNS(d,"min-width");if(C==="as-char")w="display: inline-block;";else if(C||x||z)w="position: absolute;";else if(B||O||T||N)w="display: block;";x&&(w+="left: "+x+";");z&&(w+="top: "+z+";");B&&(w+="width: "+B+";");O&&(w+="height: "+O+";");
T&&(w+="min-height: "+T+";");N&&(w+="min-width: "+N+";");w&&(w="draw|"+l.localName+'[styleid="'+v+'"] {'+w+"}",y.insertRule(w,y.cssRules.length))}m=q.getElementsByTagNameNS(c,"image");for(s=0;s<m.length;s+=1)l=m.item(s),g("image"+String(s),k,l,i);s=u.getODFElementsWithXPath(q,".//*[*[@text:anchor-type='paragraph']]",b.namespaceResolver);for(q=0;q<s.length;q+=1)k=s[q],k.setAttributeNS&&k.setAttributeNS("urn:webodf","containsparagraphanchor",true);i.insertRule("office|presentation draw|page:nth-child(1n) { display:block; }",
i.cssRules.length);i.insertRule("draw|page { background-color:#fff; }",i.cssRules.length);for(i=f;i.firstChild;)i.removeChild(i.firstChild);f.appendChild(e);if(n.hasOwnProperty("statereadychange")){e=n.statereadychange;for(i=0;i<e.length;i+=1)e[i](void 0)}}if(v===a)v.state===odf.OdfContainer.DONE?h():v.onchange=h}function h(){if(q){for(var a=q.ownerDocument.createDocumentFragment();q.firstChild;)a.insertBefore(q.firstChild,null);q.parentNode.replaceChild(a,q)}}var m=f.ownerDocument,v,E=new odf.Formatting,
B=new k(f),z=a(m),G=a(m),o=a(m),x=false;this.odfContainer=function(){return v};this.slidevisibilitycss=function(){return z};this.load=this.load=function(a){f.innerHTML="loading "+a;v=new odf.OdfContainer(a,function(a){v=a;e(a)});v.onstatereadychange=e};this.save=function(a){h();v.save(a)};this.setEditable=function(a){(x=a)||h()};this.addListener=function(a,b){if(a==="selectionchange")B.addListener(a,b);else{var c=n[a];c===void 0&&(c=n[a]=[]);c.push(b)}};this.getFormatting=function(){return E};i(f,
"click",function(a){for(var a=a||l.event,b=a.target,c=l.getSelection(),d=c.getRangeAt(0),f=d&&d.startContainer,e=d&&d.startOffset,g=d&&d.endContainer,i=d&&d.endOffset;b&&!((b.localName==="p"||b.localName==="h")&&b.namespaceURI===p);)b=b.parentNode;if(x&&b&&b.parentNode!==q)q?q.parentNode&&h():(q=b.ownerDocument.createElement("p"),q.style||(q=b.ownerDocument.createElementNS("http://www.w3.org/1999/xhtml","p")),q.style.margin="0px",q.style.padding="0px",q.style.border="0px",q.setAttribute("contenteditable",
true)),b.parentNode.replaceChild(q,b),q.appendChild(b),q.focus(),d&&(c.removeAllRanges(),d=b.ownerDocument.createRange(),d.setStart(f,e),d.setEnd(g,i),c.addRange(d)),a.preventDefault?(a.preventDefault(),a.stopPropagation()):(a.returnValue=false,a.cancelBubble=true)})};return odf.OdfCanvas}();
odf.OdfCanvas=function(){function g(a,b,c){a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent?a.attachEvent("on"+b,c):a["on"+b]=c}function m(a){function b(a,c){for(;c;){if(c===a)return!0;c=c.parentNode}return!1}function c(){var e=[],g=runtime.getWindow().getSelection(),h,i;for(h=0;h<g.rangeCount;h+=1)i=g.getRangeAt(h),null!==i&&b(a,i.startContainer)&&b(a,i.endContainer)&&e.push(i);if(e.length===d.length){for(g=0;g<e.length&&!(h=e[g],i=d[g],h=h===i?!1:null===h||null===i?!0:h.startContainer!==
i.startContainer||h.startOffset!==i.startOffset||h.endContainer!==i.endContainer||h.endOffset!==i.endOffset,h);g+=1);if(g===e.length)return}d=e;var g=[e.length],j,k=a.ownerDocument;for(h=0;h<e.length;h+=1)i=e[h],j=k.createRange(),j.setStart(i.startContainer,i.startOffset),j.setEnd(i.endContainer,i.endOffset),g[h]=j;d=g;g=f.length;for(e=0;e<g;e+=1)f[e](a,d)}var d=[],f=[];this.addListener=function(a,b){var c,d=f.length;for(c=0;c<d;c+=1)if(f[c]===b)return;f.push(b)};g(a,"mouseup",c);g(a,"keyup",c);g(a,
"keydown",c)}function e(a){for(a=a.firstChild;a;){if(a.namespaceURI===h&&"binary-data"===a.localName)return"data:image/png;base64,"+a.textContent;a=a.nextSibling}return""}function k(a,b,c,d){function f(b){b='draw|image[styleid="'+a+'"] {'+("background-image: url("+b+");")+"}";d.insertRule(b,d.cssRules.length)}c.setAttribute("styleid",a);var g=c.getAttributeNS(n,"href"),h;if(g)try{b.getPartUrl?(g=b.getPartUrl(g),f(g)):(h=b.getPart(g),h.onchange=function(a){f(a.url)},h.load())}catch(i){runtime.log("slight problem: "+
i)}else g=e(c),f(g)}function a(a,b,c){function d(a,b,c,f){z.addToQueue(function(){k(a,b,c,f)})}var f,e;f=b.getElementsByTagNameNS(o,"image");for(b=0;b<f.length;b+=1)e=f.item(b),d("image"+b,a,e,c)}function c(a){var b=a.getElementsByTagName("style"),c=a.getElementsByTagName("head")[0],f="",e,b=b&&0<b.length?b[0].cloneNode(!1):a.createElement("style");for(e in d)d.hasOwnProperty(e)&&e&&(f+="@namespace "+e+" url("+d[e]+");\n");b.appendChild(a.createTextNode(f));c.appendChild(b);return b}var b=new odf.Style2CSS,
d=b.namespaces,o=d.draw,f=d.fo,h=d.office,i=d.svg,j=d.text,n=d.xlink,x=runtime.getWindow(),p=new xmldom.XPath,t={},r,z=new function(){function a(d){c=!0;runtime.setTimeout(function(){try{d()}catch(f){runtime.log(f)}c=!1;0<b.length&&a(b.pop())},10)}var b=[],c=!1;this.clearQueue=function(){b.length=0};this.addToQueue=function(d){if(0===b.length&&!c)return a(d);b.push(d)}};odf.OdfCanvas=function(d){function e(){var a=d.firstChild.firstChild;a&&(d.style.WebkitTransform="scale("+F+")",d.style.WebkitTransformOrigin=
"left top",d.style.width=Math.round(F*a.offsetWidth)+"px",d.style.height=Math.round(F*a.offsetHeight)+"px")}function h(c){function g(){for(var h=d;h.firstChild;)h.removeChild(h.firstChild);d.style.display="inline-block";h=c.rootElement;d.ownerDocument.importNode(h,!0);G.setOdfContainer(c);var k=y;(new odf.Style2CSS).style2css(k.sheet,h.styles,h.automaticStyles);var k=c,m=B.sheet,l;l=h.body;var r,z,u;z=[];for(r=l.firstChild;r&&r!==l;)if(r.namespaceURI===o&&(z[z.length]=r),r.firstChild)r=r.firstChild;
else{for(;r&&r!==l&&!r.nextSibling;)r=r.parentNode;r&&r.nextSibling&&(r=r.nextSibling)}for(u=0;u<z.length;u+=1){r=z[u];var w="frame"+u,x=m;r.setAttribute("styleid",w);var v=void 0,S=r.getAttributeNS(j,"anchor-type"),E=r.getAttributeNS(i,"x"),F=r.getAttributeNS(i,"y"),M=r.getAttributeNS(i,"width"),Q=r.getAttributeNS(i,"height"),$=r.getAttributeNS(f,"min-height"),W=r.getAttributeNS(f,"min-width");if("as-char"===S)v="display: inline-block;";else if(S||E||F)v="position: absolute;";else if(M||Q||$||W)v=
"display: block;";E&&(v+="left: "+E+";");F&&(v+="top: "+F+";");M&&(v+="width: "+M+";");Q&&(v+="height: "+Q+";");$&&(v+="min-height: "+$+";");W&&(v+="min-width: "+W+";");v&&(v="draw|"+r.localName+'[styleid="'+w+'"] {'+v+"}",x.insertRule(v,x.cssRules.length))}u=p.getODFElementsWithXPath(l,".//*[*[@text:anchor-type='paragraph']]",b.namespaceResolver);for(z=0;z<u.length;z+=1)l=u[z],l.setAttributeNS&&l.setAttributeNS("urn:webodf","containsparagraphanchor",!0);m.insertRule("office|presentation draw|page:nth-child(1n) {display:block;}",
m.cssRules.length);m.insertRule("draw|page { background-color:#fff; }",m.cssRules.length);for(l=d;l.firstChild;)l.removeChild(l.firstChild);l=n.createElement("div");l.style.display="inline-block";l.style.background="white";l.appendChild(h);d.appendChild(l);a(k,h.body,m);e();if(t.hasOwnProperty("statereadychange")){h=t.statereadychange;for(k=0;k<h.length;k+=1)h[k](void 0)}}w===c&&(w.state===odf.OdfContainer.DONE?g():w.onchange=g)}function k(){if(r){for(var a=r.ownerDocument.createDocumentFragment();r.firstChild;)a.insertBefore(r.firstChild,
null);r.parentNode.replaceChild(a,r)}}var n=d.ownerDocument,w,G=new odf.Formatting,l=new m(d),v=c(n),y=c(n),B=c(n),M=!1,F=1;this.odfContainer=function(){return w};this.slidevisibilitycss=function(){return v};this.load=this.load=function(a){z.clearQueue();d.innerHTML="loading "+a;w=new odf.OdfContainer(a,function(a){w=a;h(a)});w.onstatereadychange=h};this.save=function(a){k();w.save(a)};this.setEditable=function(a){(M=a)||k()};this.addListener=function(a,b){if("selectionchange"===a)l.addListener(a,
b);else{var c=t[a];void 0===c&&(c=t[a]=[]);b&&-1===c.indexOf(b)&&c.push(b)}};this.getFormatting=function(){return G};this.setZoomLevel=function(a){F=a;e()};this.getZoomLevel=function(){return F};this.fitToContainingElement=function(a,b){var c=d.offsetHeight/F;F=a/(d.offsetWidth/F);b/c<F&&(F=b/c);e()};this.fitToWidth=function(a){F=a/(d.offsetWidth/F);e()};this.fitToHeight=function(a){F=a/(d.offsetHeight/F);e()};g(d,"click",function(a){for(var a=a||x.event,b=a.target,c=x.getSelection(),d=0<c.rangeCount?
c.getRangeAt(0):null,f=d&&d.startContainer,e=d&&d.startOffset,g=d&&d.endContainer,h=d&&d.endOffset;b&&!(("p"===b.localName||"h"===b.localName)&&b.namespaceURI===j);)b=b.parentNode;M&&b&&b.parentNode!==r&&(r?r.parentNode&&k():(r=b.ownerDocument.createElement("p"),r.style||(r=b.ownerDocument.createElementNS("http://www.w3.org/1999/xhtml","p")),r.style.margin="0px",r.style.padding="0px",r.style.border="0px",r.setAttribute("contenteditable",!0)),b.parentNode.replaceChild(r,b),r.appendChild(b),r.focus(),
d&&(c.removeAllRanges(),d=b.ownerDocument.createRange(),d.setStart(f,e),d.setEnd(g,h),c.addRange(d)),a.preventDefault?(a.preventDefault(),a.stopPropagation()):(a.returnValue=!1,a.cancelBubble=!0))})};return odf.OdfCanvas}();
// Input 26
runtime.loadClass("xmldom.XPath");runtime.loadClass("odf.Style2CSS");
gui.PresenterUI=function(){var i=new odf.Style2CSS,k=new xmldom.XPath,e=i.namespaceResolver;return function(g){var a=this;a.setInitialSlideMode=function(){a.startSlideMode("single")};a.keyDownHandler=function(b){if(!b.target.isContentEditable&&b.target.nodeName!=="input")switch(b.keyCode){case 84:a.toggleToolbar();break;case 37:case 8:a.prevSlide();break;case 39:case 32:a.nextSlide();break;case 36:a.firstSlide();break;case 35:a.lastSlide()}};a.root=function(){return a.odf_canvas.odfContainer().rootElement};
a.firstSlide=function(){a.slideChange(function(){return 0})};a.lastSlide=function(){a.slideChange(function(a,e){return e-1})};a.nextSlide=function(){a.slideChange(function(a,e){return a+1<e?a+1:-1})};a.prevSlide=function(){a.slideChange(function(a){return a<1?-1:a-1})};a.slideChange=function(b){var e=a.getPages(a.odf_canvas.odfContainer().rootElement),c=-1,d=0;e.forEach(function(a){a=a[1];a.hasAttribute("slide_current")&&(c=d,a.removeAttribute("slide_current"));d+=1});b=b(c,e.length);b===-1&&(b=c);
e[b][1].setAttribute("slide_current","1");document.getElementById("pagelist").selectedIndex=b;a.slide_mode==="cont"&&window.scrollBy(0,e[b][1].getBoundingClientRect().top-30)};a.selectSlide=function(b){a.slideChange(function(a,c){return b>=c?-1:b<0?-1:b})};a.scrollIntoContView=function(b){var e=a.getPages(a.odf_canvas.odfContainer().rootElement);e.length!==0&&window.scrollBy(0,e[b][1].getBoundingClientRect().top-30)};a.getPages=function(a){var a=a.getElementsByTagNameNS(e("draw"),"page"),g=[],c;for(c=
0;c<a.length;c+=1)g.push([a[c].getAttribute("draw:name"),a[c]]);return g};a.fillPageList=function(b,e){for(var c=a.getPages(b),d,f,g;e.firstChild;)e.removeChild(e.firstChild);for(d=0;d<c.length;d+=1)f=document.createElement("option"),g=k.getODFElementsWithXPath(c[d][1],'./draw:frame[@presentation:class="title"]//draw:text-box/text:p',xmldom.XPath),g=g.length>0?g[0].textContent:c[d][0],f.textContent=d+1+": "+g,e.appendChild(f)};a.startSlideMode=function(b){var e=document.getElementById("pagelist"),
c=a.odf_canvas.slidevisibilitycss().sheet;for(a.slide_mode=b;c.cssRules.length>0;)c.deleteRule(0);a.selectSlide(0);a.slide_mode==="single"?(c.insertRule("draw|page { position:fixed; left:0px;top:30px; z-index:1; }",0),c.insertRule("draw|page[slide_current] { z-index:2;}",1),c.insertRule("draw|page { -webkit-transform: scale(1);}",2),a.fitToWindow(),window.addEventListener("resize",a.fitToWindow,false)):a.slide_mode==="cont"&&window.removeEventListener("resize",a.fitToWindow,false);a.fillPageList(a.odf_canvas.odfContainer().rootElement,
e)};a.toggleToolbar=function(){var b,e,c;b=a.odf_canvas.slidevisibilitycss().sheet;e=-1;for(c=0;c<b.cssRules.length;c+=1)if(b.cssRules[c].cssText.substring(0,8)===".toolbar"){e=c;break}e>-1?b.deleteRule(e):b.insertRule(".toolbar { position:fixed; left:0px;top:-200px; z-index:0; }",0)};a.fitToWindow=function(){var b=a.getPages(a.root()),e=(window.innerHeight-40)/b[0][1].clientHeight,b=(window.innerWidth-10)/b[0][1].clientWidth,e=e<b?e:b,b=a.odf_canvas.slidevisibilitycss().sheet;b.deleteRule(2);b.insertRule("draw|page { \n-moz-transform: scale("+
e+"); \n-moz-transform-origin: 0% 0%; -webkit-transform-origin: 0% 0%; -webkit-transform: scale("+e+"); -o-transform-origin: 0% 0%; -o-transform: scale("+e+"); -ms-transform-origin: 0% 0%; -ms-transform: scale("+e+"); }",2)};a.load=function(b){a.odf_canvas.load(b)};a.odf_element=g;a.odf_canvas=new odf.OdfCanvas(a.odf_element);a.odf_canvas.addListener("statereadychange",a.setInitialSlideMode);a.slide_mode="undefined";document.addEventListener("keydown",a.keyDownHandler,false)}}();
gui.PresenterUI=function(){var g=new odf.Style2CSS,m=new xmldom.XPath,e=g.namespaceResolver;return function(g){var a=this;a.setInitialSlideMode=function(){a.startSlideMode("single")};a.keyDownHandler=function(c){if(!(c.target.isContentEditable||"input"===c.target.nodeName))switch(c.keyCode){case 84:a.toggleToolbar();break;case 37:case 8:a.prevSlide();break;case 39:case 32:a.nextSlide();break;case 36:a.firstSlide();break;case 35:a.lastSlide()}};a.root=function(){return a.odf_canvas.odfContainer().rootElement};
a.firstSlide=function(){a.slideChange(function(){return 0})};a.lastSlide=function(){a.slideChange(function(a,b){return b-1})};a.nextSlide=function(){a.slideChange(function(a,b){return a+1<b?a+1:-1})};a.prevSlide=function(){a.slideChange(function(a){return 1>a?-1:a-1})};a.slideChange=function(c){var b=a.getPages(a.odf_canvas.odfContainer().rootElement),d=-1,e=0;b.forEach(function(a){a=a[1];a.hasAttribute("slide_current")&&(d=e,a.removeAttribute("slide_current"));e+=1});c=c(d,b.length);-1===c&&(c=d);
b[c][1].setAttribute("slide_current","1");document.getElementById("pagelist").selectedIndex=c;"cont"===a.slide_mode&&window.scrollBy(0,b[c][1].getBoundingClientRect().top-30)};a.selectSlide=function(c){a.slideChange(function(a,d){return c>=d||0>c?-1:c})};a.scrollIntoContView=function(c){var b=a.getPages(a.odf_canvas.odfContainer().rootElement);0!==b.length&&window.scrollBy(0,b[c][1].getBoundingClientRect().top-30)};a.getPages=function(a){var a=a.getElementsByTagNameNS(e("draw"),"page"),b=[],d;for(d=
0;d<a.length;d+=1)b.push([a[d].getAttribute("draw:name"),a[d]]);return b};a.fillPageList=function(c,b){for(var d=a.getPages(c),e,f,g;b.firstChild;)b.removeChild(b.firstChild);for(e=0;e<d.length;e+=1)f=document.createElement("option"),g=m.getODFElementsWithXPath(d[e][1],'./draw:frame[@presentation:class="title"]//draw:text-box/text:p',xmldom.XPath),g=0<g.length?g[0].textContent:d[e][0],f.textContent=e+1+": "+g,b.appendChild(f)};a.startSlideMode=function(c){var b=document.getElementById("pagelist"),
d=a.odf_canvas.slidevisibilitycss().sheet;for(a.slide_mode=c;0<d.cssRules.length;)d.deleteRule(0);a.selectSlide(0);"single"===a.slide_mode?(d.insertRule("draw|page { position:fixed; left:0px;top:30px; z-index:1; }",0),d.insertRule("draw|page[slide_current] { z-index:2;}",1),d.insertRule("draw|page { -webkit-transform: scale(1);}",2),a.fitToWindow(),window.addEventListener("resize",a.fitToWindow,!1)):"cont"===a.slide_mode&&window.removeEventListener("resize",a.fitToWindow,!1);a.fillPageList(a.odf_canvas.odfContainer().rootElement,
b)};a.toggleToolbar=function(){var c,b,d;c=a.odf_canvas.slidevisibilitycss().sheet;b=-1;for(d=0;d<c.cssRules.length;d+=1)if(".toolbar"===c.cssRules[d].cssText.substring(0,8)){b=d;break}-1<b?c.deleteRule(b):c.insertRule(".toolbar { position:fixed; left:0px;top:-200px; z-index:0; }",0)};a.fitToWindow=function(){var c=a.getPages(a.root()),b=(window.innerHeight-40)/c[0][1].clientHeight,c=(window.innerWidth-10)/c[0][1].clientWidth,b=b<c?b:c,c=a.odf_canvas.slidevisibilitycss().sheet;c.deleteRule(2);c.insertRule("draw|page { \n-moz-transform: scale("+
b+"); \n-moz-transform-origin: 0% 0%; -webkit-transform-origin: 0% 0%; -webkit-transform: scale("+b+"); -o-transform-origin: 0% 0%; -o-transform: scale("+b+"); -ms-transform-origin: 0% 0%; -ms-transform: scale("+b+"); }",2)};a.load=function(c){a.odf_canvas.load(c)};a.odf_element=g;a.odf_canvas=new odf.OdfCanvas(a.odf_element);a.odf_canvas.addListener("statereadychange",a.setInitialSlideMode);a.slide_mode="undefined";document.addEventListener("keydown",a.keyDownHandler,!1)}}();
// Input 27
gui.Caret=function(i,k){k.ownerDocument.createElementNS("urn:webodf:names:cursor","cursor");this.updateToSelection=function(){i.rangeCount===1&&i.getRangeAt(0)}};
gui.Caret=function(g,m){m.ownerDocument.createElementNS("urn:webodf:names:cursor","cursor");this.updateToSelection=function(){1===g.rangeCount&&g.getRangeAt(0)}};
// Input 28
runtime.loadClass("core.Cursor");
gui.SelectionMover=function(i,k){function e(a,b){if(i.rangeCount!==0){var d=i.getRangeAt(0);if(d.startContainer&&d.startContainer.nodeType===1){k.setPoint(d.startContainer,d.startOffset);b();d=k.node();k.position();var f=[],e;for(e=0;e<i.rangeCount;e+=1)f[e]=i.getRangeAt(e);i.removeAllRanges();f.length===0&&(f[0]=d.ownerDocument.createRange());f[f.length-1].setStart(k.node(),k.position());for(e=0;e<f.length;e+=1)i.addRange(f[e])}}}function g(){b.updateToSelection();for(var a=b.getNode().getBoundingClientRect(),
c=a.left,d=a.top,a=false,f=200;!a;){f-=1;b.remove();if(i.focusNode&&i.focusNode.nodeType===1){k.setPoint(i.focusNode,i.focusOffset);k.stepForward();var a=k.node(),e=k.position();i.collapse(a,e);b.updateToSelection()}a=b.getNode().getBoundingClientRect();a=a.top!==d&&a.left>c}}var a=k.node().ownerDocument,b=new core.Cursor(i,a);this.movePointForward=function(a){e(a,k.stepForward)};this.movePointBackward=function(a){e(a,k.stepBackward)};this.moveLineForward=function(a){i.modify?i.modify(a?"extend":
"move","forward","line"):e(a,g)};this.moveLineBackward=function(a){i.modify?i.modify(a?"extend":"move","backward","line"):e(a,function(){})};return this};
gui.SelectionMover=function(g,m){function e(a,c){if(0!==g.rangeCount){var e=g.getRangeAt(0);if(e.startContainer&&1===e.startContainer.nodeType){m.setPoint(e.startContainer,e.startOffset);c();e=m.node();m.position();var f=[],h;for(h=0;h<g.rangeCount;h+=1)f[h]=g.getRangeAt(h);g.removeAllRanges();0===f.length&&(f[0]=e.ownerDocument.createRange());f[f.length-1].setStart(m.node(),m.position());for(h=0;h<f.length;h+=1)g.addRange(f[h])}}}function k(){c.updateToSelection();for(var a=c.getNode().getBoundingClientRect(),
d=a.left,e=a.top,a=!1;!a;){c.remove();if(g.focusNode&&1===g.focusNode.nodeType){m.setPoint(g.focusNode,g.focusOffset);m.stepForward();var a=m.node(),f=m.position();g.collapse(a,f);c.updateToSelection()}a=c.getNode().getBoundingClientRect();a=a.top!==e&&a.left>d}}var a=m.node().ownerDocument,c=new core.Cursor(g,a);this.movePointForward=function(a){e(a,m.stepForward)};this.movePointBackward=function(a){e(a,m.stepBackward)};this.moveLineForward=function(a){g.modify?g.modify(a?"extend":"move","forward",
"line"):e(a,k)};this.moveLineBackward=function(a){g.modify?g.modify(a?"extend":"move","backward","line"):e(a,function(){})};return this};
// Input 29
runtime.loadClass("core.PointWalker");runtime.loadClass("core.Cursor");
gui.XMLEdit=function(i,k){function e(a,b,c){a.addEventListener?a.addEventListener(b,c,false):a.attachEvent?a.attachEvent("on"+b,c):a["on"+b]=c}function g(a){a.preventDefault?a.preventDefault():a.returnValue=false}function a(){var a=i.ownerDocument.defaultView.getSelection();a&&!(a.rangeCount<=0)&&n&&(a=a.getRangeAt(0),n.setPoint(a.startContainer,a.startOffset))}function b(){var a=i.ownerDocument.defaultView.getSelection(),b,c;a.removeAllRanges();n&&n.node()&&(b=n.node(),c=b.ownerDocument.createRange(),
c.setStart(b,n.position()),c.collapse(true),a.addRange(c))}function h(c){var d=c.charCode||c.keyCode;if(n=null,n&&d===37)a(),n.stepBackward(),b();else if(d>=16&&d<=20||d>=33&&d<=40)return;g(c)}function c(){}function d(a){i.ownerDocument.defaultView.getSelection().getRangeAt(0);g(a)}function f(a){for(var b=a.firstChild;b&&b!==a;)b.nodeType===1&&f(b),b=b.nextSibling||b.parentNode;var c,d,e,b=a.attributes;c="";for(e=b.length-1;e>=0;e-=1)d=b.item(e),c=c+" "+d.nodeName+'="'+d.nodeValue+'"';a.setAttribute("customns_name",
a.nodeName);a.setAttribute("customns_atts",c);b=a.firstChild;for(d=/^\s*$/;b&&b!==a;)c=b,b=b.nextSibling||b.parentNode,c.nodeType===3&&d.test(c.nodeValue)&&c.parentNode.removeChild(c)}function j(a,b){for(var c=a.firstChild,d,e,f;c&&c!==a;){if(c.nodeType===1){j(c,b);d=c.attributes;for(f=d.length-1;f>=0;f-=1)if(e=d.item(f),e.namespaceURI==="http://www.w3.org/2000/xmlns/"&&!b[e.nodeValue])b[e.nodeValue]=e.localName}c=c.nextSibling||c.parentNode}}function p(){var a=i.ownerDocument.createElement("style"),
b;b={};j(i,b);var c={},d,e,f=0;for(d in b)if(b.hasOwnProperty(d)&&d){e=b[d];if(!e||c.hasOwnProperty(e)||e==="xmlns"){do e="ns"+f,f+=1;while(c.hasOwnProperty(e));b[d]=e}c[e]=true}b="@namespace customns url(customns);\n";a.type="text/css";b+=m;a.appendChild(i.ownerDocument.createTextNode(b));k=k.parentNode.replaceChild(a,k)}var m,l,u,n=null;if(!i.id)i.id="xml"+String(Math.random()).substring(2);l="#"+i.id+" ";m=l+"*,"+l+":visited, "+l+":link {display:block; margin: 0px; margin-left: 10px; font-size: medium; color: black; background: white; font-variant: normal; font-weight: normal; font-style: normal; font-family: sans-serif; text-decoration: none; white-space: pre-wrap; height: auto; width: auto}\n"+
l+":before {color: blue; content: '<' attr(customns_name) attr(customns_atts) '>';}\n"+l+":after {color: blue; content: '</' attr(customns_name) '>';}\n"+l+"{overflow: auto;}\n";(function(a){e(a,"click",d);e(a,"keydown",h);e(a,"keypress",c);e(a,"drop",g);e(a,"dragend",g);e(a,"beforepaste",g);e(a,"paste",g)})(i);this.updateCSS=p;this.setXML=function(a){a=a.documentElement||a;u=a=i.ownerDocument.importNode(a,true);for(f(a);i.lastChild;)i.removeChild(i.lastChild);i.appendChild(a);p();n=new core.PointWalker(a)};
this.getXML=function(){return u}};
gui.XMLEdit=function(g,m){function e(a,b,c){a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent?a.attachEvent("on"+b,c):a["on"+b]=c}function k(a){a.preventDefault?a.preventDefault():a.returnValue=!1}function a(){var a=g.ownerDocument.defaultView.getSelection();a&&!(0>=a.rangeCount)&&p&&(a=a.getRangeAt(0),p.setPoint(a.startContainer,a.startOffset))}function c(){var a=g.ownerDocument.defaultView.getSelection(),b,c;a.removeAllRanges();p&&p.node()&&(b=p.node(),c=b.ownerDocument.createRange(),
c.setStart(b,p.position()),c.collapse(!0),a.addRange(c))}function b(b){var d=b.charCode||b.keyCode;if(p=null,p&&37===d)a(),p.stepBackward(),c();else if(16<=d&&20>=d||33<=d&&40>=d)return;k(b)}function d(){}function o(a){g.ownerDocument.defaultView.getSelection().getRangeAt(0);k(a)}function f(a){for(var b=a.firstChild;b&&b!==a;)1===b.nodeType&&f(b),b=b.nextSibling||b.parentNode;var c,d,e,b=a.attributes;c="";for(e=b.length-1;0<=e;e-=1)d=b.item(e),c=c+" "+d.nodeName+'="'+d.nodeValue+'"';a.setAttribute("customns_name",
a.nodeName);a.setAttribute("customns_atts",c);b=a.firstChild;for(d=/^\s*$/;b&&b!==a;)c=b,b=b.nextSibling||b.parentNode,3===c.nodeType&&d.test(c.nodeValue)&&c.parentNode.removeChild(c)}function h(a,b){for(var c=a.firstChild,d,e,f;c&&c!==a;){if(1===c.nodeType){h(c,b);d=c.attributes;for(f=d.length-1;0<=f;f-=1)e=d.item(f),"http://www.w3.org/2000/xmlns/"===e.namespaceURI&&!b[e.nodeValue]&&(b[e.nodeValue]=e.localName)}c=c.nextSibling||c.parentNode}}function i(){var a=g.ownerDocument.createElement("style"),
b;b={};h(g,b);var c={},d,e,f=0;for(d in b)if(b.hasOwnProperty(d)&&d){e=b[d];if(!e||c.hasOwnProperty(e)||"xmlns"===e){do e="ns"+f,f+=1;while(c.hasOwnProperty(e));b[d]=e}c[e]=!0}a.type="text/css";b="@namespace customns url(customns);\n"+j;a.appendChild(g.ownerDocument.createTextNode(b));m=m.parentNode.replaceChild(a,m)}var j,n,x,p=null;g.id||(g.id="xml"+(""+Math.random()).substring(2));n="#"+g.id+" ";j=n+"*,"+n+":visited, "+n+":link {display:block; margin: 0px; margin-left: 10px; font-size: medium; color: black; background: white; font-variant: normal; font-weight: normal; font-style: normal; font-family: sans-serif; text-decoration: none; white-space: pre-wrap; height: auto; width: auto}\n"+
n+":before {color: blue; content: '<' attr(customns_name) attr(customns_atts) '>';}\n"+n+":after {color: blue; content: '</' attr(customns_name) '>';}\n"+n+"{overflow: auto;}\n";(function(a){e(a,"click",o);e(a,"keydown",b);e(a,"keypress",d);e(a,"drop",k);e(a,"dragend",k);e(a,"beforepaste",k);e(a,"paste",k)})(g);this.updateCSS=i;this.setXML=function(a){a=a.documentElement||a;x=a=g.ownerDocument.importNode(a,!0);for(f(a);g.lastChild;)g.removeChild(g.lastChild);g.appendChild(a);i();p=new core.PointWalker(a)};
this.getXML=function(){return x}};
// Input 30
(function(){return"core/Async.js,core/Base64.js,core/ByteArray.js,core/ByteArrayWriter.js,core/Cursor.js,core/JSLint.js,core/PointWalker.js,core/RawDeflate.js,core/RawInflate.js,core/UnitTester.js,core/Zip.js,gui/Caret.js,gui/SelectionMover.js,gui/XMLEdit.js,gui/PresenterUI.js,odf/FontLoader.js,odf/Formatting.js,odf/OdfCanvas.js,odf/OdfContainer.js,odf/Style2CSS.js,odf/StyleInfo.js,xmldom/LSSerializer.js,xmldom/LSSerializerFilter.js,xmldom/OperationalTransformDOM.js,xmldom/OperationalTransformInterface.js,xmldom/RelaxNG.js,xmldom/RelaxNG2.js,xmldom/RelaxNGParser.js,xmldom/XPath.js".split(",")})();