diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php index 4276fb48..5152c287 100644 --- a/plugins/calendar/lib/calendar_ui.php +++ b/plugins/calendar/lib/calendar_ui.php @@ -1,882 +1,883 @@ * @author Thomas Bruederli * * Copyright (C) 2010, Lazlo Westerhof * Copyright (C) 2014, Kolab Systems AG * * 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 . */ class calendar_ui { private $rc; private $cal; private $ready = false; public $screen; function __construct($cal) { $this->cal = $cal; $this->rc = $cal->rc; $this->screen = $this->rc->task == 'calendar' ? ($this->rc->action ? $this->rc->action: 'calendar') : 'other'; } /** * Calendar UI initialization and requests handlers */ public function init() { if ($this->ready) // already done return; // add taskbar button $this->cal->add_button(array( - 'command' => 'calendar', - 'class' => 'button-calendar', - 'classsel' => 'button-calendar button-selected', + 'command' => 'calendar', + 'class' => 'button-calendar', + 'classsel' => 'button-calendar button-selected', 'innerclass' => 'button-inner', - 'label' => 'calendar.calendar', + 'label' => 'calendar.calendar', + 'type' => 'link' ), 'taskbar'); // load basic client script $this->cal->include_script('calendar_base.js'); $skin_path = $this->cal->local_skin_path(); $this->cal->include_stylesheet($skin_path . '/calendar.css'); $this->ready = true; } /** * Register handler methods for the template engine */ public function init_templates() { $this->cal->register_handler('plugin.calendar_css', array($this, 'calendar_css')); $this->cal->register_handler('plugin.calendar_list', array($this, 'calendar_list')); $this->cal->register_handler('plugin.calendar_select', array($this, 'calendar_select')); $this->cal->register_handler('plugin.identity_select', array($this, 'identity_select')); $this->cal->register_handler('plugin.category_select', array($this, 'category_select')); $this->cal->register_handler('plugin.status_select', array($this, 'status_select')); $this->cal->register_handler('plugin.freebusy_select', array($this, 'freebusy_select')); $this->cal->register_handler('plugin.priority_select', array($this, 'priority_select')); $this->cal->register_handler('plugin.sensitivity_select', array($this, 'sensitivity_select')); $this->cal->register_handler('plugin.alarm_select', array($this, 'alarm_select')); $this->cal->register_handler('plugin.recurrence_form', array($this->cal->lib, 'recurrence_form')); $this->cal->register_handler('plugin.attachments_form', array($this, 'attachments_form')); $this->cal->register_handler('plugin.attachments_list', array($this, 'attachments_list')); $this->cal->register_handler('plugin.filedroparea', array($this, 'file_drop_area')); $this->cal->register_handler('plugin.attendees_list', array($this, 'attendees_list')); $this->cal->register_handler('plugin.attendees_form', array($this, 'attendees_form')); $this->cal->register_handler('plugin.resources_form', array($this, 'resources_form')); $this->cal->register_handler('plugin.resources_list', array($this, 'resources_list')); $this->cal->register_handler('plugin.resources_searchform', array($this, 'resources_search_form')); $this->cal->register_handler('plugin.resource_info', array($this, 'resource_info')); $this->cal->register_handler('plugin.resource_calendar', array($this, 'resource_calendar')); $this->cal->register_handler('plugin.attendees_freebusy_table', array($this, 'attendees_freebusy_table')); $this->cal->register_handler('plugin.edit_attendees_notify', array($this, 'edit_attendees_notify')); $this->cal->register_handler('plugin.edit_recurring_warning', array($this, 'recurring_event_warning')); $this->cal->register_handler('plugin.event_rsvp_buttons', array($this, 'event_rsvp_buttons')); $this->cal->register_handler('plugin.angenda_options', array($this, 'angenda_options')); $this->cal->register_handler('plugin.events_import_form', array($this, 'events_import_form')); $this->cal->register_handler('plugin.events_export_form', array($this, 'events_export_form')); $this->cal->register_handler('plugin.object_changelog_table', array('libkolab', 'object_changelog_table')); $this->cal->register_handler('plugin.searchform', array($this->rc->output, 'search_form')); // use generic method from rcube_template } /** * Adds CSS stylesheets to the page header */ public function addCSS() { $skin_path = $this->cal->local_skin_path(); $this->cal->include_stylesheet($skin_path . '/fullcalendar.css'); } /** * Adds JS files to the page header */ public function addJS() { $this->cal->include_script('calendar_ui.js'); $this->cal->include_script('lib/js/fullcalendar.js'); $this->rc->output->include_script('treelist.js'); // include kolab folderlist widget if available if (in_array('libkolab', $this->cal->api->loaded_plugins())) { $this->cal->api->include_script('libkolab/js/folderlist.js'); $this->cal->api->include_script('libkolab/js/audittrail.js'); } jqueryui::miniColors(); } /** * */ function calendar_css($attrib = array()) { $mode = $this->rc->config->get('calendar_event_coloring', $this->cal->defaults['calendar_event_coloring']); $categories = $this->cal->driver->list_categories(); $css = "\n"; foreach ((array)$categories as $class => $color) { if (empty($color)) continue; $class = 'cat-' . asciiwords(strtolower($class), true); $css .= ".$class { color: #$color }\n"; if ($mode > 0) { if ($mode == 2) { $css .= ".fc-event-$class .fc-event-bg {"; $css .= " opacity: 0.9;"; $css .= " filter: alpha(opacity=90);"; } else { $css .= ".fc-event-$class.fc-event-skin, "; $css .= ".fc-event-$class .fc-event-skin, "; $css .= ".fc-event-$class .fc-event-inner {"; } $css .= " background-color: #" . $color . ";"; if ($mode % 2) $css .= " border-color: #$color;"; $css .= "}\n"; } } $calendars = $this->cal->driver->list_calendars(); foreach ((array)$calendars as $id => $prop) { if (!$prop['color']) continue; $css .= $this->calendar_css_classes($id, $prop, $mode); } return html::tag('style', array('type' => 'text/css'), $css); } /** * */ public function calendar_css_classes($id, $prop, $mode) { $color = $prop['color']; $class = 'cal-' . asciiwords($id, true); $css .= "li .$class, #eventshow .$class { color: #$color; }\n"; if ($mode != 1) { if ($mode == 3) { $css .= ".fc-event-$class .fc-event-bg {"; $css .= " opacity: 0.9;"; $css .= " filter: alpha(opacity=90);"; } else { $css .= ".fc-event-$class, "; $css .= ".fc-event-$class .fc-event-inner {"; } if (!$prop['printmode']) $css .= " background-color: #$color;"; if ($mode % 2 == 0) $css .= " border-color: #$color;"; $css .= "}\n"; } return $css . ".$class .handle { background-color: #$color; }\n"; } /** * */ function calendar_list($attrib = array()) { $html = ''; $jsenv = array(); $tree = true; $calendars = $this->cal->driver->list_calendars(0, $tree); // walk folder tree if (is_object($tree)) { $html = $this->list_tree_html($tree, $calendars, $jsenv, $attrib); // append birthdays calendar which isn't part of $tree if ($bdaycal = $calendars[calendar_driver::BIRTHDAY_CALENDAR_ID]) { $calendars = array(calendar_driver::BIRTHDAY_CALENDAR_ID => $bdaycal); } else { $calendars = array(); // clear array for flat listing } } else { // fall-back to flat folder listing $attrib['class'] .= ' flat'; } foreach ((array)$calendars as $id => $prop) { if ($attrib['activeonly'] && !$prop['active']) continue; $html .= html::tag('li', array('id' => 'rcmlical' . $id, 'class' => $prop['group']), $content = $this->calendar_list_item($id, $prop, $jsenv, $attrib['activeonly']) ); } $this->rc->output->set_env('source', rcube_utils::get_input_value('source', rcube_utils::INPUT_GET)); $this->rc->output->set_env('calendars', $jsenv); $this->rc->output->add_gui_object('calendarslist', $attrib['id']); return html::tag('ul', $attrib, $html, html::$common_attrib); } /** * Return html for a structured list
    for the folder tree */ public function list_tree_html($node, $data, &$jsenv, $attrib) { $out = ''; foreach ($node->children as $folder) { $id = $folder->id; $prop = $data[$id]; $is_collapsed = false; // TODO: determine this somehow? $content = $this->calendar_list_item($id, $prop, $jsenv, $attrib['activeonly']); if (!empty($folder->children)) { $content .= html::tag('ul', array('style' => ($is_collapsed ? "display:none;" : null)), $this->list_tree_html($folder, $data, $jsenv, $attrib)); } if (strlen($content)) { $out .= html::tag('li', array( 'id' => 'rcmlical' . rcube_utils::html_identifier($id), 'class' => $prop['group'] . ($prop['virtual'] ? ' virtual' : ''), ), $content); } } return $out; } /** * Helper method to build a calendar list item (HTML content and js data) */ public function calendar_list_item($id, $prop, &$jsenv, $activeonly = false) { // enrich calendar properties with settings from the driver if (!$prop['virtual']) { unset($prop['user_id']); $prop['alarms'] = $this->cal->driver->alarms; $prop['attendees'] = $this->cal->driver->attendees; $prop['freebusy'] = $this->cal->driver->freebusy; $prop['attachments'] = $this->cal->driver->attachments; $prop['undelete'] = $this->cal->driver->undelete; $prop['feedurl'] = $this->cal->get_url(array('_cal' => $this->cal->ical_feed_hash($id) . '.ics', 'action' => 'feed')); $jsenv[$id] = $prop; } $classes = array('calendar', 'cal-' . asciiwords($id, true)); $title = $prop['title'] ?: ($prop['name'] != $prop['listname'] || strlen($prop['name']) > 25 ? html_entity_decode($prop['name'], ENT_COMPAT, RCUBE_CHARSET) : ''); if ($prop['virtual']) $classes[] = 'virtual'; else if (!$prop['editable']) $classes[] = 'readonly'; if ($prop['subscribed']) $classes[] = 'subscribed'; if ($prop['subscribed'] === 2) $classes[] = 'partial'; if ($prop['class']) $classes[] = $prop['class']; $content = ''; if (!$activeonly || $prop['active']) { $label_id = 'cl:' . $id; $content = html::div(join(' ', $classes), html::span(array('class' => 'calname', 'id' => $label_id, 'title' => $title), $prop['editname'] ? rcube::Q($prop['editname']) : $prop['listname']) . ($prop['virtual'] ? '' : html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => $prop['active'], 'aria-labelledby' => $label_id), '') . html::span('actions', ($prop['removable'] ? html::a(array('href' => '#', 'class' => 'remove', 'title' => $this->cal->gettext('removelist')), ' ') : '') . html::a(array('href' => '#', 'class' => 'quickview', 'title' => $this->cal->gettext('quickview'), 'role' => 'checkbox', 'aria-checked' => 'false'), '') . (isset($prop['subscribed']) ? html::a(array('href' => '#', 'class' => 'subscribed', 'title' => $this->cal->gettext('calendarsubscribe'), 'role' => 'checkbox', 'aria-checked' => $prop['subscribed'] ? 'true' : 'false'), ' ') : '') ) . html::span(array('class' => 'handle', 'style' => "background-color: #" . ($prop['color'] ?: 'f00')), ' ') ) ); } return $content; } /** * */ function angenda_options($attrib = array()) { $attrib += array('id' => 'agendaoptions'); $attrib['style'] .= 'display:none'; $select_range = new html_select(array('name' => 'listrange', 'id' => 'agenda-listrange')); $select_range->add(1 . ' ' . preg_replace('/\(.+\)/', '', $this->cal->lib->gettext('days')), $days); foreach (array(2,5,7,14,30,60,90,180,365) as $days) $select_range->add($days . ' ' . preg_replace('/\(|\)/', '', $this->cal->lib->gettext('days')), $days); $html .= html::label('agenda-listrange', $this->cal->gettext('listrange')); $html .= $select_range->show($this->rc->config->get('calendar_agenda_range', $this->cal->defaults['calendar_agenda_range'])); $select_sections = new html_select(array('name' => 'listsections', 'id' => 'agenda-listsections')); $select_sections->add('---', ''); foreach (array('day' => 'libcalendaring.days', 'week' => 'libcalendaring.weeks', 'month' => 'libcalendaring.months', 'smart' => 'calendar.smartsections') as $val => $label) $select_sections->add(preg_replace('/\(|\)/', '', ucfirst($this->rc->gettext($label))), $val); $html .= html::span('spacer', ' '); $html .= html::label('agenda-listsections', $this->cal->gettext('listsections')); $html .= $select_sections->show($this->rc->config->get('calendar_agenda_sections', $this->cal->defaults['calendar_agenda_sections'])); return html::div($attrib, $html); } /** * Render a HTML select box for calendar selection */ function calendar_select($attrib = array()) { $attrib['name'] = 'calendar'; $attrib['is_escaped'] = true; $select = new html_select($attrib); foreach ((array)$this->cal->driver->list_calendars() as $id => $prop) { if ($prop['editable'] || strpos($prop['rights'], 'i') !== false) $select->add($prop['name'], $id); } return $select->show(null); } /** * Render a HTML select box for user identity selection */ function identity_select($attrib = array()) { $attrib['name'] = 'identity'; $select = new html_select($attrib); $identities = $this->rc->user->list_emails(); foreach ($identities as $ident) { $select->add(format_email_recipient($ident['email'], $ident['name']), $ident['identity_id']); } return $select->show(null); } /** * Render a HTML select box to select an event category */ function category_select($attrib = array()) { $attrib['name'] = 'categories'; $select = new html_select($attrib); $select->add('---', ''); foreach (array_keys((array)$this->cal->driver->list_categories()) as $cat) { $select->add($cat, $cat); } return $select->show(null); } /** * Render a HTML select box for status property */ function status_select($attrib = array()) { $attrib['name'] = 'status'; $select = new html_select($attrib); $select->add('---', ''); $select->add($this->cal->gettext('status-confirmed'), 'CONFIRMED'); $select->add($this->cal->gettext('status-cancelled'), 'CANCELLED'); $select->add($this->cal->gettext('status-tentative'), 'TENTATIVE'); return $select->show(null); } /** * Render a HTML select box for free/busy/out-of-office property */ function freebusy_select($attrib = array()) { $attrib['name'] = 'freebusy'; $select = new html_select($attrib); $select->add($this->cal->gettext('free'), 'free'); $select->add($this->cal->gettext('busy'), 'busy'); // out-of-office is not supported by libkolabxml (#3220) // $select->add($this->cal->gettext('outofoffice'), 'outofoffice'); $select->add($this->cal->gettext('tentative'), 'tentative'); return $select->show(null); } /** * Render a HTML select for event priorities */ function priority_select($attrib = array()) { $attrib['name'] = 'priority'; $select = new html_select($attrib); $select->add('---', '0'); $select->add('1 '.$this->cal->gettext('highest'), '1'); $select->add('2 '.$this->cal->gettext('high'), '2'); $select->add('3 ', '3'); $select->add('4 ', '4'); $select->add('5 '.$this->cal->gettext('normal'), '5'); $select->add('6 ', '6'); $select->add('7 ', '7'); $select->add('8 '.$this->cal->gettext('low'), '8'); $select->add('9 '.$this->cal->gettext('lowest'), '9'); return $select->show(null); } /** * Render HTML input for sensitivity selection */ function sensitivity_select($attrib = array()) { $attrib['name'] = 'sensitivity'; $select = new html_select($attrib); $select->add($this->cal->gettext('public'), 'public'); $select->add($this->cal->gettext('private'), 'private'); $select->add($this->cal->gettext('confidential'), 'confidential'); return $select->show(null); } /** * Render HTML form for alarm configuration */ function alarm_select($attrib = array()) { return $this->cal->lib->alarm_select($attrib, $this->cal->driver->alarm_types, $this->cal->driver->alarm_absolute); } /** * */ function edit_attendees_notify($attrib = array()) { $checkbox = new html_checkbox(array('name' => '_notify', 'id' => 'edit-attendees-donotify', 'value' => 1)); return html::div($attrib, html::label(null, $checkbox->show(1) . ' ' . $this->cal->gettext('sendnotifications'))); } /** * Generate the form for recurrence settings */ function recurring_event_warning($attrib = array()) { $attrib['id'] = 'edit-recurring-warning'; $radio = new html_radiobutton(array('name' => '_savemode', 'class' => 'edit-recurring-savemode')); $form = html::label(null, $radio->show('', array('value' => 'current')) . $this->cal->gettext('currentevent')) . ' ' . html::label(null, $radio->show('', array('value' => 'future')) . $this->cal->gettext('futurevents')) . ' ' . html::label(null, $radio->show('all', array('value' => 'all')) . $this->cal->gettext('allevents')) . ' ' . html::label(null, $radio->show('', array('value' => 'new')) . $this->cal->gettext('saveasnew')); return html::div($attrib, html::div('message', html::span('ui-icon ui-icon-alert', '') . $this->cal->gettext('changerecurringeventwarning')) . html::div('savemode', $form)); } /** * Form for uploading and importing events */ function events_import_form($attrib = array()) { if (!$attrib['id']) $attrib['id'] = 'rcmImportForm'; // Get max filesize, enable upload progress bar $max_filesize = $this->rc->upload_init(); $accept = '.ics, text/calendar, text/x-vcalendar, application/ics'; if (class_exists('ZipArchive', false)) { $accept .= ', .zip, application/zip'; } $input = new html_inputfield(array( 'type' => 'file', 'name' => '_data', 'size' => $attrib['uploadfieldsize'], 'accept' => $accept)); $select = new html_select(array('name' => '_range', 'id' => 'event-import-range')); $select->add(array( $this->cal->gettext('onemonthback'), $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>2))), $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>3))), $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>6))), $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>12))), $this->cal->gettext('all'), ), array('1','2','3','6','12',0)); $html .= html::div('form-section', html::div(null, $input->show()) . html::div('hint', $this->rc->gettext(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) ); $html .= html::div('form-section', html::label('event-import-calendar', $this->cal->gettext('calendar')) . $this->calendar_select(array('name' => 'calendar', 'id' => 'event-import-calendar')) ); $html .= html::div('form-section', html::label('event-import-range', $this->cal->gettext('importrange')) . $select->show(1) ); $this->rc->output->add_gui_object('importform', $attrib['id']); $this->rc->output->add_label('import'); return html::tag('form', array('action' => $this->rc->url(array('task' => 'calendar', 'action' => 'import_events')), 'method' => "post", 'enctype' => 'multipart/form-data', 'id' => $attrib['id']), $html ); } /** * Form to select options for exporting events */ function events_export_form($attrib = array()) { if (!$attrib['id']) $attrib['id'] = 'rcmExportForm'; $html .= html::div('form-section', html::label('event-export-calendar', $this->cal->gettext('calendar')) . $this->calendar_select(array('name' => 'calendar', 'id' => 'event-export-calendar')) ); $select = new html_select(array('name' => 'range', 'id' => 'event-export-range')); $select->add(array( $this->cal->gettext('all'), $this->cal->gettext('onemonthback'), $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>2))), $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>3))), $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>6))), $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>12))), $this->cal->gettext('customdate'), ), array(0,'1','2','3','6','12','custom')); $startdate = new html_inputfield(array('name' => 'start', 'size' => 11, 'id' => 'event-export-startdate')); $html .= html::div('form-section', html::label('event-export-range', $this->cal->gettext('exportrange')) . $select->show(0) . html::span(array('style'=>'display:none'), $startdate->show()) ); $checkbox = new html_checkbox(array('name' => 'attachments', 'id' => 'event-export-attachments', 'value' => 1)); $html .= html::div('form-section', html::label('event-export-attachments', $this->cal->gettext('exportattachments')) . $checkbox->show(1) ); $this->rc->output->add_gui_object('exportform', $attrib['id']); return html::tag('form', array('action' => $this->rc->url(array('task' => 'calendar', 'action' => 'export_events')), 'method' => "post", 'id' => $attrib['id']), $html ); } /** * Generate the form for event attachments upload */ function attachments_form($attrib = array()) { // add ID if not given if (!$attrib['id']) $attrib['id'] = 'rcmUploadForm'; // Get max filesize, enable upload progress bar $max_filesize = $this->rc->upload_init(); $button = new html_inputfield(array('type' => 'button')); $input = new html_inputfield(array( 'type' => 'file', 'name' => '_attachments[]', 'multiple' => 'multiple', 'size' => $attrib['attachmentfieldsize'])); return html::div($attrib, html::div(null, $input->show()) . html::div('buttons', $button->show($this->rc->gettext('upload'), array('class' => 'button mainaction', 'onclick' => rcmail_output::JS_OBJECT_NAME . ".upload_file(this.form)"))) . html::div('hint', $this->rc->gettext(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) ); } /** * Register UI object for HTML5 drag & drop file upload */ function file_drop_area($attrib = array()) { if ($attrib['id']) { $this->rc->output->add_gui_object('filedrop', $attrib['id']); $this->rc->output->set_env('filedrop', array('action' => 'upload', 'fieldname' => '_attachments')); } } /** * Generate HTML element for attachments list */ function attachments_list($attrib = array()) { if (!$attrib['id']) $attrib['id'] = 'rcmAttachmentList'; $skin_path = $this->cal->local_skin_path(); if ($attrib['deleteicon']) { $_SESSION[calendar::SESSION_KEY . '_deleteicon'] = $skin_path . $attrib['deleteicon']; $this->rc->output->set_env('deleteicon', $skin_path . $attrib['deleteicon']); } if ($attrib['cancelicon']) $this->rc->output->set_env('cancelicon', $skin_path . $attrib['cancelicon']); if ($attrib['loadingicon']) $this->rc->output->set_env('loadingicon', $skin_path . $attrib['loadingicon']); $this->rc->output->add_gui_object('attachmentlist', $attrib['id']); $this->attachmentlist_id = $attrib['id']; return html::tag('ul', $attrib, '', html::$common_attrib); } /** * Handler for calendar form template. * The form content could be overriden by the driver */ function calendar_editform($action, $calendar = array()) { // compose default calendar form fields $input_name = new html_inputfield(array('name' => 'name', 'id' => 'calendar-name', 'size' => 20)); $input_color = new html_inputfield(array('name' => 'color', 'id' => 'calendar-color', 'size' => 6)); $formfields = array( 'name' => array( 'label' => $this->cal->gettext('name'), 'value' => $input_name->show($calendar['name']), 'id' => 'calendar-name', ), 'color' => array( 'label' => $this->cal->gettext('color'), 'value' => $input_color->show($calendar['color']), 'id' => 'calendar-color', ), ); if ($this->cal->driver->alarms) { $checkbox = new html_checkbox(array('name' => 'showalarms', 'id' => 'calendar-showalarms', 'value' => 1)); $formfields['showalarms'] = array( 'label' => $this->cal->gettext('showalarms'), 'value' => $checkbox->show($calendar['showalarms']?1:0), 'id' => 'calendar-showalarms', ); } // allow driver to extend or replace the form content return html::tag('form', array('action' => "#", 'method' => "get", 'id' => 'calendarpropform'), $this->cal->driver->calendar_form($action, $calendar, $formfields) ); } /** * */ function attendees_list($attrib = array()) { // add "noreply" checkbox to attendees table only $invitations = strpos($attrib['id'], 'attend') !== false; $invite = new html_checkbox(array('value' => 1, 'id' => 'edit-attendees-invite')); $table = new html_table(array('cols' => 5 + intval($invitations), 'border' => 0, 'cellpadding' => 0, 'class' => 'rectable')); $table->add_header('role', $this->cal->gettext('role')); $table->add_header('name', $this->cal->gettext($attrib['coltitle'] ?: 'attendee')); $table->add_header('availability', $this->cal->gettext('availability')); $table->add_header('confirmstate', $this->cal->gettext('confirmstate')); if ($invitations) { $table->add_header(array('class' => 'invite', 'title' => $this->cal->gettext('sendinvitations')), $invite->show(1) . html::label('edit-attendees-invite', $this->cal->gettext('sendinvitations'))); } $table->add_header('options', ''); // hide invite column if disabled by config $itip_notify = (int)$this->rc->config->get('calendar_itip_send_option', $this->cal->defaults['calendar_itip_send_option']); if ($invitations && !($itip_notify & 2)) { $css = sprintf('#%s td.invite, #%s th.invite { display:none !important }', $attrib['id'], $attrib['id']); $this->rc->output->add_footer(html::tag('style', array('type' => 'text/css'), $css)); } return $table->show($attrib); } /** * */ function attendees_form($attrib = array()) { $input = new html_inputfield(array('name' => 'participant', 'id' => 'edit-attendee-name', 'size' => 30)); $textarea = new html_textarea(array('name' => 'comment', 'id' => 'edit-attendees-comment', 'rows' => 4, 'cols' => 55, 'title' => $this->cal->gettext('itipcommenttitle'))); return html::div($attrib, html::div(null, $input->show() . " " . html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-attendee-add', 'value' => $this->cal->gettext('addattendee'))) . " " . html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-attendee-schedule', 'value' => $this->cal->gettext('scheduletime').'...'))) . html::p('attendees-commentbox', html::label(null, $this->cal->gettext('itipcomment') . $textarea->show())) ); } /** * */ function resources_form($attrib = array()) { $input = new html_inputfield(array('name' => 'resource', 'id' => 'edit-resource-name', 'size' => 30)); return html::div($attrib, html::div(null, $input->show() . " " . html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-resource-add', 'value' => $this->cal->gettext('addresource'))) . " " . html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-resource-find', 'value' => $this->cal->gettext('findresources').'...'))) ); } /** * */ function resources_list($attrib = array()) { $attrib += array('id' => 'calendar-resources-list'); $this->rc->output->add_gui_object('resourceslist', $attrib['id']); return html::tag('ul', $attrib, '', html::$common_attrib); } /** * */ public function resource_info($attrib = array()) { $attrib += array('id' => 'calendar-resources-info'); $this->rc->output->add_gui_object('resourceinfo', $attrib['id']); $this->rc->output->add_gui_object('resourceownerinfo', $attrib['id'] . '-owner'); // copy address book labels for owner details to client $this->rc->output->add_label('name','firstname','surname','department','jobtitle','email','phone','address'); $table_attrib = array('id','class','style','width','summary','cellpadding','cellspacing','border'); return html::tag('table', $attrib, html::tag('tbody', null, ''), $table_attrib) . html::tag('table', array('id' => $attrib['id'] . '-owner', 'style' => 'display:none') + $attrib, html::tag('thead', null, html::tag('tr', null, html::tag('td', array('colspan' => 2), rcube::Q($this->cal->gettext('resourceowner'))) ) ) . html::tag('tbody', null, ''), $table_attrib); } /** * */ public function resource_calendar($attrib = array()) { $attrib += array('id' => 'calendar-resources-calendar'); $this->rc->output->add_gui_object('resourceinfocalendar', $attrib['id']); return html::div($attrib, ''); } /** * GUI object 'searchform' for the resource finder dialog * * @param array Named parameters * @return string HTML code for the gui object */ function resources_search_form($attrib) { $attrib += array('command' => 'search-resource', 'id' => 'rcmcalresqsearchbox', 'autocomplete' => 'off'); $attrib['name'] = '_q'; $input_q = new html_inputfield($attrib); $out = $input_q->show(); // add form tag around text field $out = $this->rc->output->form_tag(array( 'name' => "rcmcalresoursqsearchform", 'onsubmit' => rcmail_output::JS_OBJECT_NAME . ".command('" . $attrib['command'] . "'); return false", 'style' => "display:inline"), $out); return $out; } /** * */ function attendees_freebusy_table($attrib = array()) { $table = new html_table(array('cols' => 2, 'border' => 0, 'cellspacing' => 0)); $table->add('attendees', html::tag('h3', 'boxtitle', $this->cal->gettext('tabattendees')) . html::div('timesheader', ' ') . html::div(array('id' => 'schedule-attendees-list', 'class' => 'attendees-list'), '') ); $table->add('times', html::div('scroll', html::tag('table', array('id' => 'schedule-freebusy-times', 'border' => 0, 'cellspacing' => 0), html::tag('thead') . html::tag('tbody')) . html::div(array('id' => 'schedule-event-time', 'style' => 'display:none'), ' ') ) ); return $table->show($attrib); } /** * */ function event_invitebox($attrib = array()) { if ($this->cal->event) { return html::div($attrib, $this->cal->itip->itip_object_details_table($this->cal->event, $this->cal->itip->gettext('itipinvitation')) . $this->cal->invitestatus ); } return ''; } function event_rsvp_buttons($attrib = array()) { $actions = array('accepted','tentative','declined'); if ($attrib['delegate'] !== 'false') $actions[] = 'delegated'; return $this->cal->itip->itip_rsvp_buttons($attrib, $actions); } } diff --git a/plugins/calendar/skins/larry/templates/calendar.html b/plugins/calendar/skins/larry/templates/calendar.html index b7f04bd9..895b6cfb 100644 --- a/plugins/calendar/skins/larry/templates/calendar.html +++ b/plugins/calendar/skins/larry/templates/calendar.html @@ -1,532 +1,532 @@ <roundcube:object name="pagetitle" />

    diff --git a/plugins/kolab_addressbook/lib/kolab_addressbook_ui.php b/plugins/kolab_addressbook/lib/kolab_addressbook_ui.php index 26455cf1..941c4242 100644 --- a/plugins/kolab_addressbook/lib/kolab_addressbook_ui.php +++ b/plugins/kolab_addressbook/lib/kolab_addressbook_ui.php @@ -1,352 +1,353 @@ * * Copyright (C) 2012, Kolab Systems AG * * 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 . */ class kolab_addressbook_ui { private $plugin; private $rc; /** * Class constructor * * @param kolab_addressbook $plugin Plugin object */ public function __construct($plugin) { $this->rc = rcube::get_instance(); $this->plugin = $plugin; $this->init_ui(); } /** * Adds folders management functionality to Addressbook UI */ private function init_ui() { if (!empty($this->rc->action) && !preg_match('/^plugin\.book/', $this->rc->action) && $this->rc->action != 'show') { return; } // Include script $this->plugin->include_script('kolab_addressbook.js'); if (empty($this->rc->action)) { // Include stylesheet (for directorylist) $this->plugin->include_stylesheet($this->plugin->local_skin_path().'/kolab_addressbook.css'); // include kolab folderlist widget if available if (in_array('libkolab', $this->plugin->api->loaded_plugins())) { $this->plugin->api->include_script('libkolab/js/folderlist.js'); } // Add actions on address books $options = array('book-create', 'book-edit', 'book-delete', 'book-remove'); $idx = 0; if ($dav_url = $this->rc->config->get('kolab_addressbook_carddav_url')) { $options[] = 'book-showurl'; $this->rc->output->set_env('kolab_addressbook_carddav_url', true); // set CardDAV URI for specified ldap addressbook if ($ldap_abook = $this->rc->config->get('kolab_addressbook_carddav_ldap')) { $dav_ldap_url = strtr($dav_url, array( '%h' => $_SERVER['HTTP_HOST'], '%u' => urlencode($this->rc->get_user_name()), '%i' => 'ldap-directory', '%n' => '', )); $this->rc->output->set_env('kolab_addressbook_carddav_ldap', $ldap_abook); $this->rc->output->set_env('kolab_addressbook_carddav_ldap_url', $dav_ldap_url); } } foreach ($options as $command) { $content = html::tag('li', $idx ? null : array('class' => 'separator_above'), $this->plugin->api->output->button(array( 'label' => 'kolab_addressbook.'.str_replace('-', '', $command), 'domain' => $this->ID, 'classact' => 'active', - 'command' => $command + 'command' => $command, + 'type' => 'link' ))); $this->plugin->api->add_content($content, 'groupoptions'); $idx++; } // Link to Settings/Folders $content = html::tag('li', array('class' => 'separator_above'), $this->plugin->api->output->button(array( 'label' => 'managefolders', 'type' => 'link', 'classact' => 'active', 'command' => 'folders', 'task' => 'settings', ))); $this->plugin->api->add_content($content, 'groupoptions'); $this->rc->output->add_label('kolab_addressbook.bookdeleteconfirm', 'kolab_addressbook.bookdeleting', 'kolab_addressbook.bookshowurl', 'kolab_addressbook.carddavurldescription', 'kolab_addressbook.bookedit', 'kolab_addressbook.bookdelete', 'kolab_addressbook.bookshowurl', 'kolab_addressbook.findaddressbooks', 'kolab_addressbook.searchterms', 'kolab_addressbook.foldersearchform', 'kolab_addressbook.listsearchresults', 'kolab_addressbook.nraddressbooksfound', 'kolab_addressbook.noaddressbooksfound', 'kolab_addressbook.foldersubscribe', 'resetsearch'); if ($this->plugin->bonnie_api) { $this->rc->output->set_env('kolab_audit_trail', true); $this->plugin->api->include_script('libkolab/js/audittrail.js'); $this->rc->output->add_label( 'kolab_addressbook.showhistory', 'kolab_addressbook.objectchangelog', 'kolab_addressbook.objectdiff', 'kolab_addressbook.objectdiffnotavailable', 'kolab_addressbook.objectchangelognotavailable', 'kolab_addressbook.revisionrestoreconfirm' ); $this->plugin->add_hook('render_page', array($this, 'render_audittrail_page')); $this->plugin->register_handler('plugin.object_changelog_table', array('libkolab', 'object_changelog_table')); } } // include stylesheet for audit trail else if ($this->rc->action == 'show' && $this->plugin->bonnie_api) { $this->plugin->include_stylesheet($this->plugin->local_skin_path().'/kolab_addressbook.css'); $this->rc->output->add_label('kolab_addressbook.showhistory'); } // book create/edit form else { $this->rc->output->add_label('kolab_addressbook.nobooknamewarning', 'kolab_addressbook.booksaving'); } } /** * Handler for address book create/edit action */ public function book_edit() { $this->rc->output->add_handler('bookdetails', array($this, 'book_form')); $this->rc->output->send('kolab_addressbook.bookedit'); } /** * Handler for 'bookdetails' object returning form content for book create/edit * * @param array $attr Object attributes * * @return string HTML output */ public function book_form($attrib) { $action = trim(rcube_utils::get_input_value('_act', rcube_utils::INPUT_GPC)); $folder = trim(rcube_utils::get_input_value('_source', rcube_utils::INPUT_GPC, true)); // UTF8 $hidden_fields[] = array('name' => '_source', 'value' => $folder); $folder = rcube_charset::convert($folder, RCUBE_CHARSET, 'UTF7-IMAP'); $storage = $this->rc->get_storage(); $delim = $storage->get_hierarchy_delimiter(); if ($this->rc->action == 'plugin.book-save') { // save error $name = trim(rcube_utils::get_input_value('_name', rcube_utils::INPUT_GPC, true)); // UTF8 $old = trim(rcube_utils::get_input_value('_oldname', rcube_utils::INPUT_GPC, true)); // UTF7-IMAP $path_imap = trim(rcube_utils::get_input_value('_parent', rcube_utils::INPUT_GPC, true)); // UTF7-IMAP $hidden_fields[] = array('name' => '_oldname', 'value' => $old); $folder = $old; } else if ($action == 'edit') { $path_imap = explode($delim, $folder); $name = rcube_charset::convert(array_pop($path_imap), 'UTF7-IMAP'); $path_imap = implode($path_imap, $delim); } else { // create $path_imap = $folder; $name = ''; $folder = ''; } // Store old name, get folder options if (strlen($folder)) { $hidden_fields[] = array('name' => '_oldname', 'value' => $folder); $options = $storage->folder_info($folder); } $form = array(); // General tab $form['props'] = array( 'name' => $this->rc->gettext('properties'), ); if (!empty($options) && ($options['norename'] || $options['protected'])) { $foldername = rcube::Q(str_replace($delim, ' » ', kolab_storage::object_name($folder))); } else { $foldername = new html_inputfield(array('name' => '_name', 'id' => '_name', 'size' => 30)); $foldername = $foldername->show($name); } $form['props']['fieldsets']['location'] = array( 'name' => $this->rc->gettext('location'), 'content' => array( 'name' => array( 'label' => $this->plugin->gettext('bookname'), 'value' => $foldername, ), ), ); if (!empty($options) && ($options['norename'] || $options['protected'])) { // prevent user from moving folder $hidden_fields[] = array('name' => '_parent', 'value' => $path_imap); } else { $select = kolab_storage::folder_selector('contact', array('name' => '_parent'), $folder); $form['props']['fieldsets']['location']['content']['path'] = array( 'label' => $this->plugin->gettext('parentbook'), 'value' => $select->show(strlen($folder) ? $path_imap : ''), ); } // Allow plugins to modify address book form content (e.g. with ACL form) $plugin = $this->rc->plugins->exec_hook('addressbook_form', array('form' => $form, 'options' => $options, 'name' => $folder)); $form = $plugin['form']; // Set form tags and hidden fields list($form_start, $form_end) = $this->get_form_tags($attrib, 'plugin.book-save', null, $hidden_fields); unset($attrib['form']); // return the complete edit form as table $out = "$form_start\n"; // Create form output foreach ($form as $tab) { if (!empty($tab['fieldsets']) && is_array($tab['fieldsets'])) { $content = ''; foreach ($tab['fieldsets'] as $fieldset) { $subcontent = $this->get_form_part($fieldset); if ($subcontent) { $content .= html::tag('fieldset', null, html::tag('legend', null, rcube::Q($fieldset['name'])) . $subcontent) ."\n"; } } } else { $content = $this->get_form_part($tab); } if ($content) { $out .= html::tag('fieldset', null, html::tag('legend', null, rcube::Q($tab['name'])) . $content) ."\n"; } } $out .= "\n$form_end"; return $out; } /** * */ public function render_audittrail_page($p) { // append audit trail UI elements to contact page if ($p['template'] === 'addressbook' && !$p['kolab-audittrail']) { $this->rc->output->add_footer($this->rc->output->parse('kolab_addressbook.audittrail', false, false)); $p['kolab-audittrail'] = true; } return $p; } private function get_form_part($form) { $content = ''; if (is_array($form['content']) && !empty($form['content'])) { $table = new html_table(array('cols' => 2, 'class' => 'propform')); foreach ($form['content'] as $col => $colprop) { $colprop['id'] = '_'.$col; $label = !empty($colprop['label']) ? $colprop['label'] : $this->rc->gettext($col); $table->add('title', sprintf('', $colprop['id'], rcube::Q($label))); $table->add(null, $colprop['value']); } $content = $table->show(); } else { $content = $form['content']; } return $content; } private function get_form_tags($attrib, $action, $id = null, $hidden = null) { $form_start = $form_end = ''; $request_key = $action . (isset($id) ? '.'.$id : ''); $form_start = $this->rc->output->request_form(array( 'name' => 'form', 'method' => 'post', 'task' => $this->rc->task, 'action' => $action, 'request' => $request_key, 'noclose' => true, ) + $attrib); if (is_array($hidden)) { foreach ($hidden as $field) { $hiddenfield = new html_hiddenfield($field); $form_start .= $hiddenfield->show(); } } $form_end = !strlen($attrib['form']) ? '' : ''; $EDIT_FORM = !empty($attrib['form']) ? $attrib['form'] : 'form'; $this->rc->output->add_gui_object('editform', $EDIT_FORM); return array($form_start, $form_end); } } diff --git a/plugins/kolab_files/lib/kolab_files_engine.php b/plugins/kolab_files/lib/kolab_files_engine.php index 4ea58ba1..5d98a146 100644 --- a/plugins/kolab_files/lib/kolab_files_engine.php +++ b/plugins/kolab_files/lib/kolab_files_engine.php @@ -1,1552 +1,1553 @@ * * Copyright (C) 2013-2015, Kolab Systems AG * * 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 . */ class kolab_files_engine { private $plugin; private $rc; private $url; private $url_srv; private $timeout = 600; private $files_sort_cols = array('name', 'mtime', 'size'); private $sessions_sort_cols = array('name'); const API_VERSION = 2; /** * Class constructor */ public function __construct($plugin, $client_url, $server_url = null) { $this->url = rtrim(rcube_utils::resolve_url($client_url), '/ '); $this->url_srv = $server_url ? rtrim(rcube_utils::resolve_url($server_url), '/ ') : $this->url; $this->plugin = $plugin; $this->rc = $plugin->rc; $this->timeout = $this->rc->config->get('session_lifetime') * 60; } /** * User interface initialization */ public function ui() { $this->plugin->add_texts('localization/'); $templates = array(); // set templates of Files UI and widgets if ($this->rc->task == 'mail') { if (in_array($this->rc->action, array('', 'show', 'compose'))) { $templates[] = 'compose_plugin'; } if (in_array($this->rc->action, array('show', 'preview', 'get'))) { $templates[] = 'message_plugin'; if ($this->rc->action == 'get') { // add "Save as" button into attachment toolbar $this->plugin->add_button(array( 'id' => 'saveas', 'name' => 'saveas', 'type' => 'link', 'onclick' => 'kolab_directory_selector_dialog()', 'class' => 'button buttonPas saveas', 'classact' => 'button saveas', 'label' => 'kolab_files.save', 'title' => 'kolab_files.saveto', ), 'toolbar'); } else { // add "Save as" button into attachment menu $this->plugin->add_button(array( 'id' => 'attachmenusaveas', 'name' => 'attachmenusaveas', 'type' => 'link', 'wrapper' => 'li', 'onclick' => 'return false', 'class' => 'icon active saveas', 'classact' => 'icon active saveas', 'innerclass' => 'icon active saveas', 'label' => 'kolab_files.saveto', ), 'attachmentmenu'); } } $list_widget = true; } else if (!$this->rc->action && in_array($this->rc->task, array('calendar', 'tasks'))) { $list_widget = true; $templates[] = 'compose_plugin'; } else if ($this->rc->task == 'files') { $templates[] = 'files'; // get list of external sources $this->get_external_storage_drivers(); // these labels may be needed even if fetching ext sources failed $this->plugin->add_label('folderauthtitle', 'authenticating'); } if ($list_widget) { $this->folder_list_env(); $this->plugin->add_label('save', 'cancel', 'saveto', 'saveall', 'fromcloud', 'attachsel', 'selectfiles', 'attaching', 'collection_audio', 'collection_video', 'collection_image', 'collection_document', 'folderauthtitle', 'authenticating' ); } // add taskbar button if (empty($_REQUEST['framed'])) { $this->plugin->add_button(array( 'command' => 'files', 'class' => 'button-files', 'classsel' => 'button-files button-selected', 'innerclass' => 'button-inner', 'label' => 'kolab_files.files', + 'type' => 'link' ), 'taskbar'); } if ($_SESSION['kolab_files_caps']['MANTICORE'] || $_SESSION['kolab_files_caps']['WOPI']) { $_SESSION['kolab_files_caps']['DOCEDIT'] = true; $_SESSION['kolab_files_caps']['DOCTYPE'] = $_SESSION['kolab_files_caps']['MANTICORE'] ? 'manticore' : 'wopi'; } $this->plugin->include_stylesheet($this->plugin->local_skin_path().'/style.css'); $this->plugin->include_script($this->url . '/js/files_api.js'); $this->plugin->include_script('kolab_files.js'); $this->rc->output->set_env('files_url', $this->url . '/api/'); $this->rc->output->set_env('files_token', $this->get_api_token()); $this->rc->output->set_env('files_caps', $_SESSION['kolab_files_caps']); $this->rc->output->set_env('files_user', $this->rc->get_user_name()); if ($_SESSION['kolab_files_caps']['DOCEDIT']) { $this->plugin->add_label('declinednotice', 'invitednotice', 'acceptedownernotice', 'declinedownernotice', 'requestednotice', 'acceptednotice', 'declinednotice', 'more', 'accept', 'decline', 'join', 'status', 'when', 'file', 'comment', 'statusaccepted', 'statusinvited', 'statusdeclined', 'statusrequested', 'invitationaccepting', 'invitationdeclining', 'invitationrequesting', 'close', 'invitationtitle', 'sessions'); } if (!empty($templates)) { $collapsed_folders = (string) $this->rc->config->get('kolab_files_collapsed_folders'); $this->rc->output->include_script('treelist.js'); $this->rc->output->set_env('kolab_files_collapsed_folders', $collapsed_folders); // register template objects for dialogs (and main interface) $this->rc->output->add_handlers(array( 'folder-create-form' => array($this, 'folder_create_form'), 'folder-edit-form' => array($this, 'folder_edit_form'), 'folder-mount-form' => array($this, 'folder_mount_form'), 'folder-auth-options'=> array($this, 'folder_auth_options'), 'file-search-form' => array($this, 'file_search_form'), 'file-rename-form' => array($this, 'file_rename_form'), 'file-create-form' => array($this, 'file_create_form'), 'file-edit-dialog' => array($this, 'file_edit_dialog'), 'file-session-dialog' => array($this, 'file_session_dialog'), 'filelist' => array($this, 'file_list'), 'sessionslist' => array($this, 'sessions_list'), 'filequotadisplay' => array($this, 'quota_display'), 'document-editors-dialog' => array($this, 'document_editors_dialog'), )); if ($this->rc->task != 'files') { // add dialog(s) content at the end of page body foreach ($templates as $template) { $this->rc->output->add_footer( $this->rc->output->parse('kolab_files.' . $template, false, false)); } } } } /** * Engine actions handler */ public function actions() { if ($this->rc->task == 'files' && $this->rc->action) { $action = $this->rc->action; } else if ($this->rc->task != 'files' && $_POST['act']) { $action = $_POST['act']; } else { $action = 'index'; } $method = 'action_' . str_replace('-', '_', $action); if (method_exists($this, $method)) { $this->plugin->add_texts('localization/'); $this->{$method}(); } } /** * Template object for folder creation form */ public function folder_create_form($attrib) { $attrib['name'] = 'folder-create-form'; if (empty($attrib['id'])) { $attrib['id'] = 'folder-create-form'; } $input_name = new html_inputfield(array('id' => 'folder-name', 'name' => 'name', 'size' => 30)); $select_parent = new html_select(array('id' => 'folder-parent', 'name' => 'parent')); $table = new html_table(array('cols' => 2, 'class' => 'propform')); $table->add('title', html::label('folder-name', rcube::Q($this->plugin->gettext('foldername')))); $table->add(null, $input_name->show()); $table->add('title', html::label('folder-parent', rcube::Q($this->plugin->gettext('folderinside')))); $table->add(null, $select_parent->show()); $out = $table->show(); // add form tag around text field if (empty($attrib['form'])) { $out = $this->rc->output->form_tag($attrib, $out); } $this->plugin->add_label('foldercreating', 'foldercreatenotice', 'create', 'foldercreate', 'cancel'); $this->rc->output->add_gui_object('folder-create-form', $attrib['id']); return $out; } /** * Template object for folder editing form */ public function folder_edit_form($attrib) { $attrib['name'] = 'folder-edit-form'; if (empty($attrib['id'])) { $attrib['id'] = 'folder-edit-form'; } $input_name = new html_inputfield(array('id' => 'folder-edit-name', 'name' => 'name', 'size' => 30)); $select_parent = new html_select(array('id' => 'folder-edit-parent', 'name' => 'parent')); $table = new html_table(array('cols' => 2, 'class' => 'propform')); $table->add('title', html::label('folder-name', rcube::Q($this->plugin->gettext('foldername')))); $table->add(null, $input_name->show()); $table->add('title', html::label('folder-parent', rcube::Q($this->plugin->gettext('folderinside')))); $table->add(null, $select_parent->show()); $out = $table->show(); // add form tag around text field if (empty($attrib['form'])) { $out = $this->rc->output->form_tag($attrib, $out); } $this->plugin->add_label('folderupdating', 'folderupdatenotice', 'save', 'folderedit', 'cancel'); $this->rc->output->add_gui_object('folder-edit-form', $attrib['id']); return $out; } /** * Template object for folder mounting form */ public function folder_mount_form($attrib) { $sources = $this->rc->output->get_env('external_sources'); if (empty($sources) || !is_array($sources)) { return ''; } $attrib['name'] = 'folder-mount-form'; if (empty($attrib['id'])) { $attrib['id'] = 'folder-mount-form'; } // build form content $table = new html_table(array('cols' => 2, 'class' => 'propform')); $input_name = new html_inputfield(array('id' => 'folder-mount-name', 'name' => 'name', 'size' => 30)); $input_driver = new html_radiobutton(array('name' => 'driver', 'size' => 30)); $table->add('title', html::label('folder-mount-name', rcube::Q($this->plugin->gettext('name')))); $table->add(null, $input_name->show()); foreach ($sources as $key => $source) { $id = 'source-' . $key; $form = new html_table(array('cols' => 2, 'class' => 'propform driverform')); foreach ((array) $source['form'] as $idx => $label) { $iid = $id . '-' . $idx; $type = stripos($idx, 'pass') !== false ? 'html_passwordfield' : 'html_inputfield'; $input = new $type(array('size' => 30)); $form->add('title', html::label($iid, rcube::Q($label))); $form->add(null, $input->show('', array( 'id' => $iid, 'name' => $key . '[' . $idx . ']' ))); } $row = $input_driver->show(null, array('value' => $key)) . html::img(array('src' => $source['image'], 'alt' => $key, 'title' => $source['name'])) . html::div(null, html::span('name', rcube::Q($source['name'])) . html::br() . html::span('description', rcube::Q($source['description'])) . $form->show() ); $table->add(array('id' => $id, 'colspan' => 2, 'class' => 'source'), $row); } $out = $table->show() . $this->folder_auth_options(array('suffix' => '-form')); // add form tag around text field if (empty($attrib['form'])) { $out = $this->rc->output->form_tag($attrib, $out); } $this->plugin->add_label('foldermounting', 'foldermountnotice', 'foldermount', 'save', 'cancel', 'folderauthtitle', 'authenticating' ); $this->rc->output->add_gui_object('folder-mount-form', $attrib['id']); return $out; } /** * Template object for folder authentication options */ public function folder_auth_options($attrib) { $checkbox = new html_checkbox(array( 'name' => 'store_passwords', 'value' => '1', 'id' => 'auth-pass-checkbox' . $attrib['suffix'], )); return html::div('auth-options', $checkbox->show(). ' ' . html::label('auth-pass-checkbox' . $attrib['suffix'], $this->plugin->gettext('storepasswords')) . html::span('description', $this->plugin->gettext('storepasswordsdesc')) ); } /** * Template object for file edit dialog/warnings */ public function file_edit_dialog($attrib) { $this->plugin->add_label('select', 'create', 'cancel', 'editfiledialog', 'editfilesessions', 'newsession', 'ownedsession', 'invitedsession', 'joinsession', 'editfilero', 'editfilerotitle', 'newsessionro' ); return '
    '; } /** * Template object for file session dialog */ public function file_session_dialog($attrib) { $this->plugin->add_label('join', 'open', 'close', 'request', 'cancel', 'sessiondialog', 'sessiondialogcontent'); return '
    '; } /** * Template object for dcument editors dialog */ public function document_editors_dialog($attrib) { $table = new html_table(array('cols' => 3, 'border' => 0, 'cellpadding' => 0, 'class' => 'records-table')); $table->add_header('username', $this->plugin->gettext('participant')); $table->add_header('status', $this->plugin->gettext('status')); $table->add_header('options', null); $input = new html_inputfield(array('name' => 'participant', 'id' => 'invitation-editor-name', 'size' => 30)); $textarea = new html_textarea(array('name' => 'comment', 'id' => 'invitation-comment', 'rows' => 4, 'cols' => 55, 'title' => $this->plugin->gettext('invitationtexttitle'))); $button = new html_inputfield(array('type' => 'button', 'class' => 'button', 'id' => 'invitation-editor-add', 'value' => $this->plugin->gettext('addparticipant'))); $this->plugin->add_label('manageeditors', 'statusorganizer'); // initialize attendees autocompletion $this->rc->autocomplete_init(); return '
    ' . $table->show() . html::div(null, html::div(null, $input->show() . " " . $button->show()) . html::p('attendees-commentbox', html::label(null, $this->plugin->gettext('invitationtextlabel') . $textarea->show()) ) ) . '
    '; } /** * Template object for file_rename form */ public function file_rename_form($attrib) { $attrib['name'] = 'file-rename-form'; if (empty($attrib['id'])) { $attrib['id'] = 'file-rename-form'; } $input_name = new html_inputfield(array('id' => 'file-rename-name', 'name' => 'name', 'size' => 50)); $table = new html_table(array('cols' => 2, 'class' => 'propform')); $table->add('title', html::label('file-rename-name', rcube::Q($this->plugin->gettext('filename')))); $table->add(null, $input_name->show()); $out = $table->show(); // add form tag around text field if (empty($attrib['form'])) { $out = $this->rc->output->form_tag($attrib, $out); } $this->plugin->add_label('save', 'cancel', 'fileupdating', 'renamefile'); $this->rc->output->add_gui_object('file-rename-form', $attrib['id']); return $out; } /** * Template object for file_create form */ public function file_create_form($attrib) { $attrib['name'] = 'file-create-form'; if (empty($attrib['id'])) { $attrib['id'] = 'file-create-form'; } $input_name = new html_inputfield(array('id' => 'file-create-name', 'name' => 'name', 'size' => 30)); $select_parent = new html_select(array('id' => 'file-create-parent', 'name' => 'parent')); $select_type = new html_select(array('id' => 'file-create-type', 'name' => 'type')); $table = new html_table(array('cols' => 2, 'class' => 'propform')); $types = array(); foreach ($this->get_mimetypes('edit') as $type => $mimetype) { $types[$type] = $mimetype['ext']; $select_type->add($mimetype['label'], $type); } $table->add('title', html::label('file-create-name', rcube::Q($this->plugin->gettext('filename')))); $table->add(null, $input_name->show()); $table->add('title', html::label('file-create-type', rcube::Q($this->plugin->gettext('type')))); $table->add(null, $select_type->show()); $table->add('title', html::label('folder-parent', rcube::Q($this->plugin->gettext('folderinside')))); $table->add(null, $select_parent->show()); $out = $table->show(); // add form tag around text field if (empty($attrib['form'])) { $out = $this->rc->output->form_tag($attrib, $out); } $this->plugin->add_label('create', 'cancel', 'filecreating', 'createfile', 'createandedit', 'copyfile', 'copyandedit'); $this->rc->output->add_gui_object('file-create-form', $attrib['id']); $this->rc->output->set_env('file_extensions', $types); return $out; } /** * Template object for file search form in "From cloud" dialog */ public function file_search_form($attrib) { $attrib['name'] = '_q'; if (empty($attrib['id'])) { $attrib['id'] = 'filesearchbox'; } if ($attrib['type'] == 'search' && !$this->rc->output->browser->khtml) { unset($attrib['type'], $attrib['results']); } $input_q = new html_inputfield($attrib); $out = $input_q->show(); // add some labels to client $this->rc->output->add_label('searching'); $this->rc->output->add_gui_object('filesearchbox', $attrib['id']); // add form tag around text field if (empty($attrib['form'])) { $out = $this->rc->output->form_tag(array( 'action' => '?_task=files', 'name' => "filesearchform", 'onsubmit' => rcmail_output::JS_OBJECT_NAME . ".command('files-search'); return false", ), $out); } return $out; } /** * Template object for files list */ public function file_list($attrib) { return $this->list_handler($attrib, 'files'); } /** * Template object for sessions list */ public function sessions_list($attrib) { return $this->list_handler($attrib, 'sessions'); } /** * Creates unified template object for files|sessions list */ protected function list_handler($attrib, $type = 'files') { $prefix = 'kolab_' . $type . '_'; $c_prefix = 'kolab_files' . ($type != 'files' ? '_' . $type : '') . '_'; // define list of cols to be displayed based on parameter or config if (empty($attrib['columns'])) { $list_cols = $this->rc->config->get($c_prefix . 'list_cols'); $dont_override = $this->rc->config->get('dont_override'); $a_show_cols = is_array($list_cols) ? $list_cols : array('name'); $this->rc->output->set_env($type . '_col_movable', !in_array($c_prefix . 'list_cols', (array)$dont_override)); } else { $columns = str_replace(array("'", '"'), '', $attrib['columns']); $a_show_cols = preg_split('/[\s,;]+/', $columns); } // make sure 'name' and 'options' column is present if (!in_array('name', $a_show_cols)) { array_unshift($a_show_cols, 'name'); } if (!in_array('options', $a_show_cols)) { array_unshift($a_show_cols, 'options'); } $attrib['columns'] = $a_show_cols; // save some variables for use in ajax list $_SESSION[$prefix . 'list_attrib'] = $attrib; // For list in dialog(s) remove all option-like columns if ($this->rc->task != 'files') { $a_show_cols = array_intersect($a_show_cols, $this->{$type . '_sort_cols'}); } // set default sort col/order to session if (!isset($_SESSION[$prefix . 'sort_col'])) $_SESSION[$prefix . 'sort_col'] = $this->rc->config->get($c_prefix . 'sort_col') ?: 'name'; if (!isset($_SESSION[$prefix . 'sort_order'])) $_SESSION[$prefix . 'sort_order'] = strtoupper($this->rc->config->get($c_prefix . 'sort_order') ?: 'asc'); // set client env $this->rc->output->add_gui_object($type . 'list', $attrib['id']); $this->rc->output->set_env($type . '_sort_col', $_SESSION[$prefix . 'sort_col']); $this->rc->output->set_env($type . '_sort_order', $_SESSION[$prefix . 'sort_order']); $this->rc->output->set_env($type . '_coltypes', $a_show_cols); $this->rc->output->include_script('list.js'); // attach css rules for mimetype icons if (!$this->filetypes_style) { $this->plugin->include_stylesheet($this->url . '/skins/default/images/mimetypes/style.css'); $this->filetypes_style = true; } $thead = ''; foreach ($this->list_head($attrib, $a_show_cols, $type) as $cell) { $thead .= html::tag('th', array('class' => $cell['className'], 'id' => $cell['id']), $cell['html']); } return html::tag('table', $attrib, html::tag('thead', null, html::tag('tr', null, $thead)) . html::tag('tbody', null, ''), array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary')); } /** * Creates for message list table */ protected function list_head($attrib, $a_show_cols, $type = 'files') { $prefix = 'kolab_' . $type . '_'; $c_prefix = 'kolab_files_' . ($type != 'files' ? $type : '') . '_'; $skin_path = $_SESSION['skin_path']; // check to see if we have some settings for sorting $sort_col = $_SESSION[$prefix . 'sort_col']; $sort_order = $_SESSION[$prefix . 'sort_order']; $dont_override = (array)$this->rc->config->get('dont_override'); $disabled_sort = in_array($c_prefix . 'sort_col', $dont_override); $disabled_order = in_array($c_prefix . 'sort_order', $dont_override); $this->rc->output->set_env($prefix . 'disabled_sort_col', $disabled_sort); $this->rc->output->set_env($prefix . 'disabled_sort_order', $disabled_order); // define sortable columns if ($disabled_sort) $a_sort_cols = $sort_col && !$disabled_order ? array($sort_col) : array(); else $a_sort_cols = $this->{$type . '_sort_cols'}; if (!empty($attrib['optionsmenuicon'])) { $onclick = 'return ' . rcmail_output::JS_OBJECT_NAME . ".command('menu-open', '{$type}listmenu', this, event)"; $inner = $this->rc->gettext('listoptions'); if (is_string($attrib['optionsmenuicon']) && $attrib['optionsmenuicon'] != 'true') { $inner = html::img(array('src' => $skin_path . $attrib['optionsmenuicon'], 'alt' => $this->rc->gettext('listoptions'))); } $list_menu = html::a(array( 'href' => '#list-options', 'onclick' => $onclick, 'class' => 'listmenu', 'id' => $type . 'listmenulink', 'title' => $this->rc->gettext('listoptions'), 'tabindex' => '0', ), $inner); } else { $list_menu = ''; } $cells = array(); foreach ($a_show_cols as $col) { // get column name switch ($col) { case 'options': $col_name = $list_menu; break; default: $col_name = rcube::Q($this->plugin->gettext($col)); } // make sort links if (in_array($col, $a_sort_cols)) { $col_name = html::a(array( 'href' => "#sort", 'onclick' => 'return ' . rcmail_output::JS_OBJECT_NAME . ".command('$type-sort','$col',this)", 'title' => $this->plugin->gettext('sortby') ), $col_name); } else if ($col_name[0] != '<') { $col_name = '' . $col_name . ''; } $sort_class = $col == $sort_col && !$disabled_order ? " sorted$sort_order" : ''; $class_name = $col.$sort_class; // put it all together $cells[] = array('className' => $class_name, 'id' => "rcm$col", 'html' => $col_name); } return $cells; } /** * Update files|sessions list object */ protected function list_update($prefs, $type = 'files') { $prefix = 'kolab_' . $type . '_list_'; $c_prefix = 'kolab_files' . ($type != 'files' ? '_' . $type : '') . '_list_'; $attrib = $_SESSION[$prefix . 'attrib']; if (!empty($prefs[$c_prefix . 'cols'])) { $attrib['columns'] = $prefs[$c_prefix . 'cols']; $_SESSION[$prefix . 'attrib'] = $attrib; } $a_show_cols = $attrib['columns']; $head = ''; foreach ($this->list_head($attrib, $a_show_cols, $type) as $cell) { $head .= html::tag('th', array('class' => $cell['className'], 'id' => $cell['id']), $cell['html']); } $head = html::tag('tr', null, $head); $this->rc->output->set_env($type . '_coltypes', $a_show_cols); $this->rc->output->command($type . '_list_update', $head); } /** * Template object for file info box */ public function file_info_box($attrib) { // print_r($this->file_data, true); $table = new html_table(array('cols' => 2, 'class' => $attrib['class'])); // file name $table->add('label', $this->plugin->gettext('name').':'); $table->add('data filename', $this->file_data['name']); // file type // @TODO: human-readable type name $table->add('label', $this->plugin->gettext('type').':'); $table->add('data filetype', $this->file_data['type']); // file size $table->add('label', $this->plugin->gettext('size').':'); $table->add('data filesize', $this->rc->show_bytes($this->file_data['size'])); // file modification time $table->add('label', $this->plugin->gettext('mtime').':'); $table->add('data filemtime', $this->file_data['mtime']); // @TODO: for images: width, height, color depth, etc. // @TODO: for text files: count of characters, lines, words return $table->show(); } /** * Template object for file preview frame */ public function file_preview_frame($attrib) { if (empty($attrib['id'])) { $attrib['id'] = 'filepreviewframe'; } if ($frame = $this->file_data['viewer']['frame']) { return $frame; } if ($href = $this->file_data['viewer']['href']) { // file href attribute must be an absolute URL (Bug #2063) if (!empty($href)) { if (!preg_match('|^https?://|', $href)) { $href = $this->url . '/api/' . $href; } } } else { $token = $this->get_api_token(); $href = $this->url . '/api/?method=file_get' . '&file=' . urlencode($this->file_data['filename']) . '&token=' . urlencode($token); } $this->rc->output->add_gui_object('preview_frame', $attrib['id']); $attrib['allowfullscreen'] = true; $attrib['src'] = $href; $attrib['onload'] = 'kolab_files_frame_load(this)'; // editor requires additional arguments via POST if (!empty($this->file_data['viewer']['post'])) { $attrib['src'] = 'program/resources/blank.gif'; $form_content = new html_hiddenfield(); $form_attrib = array( 'action' => $href, 'id' => $attrib['id'] . '-form', 'target' => $attrib['name'], 'method' => 'post', ); foreach ($this->file_data['viewer']['post'] as $name => $value) { $form_content->add(array('name' => $name, 'value' => $value)); } $form = html::tag('form', $form_attrib, $form_content->show()) . html::script(array(), "\$('#{$attrib['id']}-form').submit()"); } return html::iframe($attrib) . $form; } /** * Template object for quota display */ public function quota_display($attrib) { if (!$attrib['id']) { $attrib['id'] = 'rcmquotadisplay'; } $quota_type = !empty($attrib['display']) ? $attrib['display'] : 'text'; $this->rc->output->add_gui_object('quotadisplay', $attrib['id']); $this->rc->output->set_env('quota_type', $quota_type); // get quota $token = $this->get_api_token(); $request = $this->get_request(array('method' => 'quota'), $token); // send request to the API try { $response = $request->send(); $status = $response->getStatus(); $body = @json_decode($response->getBody(), true); if ($status == 200 && $body['status'] == 'OK') { $quota = $body['result']; } else { throw new Exception($body['reason'] ?: "Failed to get quota. Status: $status"); } } catch (Exception $e) { rcube::raise_error($e, true, false); $quota = array('total' => 0, 'percent' => 0); } $quota = rcube_output::json_serialize($quota); $this->rc->output->add_script(rcmail_output::JS_OBJECT_NAME . ".files_set_quota($quota);", 'docready'); return html::span($attrib, ''); } /** * Get API token for current user session, authenticate if needed */ public function get_api_token($configure = true) { $token = $_SESSION['kolab_files_token']; $time = $_SESSION['kolab_files_time']; if ($token && time() - $this->timeout < $time) { if (time() - $time <= $this->timeout / 2) { return $token; } } $request = $this->get_request(array('method' => 'ping'), $token); try { $url = $request->getUrl(); // Send ping request if ($token) { $url->setQueryVariables(array('method' => 'ping')); $request->setUrl($url); $response = $request->send(); $status = $response->getStatus(); if ($status == 200 && ($body = json_decode($response->getBody(), true))) { if ($body['status'] == 'OK') { $_SESSION['kolab_files_time'] = time(); return $token; } } } // Go with authenticate request $url->setQueryVariables(array('method' => 'authenticate', 'version' => self::API_VERSION)); $request->setUrl($url); $request->setAuth($this->rc->user->get_username(), $this->rc->decrypt($_SESSION['password'])); $response = $request->send(); $status = $response->getStatus(); if ($status == 200 && ($body = json_decode($response->getBody(), true))) { $token = $body['result']['token']; if ($token) { $_SESSION['kolab_files_token'] = $token; $_SESSION['kolab_files_time'] = time(); $_SESSION['kolab_files_caps'] = $body['result']['capabilities']; } } else { throw new Exception(sprintf("Authenticate error (Status: %d)", $status)); } // Configure session if ($configure && $token) { $this->configure($token); } } catch (Exception $e) { rcube::raise_error($e, true, false); } return $token; } /** * Initialize HTTP_Request object */ protected function get_request($get = null, $token = null) { $url = $this->url_srv . '/api/'; if (!$this->request) { $config = array( 'store_body' => true, 'follow_redirects' => true, ); $this->request = libkolab::http_request($url, 'GET', $config); } else { // cleanup try { $this->request->setBody(''); $this->request->setUrl($url); $this->request->setMethod(HTTP_Request2::METHOD_GET); } catch (Exception $e) { rcube::raise_error($e, true, true); } } if ($token) { $this->request->setHeader('X-Session-Token', $token); } if (!empty($get)) { $url = $this->request->getUrl(); $url->setQueryVariables($get); $this->request->setUrl($url); } // some HTTP server configurations require this header $this->request->setHeader('accept', "application/json,text/javascript,*/*"); // Localization $this->request->setHeader('accept-language', $_SESSION['language']); // set Referer which is used as an origin for cross-window // communication with document editor iframe $host = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST']; $this->request->setHeader('referer', $host); return $this->request; } /** * Configure chwala session */ public function configure($token = null, $prefs = array()) { if (!$token) { $token = $this->get_api_token(false); } try { // Configure session $query = array( 'method' => 'configure', 'timezone' => $prefs['timezone'] ?: $this->rc->config->get('timezone'), 'date_format' => $prefs['date_long'] ?: $this->rc->config->get('date_long', 'Y-m-d H:i'), ); $request = $this->get_request($query, $token); $response = $request->send(); $status = $response->getStatus(); if ($status != 200) { throw new Exception(sprintf("Failed to configure chwala session (Status: %d)", $status)); } } catch (Exception $e) { rcube::raise_error($e, true, false); } } /** * Handler for main files interface (Files task) */ protected function action_index() { $this->plugin->add_label( 'uploading', 'attaching', 'searching', 'uploadsizeerror', 'filedeleting', 'filedeletenotice', 'filedeleteconfirm', 'filemoving', 'filemovenotice', 'filemoveconfirm', 'filecopying', 'filecopynotice', 'fileskip', 'fileskipall', 'fileoverwrite', 'fileoverwriteall' ); $this->folder_list_env(); if ($this->rc->task == 'files') { $this->rc->output->set_env('folder', rcube_utils::get_input_value('folder', rcube_utils::INPUT_GET)); $this->rc->output->set_env('collection', rcube_utils::get_input_value('collection', rcube_utils::INPUT_GET)); } $this->rc->output->add_label('uploadprogress', 'GB', 'MB', 'KB', 'B'); $this->rc->output->set_pagetitle($this->plugin->gettext('files')); $this->rc->output->set_env('file_mimetypes', $this->get_mimetypes()); $this->rc->output->set_env('files_quota', $_SESSION['kolab_files_caps']['QUOTA']); $this->rc->output->set_env('files_max_upload', $_SESSION['kolab_files_caps']['MAX_UPLOAD']); $this->rc->output->set_env('files_progress_name', $_SESSION['kolab_files_caps']['PROGRESS_NAME']); $this->rc->output->set_env('files_progress_time', $_SESSION['kolab_files_caps']['PROGRESS_TIME']); $this->rc->output->send('kolab_files.files'); } /** * Handler for preferences save action */ protected function action_prefs() { $dont_override = (array)$this->rc->config->get('dont_override'); $prefs = array(); $type = rcube_utils::get_input_value('type', rcube_utils::INPUT_POST); $opts = array( 'kolab_files_sort_col' => true, 'kolab_files_sort_order' => true, 'kolab_files_list_cols' => false, ); foreach ($opts as $o => $sess) { if (isset($_POST[$o])) { $value = rcube_utils::get_input_value($o, rcube_utils::INPUT_POST); $session_key = $o; $config_key = $o; if ($type != 'files') { $config_key = str_replace('files', 'files_' . $type, $config_key); } if (in_array($config_key, $dont_override)) { continue; } if ($o == 'kolab_files_list_cols') { $update_list = true; } $prefs[$config_key] = $value; if ($sess) { $_SESSION[$session_key] = $prefs[$config_key]; } } } // save preference values if (!empty($prefs)) { $this->rc->user->save_prefs($prefs); } if (!empty($update_list)) { $this->list_update($prefs, $type); } $this->rc->output->send(); } /** * Handler for file open action */ protected function action_open() { $this->rc->output->set_env('file_mimetypes', $this->get_mimetypes()); $this->file_opener(intval($_GET['_viewer']) & ~4); } /** * Handler for file open action */ protected function action_edit() { $this->plugin->add_label('sessionterminating', 'unsavedchanges', 'documentinviting', 'documentcancelling', 'removeparticipant', 'sessionterminated', 'sessionterminatedtitle'); $this->file_opener(intval($_GET['_viewer'])); } /** * Handler for "save all attachments into cloud" action */ protected function action_save_file() { // $source = rcube_utils::get_input_value('source', rcube_utils::INPUT_POST); $uid = rcube_utils::get_input_value('uid', rcube_utils::INPUT_POST); $dest = rcube_utils::get_input_value('dest', rcube_utils::INPUT_POST); $id = rcube_utils::get_input_value('id', rcube_utils::INPUT_POST); $name = rcube_utils::get_input_value('name', rcube_utils::INPUT_POST); $temp_dir = unslashify($this->rc->config->get('temp_dir')); $message = new rcube_message($uid); $request = $this->get_request(); $url = $request->getUrl(); $files = array(); $errors = array(); $attachments = array(); $request->setMethod(HTTP_Request2::METHOD_POST); $request->setHeader('X-Session-Token', $this->get_api_token()); $url->setQueryVariables(array('method' => 'file_upload', 'folder' => $dest)); $request->setUrl($url); foreach ($message->attachments as $attach_prop) { if (empty($id) || $id == $attach_prop->mime_id) { $filename = strlen($name) ? $name : rcmail_attachment_name($attach_prop, true); $attachments[$filename] = $attach_prop; } } // @TODO: handle error // @TODO: implement file upload using file URI instead of body upload foreach ($attachments as $attach_name => $attach_prop) { $path = tempnam($temp_dir, 'rcmAttmnt'); // save attachment to file if ($fp = fopen($path, 'w+')) { $message->get_part_body($attach_prop->mime_id, false, 0, $fp); } else { $errors[] = true; rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__, 'message' => "Unable to save attachment into file $path"), true, false); continue; } fclose($fp); // send request to the API try { $request->setBody(''); $request->addUpload('file[]', $path, $attach_name, $attach_prop->mimetype); $response = $request->send(); $status = $response->getStatus(); $body = @json_decode($response->getBody(), true); if ($status == 200 && $body['status'] == 'OK') { $files[] = $attach_name; } else { throw new Exception($body['reason'] ?: "Failed to post file_upload. Status: $status"); } } catch (Exception $e) { unlink($path); $errors[] = $e->getMessage(); rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__, 'message' => $e->getMessage()), true, false); continue; } // clean up unlink($path); $request->setBody(''); } if ($count = count($files)) { $msg = $this->plugin->gettext(array('name' => 'saveallnotice', 'vars' => array('n' => $count))); $this->rc->output->show_message($msg, 'confirmation'); } if ($count = count($errors)) { $msg = $this->plugin->gettext(array('name' => 'saveallerror', 'vars' => array('n' => $count))); $this->rc->output->show_message($msg, 'error'); } // @TODO: update quota indicator, make this optional in case files aren't stored in IMAP $this->rc->output->send(); } /** * Handler for "add attachments from the cloud" action */ protected function action_attach_file() { $files = rcube_utils::get_input_value('files', rcube_utils::INPUT_POST); $uploadid = rcube_utils::get_input_value('uploadid', rcube_utils::INPUT_POST); $COMPOSE_ID = rcube_utils::get_input_value('id', rcube_utils::INPUT_POST); $COMPOSE = null; $errors = array(); $attachments = array(); if ($this->rc->task == 'mail') { if ($COMPOSE_ID && $_SESSION['compose_data_'.$COMPOSE_ID]) { $COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID]; } if (!$COMPOSE) { die("Invalid session var!"); } // attachment upload action if (!is_array($COMPOSE['attachments'])) { $COMPOSE['attachments'] = array(); } } // clear all stored output properties (like scripts and env vars) $this->rc->output->reset(); $temp_dir = unslashify($this->rc->config->get('temp_dir')); $request = $this->get_request(); $url = $request->getUrl(); // Use observer object to store HTTP response into a file require_once $this->plugin->home . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'kolab_files_observer.php'; $observer = new kolab_files_observer(); $request->setHeader('X-Session-Token', $this->get_api_token()); // download files from the API and attach them foreach ($files as $file) { // decode filename $file = urldecode($file); // get file information try { $url->setQueryVariables(array('method' => 'file_info', 'file' => $file)); $request->setUrl($url); $response = $request->send(); $status = $response->getStatus(); $body = @json_decode($response->getBody(), true); if ($status == 200 && $body['status'] == 'OK') { $file_params = $body['result']; } else { throw new Exception($body['reason'] ?: "Failed to get file_info. Status: $status"); } } catch (Exception $e) { $errors[] = $e->getMessage(); rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__, 'message' => $e->getMessage()), true, false); continue; } // set location of downloaded file $path = tempnam($temp_dir, 'rcmAttmnt'); $observer->set_file($path); // download file try { $url->setQueryVariables(array('method' => 'file_get', 'file' => $file)); $request->setUrl($url); $request->attach($observer); $response = $request->send(); $status = $response->getStatus(); $response->getBody(); // returns nothing $request->detach($observer); if ($status != 200 || !file_exists($path)) { throw new Exception("Unable to save file"); } } catch (Exception $e) { $errors[] = $e->getMessage(); rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__, 'message' => $e->getMessage()), true, false); continue; } $attachment = array( 'path' => $path, 'size' => $file_params['size'], 'name' => $file_params['name'], 'mimetype' => $file_params['type'], 'group' => $COMPOSE_ID, ); if ($this->rc->task != 'mail') { $attachments[] = $attachment; continue; } $attachment = $this->rc->plugins->exec_hook('attachment_save', $attachment); if ($attachment['status'] && !$attachment['abort']) { $id = $attachment['id']; // store new attachment in session unset($attachment['data'], $attachment['status'], $attachment['abort']); $COMPOSE['attachments'][$id] = $attachment; if (($icon = $COMPOSE['deleteicon']) && is_file($icon)) { $button = html::img(array( 'src' => $icon, 'alt' => $this->rc->gettext('delete') )); } else { $button = rcube::Q($this->rc->gettext('delete')); } $content = html::a(array( 'href' => "#delete", 'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%s', this)", rcmail_output::JS_OBJECT_NAME, $id), 'title' => $this->rc->gettext('delete'), 'class' => 'delete', ), $button); $content .= rcube::Q($attachment['name']); $this->rc->output->command('add2attachment_list', "rcmfile$id", array( 'html' => $content, 'name' => $attachment['name'], 'mimetype' => $attachment['mimetype'], 'classname' => rcube_utils::file2class($attachment['mimetype'], $attachment['name']), 'complete' => true), $uploadid); } else if ($attachment['error']) { $errors[] = $attachment['error']; } else { $errors[] = $this->plugin->gettext('attacherror'); } } if (!empty($errors)) { $this->rc->output->command('display_message', $this->plugin->gettext('attacherror'), 'error'); $this->rc->output->command('remove_from_attachment_list', $uploadid); } else if ($this->rc->task == 'calendar' || $this->rc->task == 'tasks') { // for uploads in events/tasks we'll use its standard upload handler, // for this we have to fake $_FILES and some other POST args foreach ($attachments as $attach) { $_FILES['_attachments']['tmp_name'][] = $attachment['path']; $_FILES['_attachments']['name'][] = $attachment['name']; $_FILES['_attachments']['size'][] = $attachment['size']; $_FILES['_attachments']['type'][] = $attachment['mimetype']; $_FILES['_attachments']['error'][] = null; } $_GET['_uploadid'] = $uploadid; $_GET['_id'] = $COMPOSE_ID; switch ($this->rc->task) { case 'tasks': libcalendaring::get_instance()->attachment_upload(tasklist::SESSION_KEY); break; case 'calendar': libcalendaring::get_instance()->attachment_upload(calendar::SESSION_KEY, 'cal-'); break; } } // send html page with JS calls as response $this->rc->output->command('auto_save_start', false); $this->rc->output->send(); } /** * Handler for file open/edit action */ protected function file_opener($viewer) { $file = rcube_utils::get_input_value('_file', rcube_utils::INPUT_GET); $session = rcube_utils::get_input_value('_session', rcube_utils::INPUT_GET); // get file info $token = $this->get_api_token(); $request = $this->get_request(array( 'method' => 'file_info', 'file' => $file, 'viewer' => $viewer, 'session' => $session, ), $token); // send request to the API try { $response = $request->send(); $status = $response->getStatus(); $body = @json_decode($response->getBody(), true); if ($status == 200 && $body['status'] == 'OK') { $this->file_data = $body['result']; } else { throw new Exception($body['reason'] ?: "Failed to get file_info. Status: $status"); } } catch (Exception $e) { rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__, 'message' => $e->getMessage()), true, true); } if ($file === null || $file === '') { $file = $this->file_data['file']; } $this->file_data['filename'] = $file; $this->plugin->add_label('filedeleteconfirm', 'filedeleting', 'filedeletenotice'); // register template objects for dialogs (and main interface) $this->rc->output->add_handlers(array( 'fileinfobox' => array($this, 'file_info_box'), 'filepreviewframe' => array($this, 'file_preview_frame'), )); $placeholder = $this->rc->output->asset_url('program/resources/blank.gif'); if ($this->file_data['viewer']['wopi']) { $editor_type = 'wopi'; $got_editor = ($viewer & 4); } else if ($this->file_data['viewer']['manticore']) { $editor_type = 'manticore'; $got_editor = ($viewer & 4); } // this one is for styling purpose $this->rc->output->set_env('extwin', true); $this->rc->output->set_env('file', $file); $this->rc->output->set_env('file_data', $this->file_data); $this->rc->output->set_env('editor_type', $editor_type); $this->rc->output->set_env('photo_placeholder', $placeholder); $this->rc->output->set_pagetitle(rcube::Q($file)); $this->rc->output->send('kolab_files.' . ($got_editor ? 'docedit' : 'filepreview')); } /** * Returns mimetypes supported by File API viewers */ protected function get_mimetypes($type = 'view') { $mimetypes = array(); // send request to the API try { if ($this->mimetypes === null) { $this->mimetypes = false; $token = $this->get_api_token(); $request = $this->get_request(array('method' => 'mimetypes'), $token); $response = $request->send(); $status = $response->getStatus(); $body = @json_decode($response->getBody(), true); if ($status == 200 && $body['status'] == 'OK') { $this->mimetypes = $body['result']; } else { throw new Exception($body['reason'] ?: "Failed to get mimetypes. Status: $status"); } } if (is_array($this->mimetypes)) { if (array_key_exists($type, $this->mimetypes)) { $mimetypes = $this->mimetypes[$type]; } // fallback to static definition if old Chwala is used else if ($type == 'edit') { $mimetypes = array( 'text/plain' => 'txt', 'text/html' => 'html', ); if (!empty($_SESSION['kolab_files_caps']['MANTICORE'])) { $mimetypes = array_merge(array('application/vnd.oasis.opendocument.text' => 'odt'), $mimetypes); } foreach (array_keys($mimetypes) as $type) { list ($app, $label) = explode('/', $type); $label = preg_replace('/[^a-z]/', '', $label); $mimetypes[$type] = array( 'ext' => $mimetypes[$type], 'label' => $this->plugin->gettext('type.' . $label), ); } } else { $mimetypes = $this->mimetypes; } } } catch (Exception $e) { rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__, 'message' => $e->getMessage()), true, false); } return $mimetypes; } /** * Get list of available external storage drivers */ protected function get_external_storage_drivers() { // first get configured sources from Chwala $token = $this->get_api_token(); $request = $this->get_request(array('method' => 'folder_types'), $token); // send request to the API try { $response = $request->send(); $status = $response->getStatus(); $body = @json_decode($response->getBody(), true); if ($status == 200 && $body['status'] == 'OK') { $sources = $body['result']; } else { throw new Exception($body['reason'] ?: "Failed to get folder_types. Status: $status"); } } catch (Exception $e) { rcube::raise_error($e, true, false); return; } $this->rc->output->set_env('external_sources', $sources); } /** * Registers translation labels for folder lists in UI */ protected function folder_list_env() { // folder list and actions $this->plugin->add_label( 'folderdeleting', 'folderdeleteconfirm', 'folderdeletenotice', 'collection_audio', 'collection_video', 'collection_image', 'collection_document', 'additionalfolders', 'listpermanent' ); $this->rc->output->add_label('foldersubscribing', 'foldersubscribed', 'folderunsubscribing', 'folderunsubscribed', 'searching' ); } } diff --git a/plugins/kolab_files/skins/larry/templates/compose_plugin.html b/plugins/kolab_files/skins/larry/templates/compose_plugin.html index 4e249edd..a823a2a1 100644 --- a/plugins/kolab_files/skins/larry/templates/compose_plugin.html +++ b/plugins/kolab_files/skins/larry/templates/compose_plugin.html @@ -1,51 +1,51 @@ diff --git a/plugins/kolab_files/skins/larry/templates/files.html b/plugins/kolab_files/skins/larry/templates/files.html index db5a222c..05772770 100644 --- a/plugins/kolab_files/skins/larry/templates/files.html +++ b/plugins/kolab_files/skins/larry/templates/files.html @@ -1,224 +1,224 @@ <roundcube:object name="pagetitle" />

    diff --git a/plugins/kolab_files/skins/larry/templates/message_plugin.html b/plugins/kolab_files/skins/larry/templates/message_plugin.html index df8452b0..0be224b5 100644 --- a/plugins/kolab_files/skins/larry/templates/message_plugin.html +++ b/plugins/kolab_files/skins/larry/templates/message_plugin.html @@ -1,35 +1,35 @@ diff --git a/plugins/kolab_notes/kolab_notes_ui.php b/plugins/kolab_notes/kolab_notes_ui.php index f180f50a..bf790237 100644 --- a/plugins/kolab_notes/kolab_notes_ui.php +++ b/plugins/kolab_notes/kolab_notes_ui.php @@ -1,440 +1,441 @@ plugin = $plugin; $this->rc = $plugin->rc; } /** * Calendar UI initialization and requests handlers */ public function init() { if ($this->ready) // already done return; // add taskbar button $this->plugin->add_button(array( - 'command' => 'notes', - 'class' => 'button-notes', - 'classsel' => 'button-notes button-selected', + 'command' => 'notes', + 'class' => 'button-notes', + 'classsel' => 'button-notes button-selected', 'innerclass' => 'button-inner', - 'label' => 'kolab_notes.navtitle', + 'label' => 'kolab_notes.navtitle', + 'type' => 'link' ), 'taskbar'); $this->plugin->include_stylesheet($this->plugin->local_skin_path() . '/notes.css'); $this->plugin->register_action('print', array($this, 'print_template')); $this->plugin->register_action('folder-acl', array($this, 'folder_acl')); $this->ready = true; } /** * Register handler methods for the template engine */ public function init_templates() { $this->plugin->register_handler('plugin.tagslist', array($this, 'tagslist')); $this->plugin->register_handler('plugin.notebooks', array($this, 'folders')); #$this->plugin->register_handler('plugin.folders_select', array($this, 'folders_select')); $this->plugin->register_handler('plugin.searchform', array($this->rc->output, 'search_form')); $this->plugin->register_handler('plugin.listing', array($this, 'listing')); $this->plugin->register_handler('plugin.editform', array($this, 'editform')); $this->plugin->register_handler('plugin.notetitle', array($this, 'notetitle')); $this->plugin->register_handler('plugin.detailview', array($this, 'detailview')); $this->plugin->register_handler('plugin.attachments_list', array($this, 'attachments_list')); $this->plugin->register_handler('plugin.object_changelog_table', array('libkolab', 'object_changelog_table')); $this->rc->output->include_script('list.js'); $this->rc->output->include_script('treelist.js'); $this->plugin->include_script('notes.js'); jqueryui::tagedit(); // include kolab folderlist widget if available if (in_array('libkolab', $this->plugin->api->loaded_plugins())) { $this->plugin->api->include_script('libkolab/js/folderlist.js'); $this->plugin->api->include_script('libkolab/js/audittrail.js'); } // load config options and user prefs relevant for the UI $settings = array( 'sort_col' => $this->rc->config->get('kolab_notes_sort_col', 'changed'), 'print_template' => $this->rc->url('print'), ); if ($list = rcube_utils::get_input_value('_list', rcube_utils::INPUT_GPC)) { $settings['selected_list'] = $list; } if ($uid = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC)) { $settings['selected_uid'] = $uid; } $lang_codes = array($_SESSION['language']); $assets_dir = $this->rc->config->get('assets_dir') ?: INSTALL_PATH; if ($pos = strpos($_SESSION['language'], '_')) { $lang_codes[] = substr($_SESSION['language'], 0, $pos); } foreach ($lang_codes as $code) { if (file_exists("$assets_dir/program/js/tinymce/langs/$code.js")) { $lang = $code; break; } } if (empty($lang)) { $lang = 'en'; } $settings['editor'] = array( 'lang' => $lang, 'spellcheck' => intval($this->rc->config->get('enable_spellcheck')), 'spelldict' => intval($this->rc->config->get('spellcheck_dictionary')) ); $this->rc->output->set_env('kolab_notes_settings', $settings); $this->rc->output->add_label('save','cancel','delete','close'); } public function folders($attrib) { $attrib += array('id' => 'rcmkolabnotebooks'); if ($attrib['type'] == 'select') { $attrib['is_escaped'] = true; $select = new html_select($attrib); } $tree = $attrib['type'] != 'select' ? true : null; $lists = $this->plugin->get_lists($tree); $jsenv = array(); if (is_object($tree)) { $html = $this->folder_tree_html($tree, $lists, $jsenv, $attrib); } else { $html = ''; foreach ($lists as $prop) { $id = $prop['id']; if (!$prop['virtual']) { unset($prop['user_id']); $jsenv[$id] = $prop; } if ($attrib['type'] == 'select') { if ($prop['editable'] || strpos($prop['rights'], 'i') !== false) { $select->add($prop['name'], $prop['id']); } } else { $html .= html::tag('li', array('id' => 'rcmliknb' . rcube_utils::html_identifier($id), 'class' => $prop['group']), $this->folder_list_item($id, $prop, $jsenv) ); } } } $this->rc->output->set_env('kolab_notebooks', $jsenv); $this->rc->output->add_gui_object('notebooks', $attrib['id']); return $attrib['type'] == 'select' ? $select->show() : html::tag('ul', $attrib, $html, html::$common_attrib); } /** * Return html for a structured list
      for the folder tree */ public function folder_tree_html($node, $data, &$jsenv, $attrib) { $out = ''; foreach ($node->children as $folder) { $id = $folder->id; $prop = $data[$id]; $is_collapsed = false; // TODO: determine this somehow? $content = $this->folder_list_item($id, $prop, $jsenv); if (!empty($folder->children)) { $content .= html::tag('ul', array('style' => ($is_collapsed ? "display:none;" : null)), $this->folder_tree_html($folder, $data, $jsenv, $attrib)); } if (strlen($content)) { $out .= html::tag('li', array( 'id' => 'rcmliknb' . rcube_utils::html_identifier($id), 'class' => $prop['group'] . ($prop['virtual'] ? ' virtual' : ''), ), $content); } } return $out; } /** * Helper method to build a tasklist item (HTML content and js data) */ public function folder_list_item($id, $prop, &$jsenv, $checkbox = false) { if (!$prop['virtual']) { unset($prop['user_id']); $jsenv[$id] = $prop; } $classes = array('folder'); if ($prop['virtual']) { $classes[] = 'virtual'; } else if (!$prop['editable']) { $classes[] = 'readonly'; } if ($prop['subscribed']) { $classes[] = 'subscribed'; } if ($prop['class']) { $classes[] = $prop['class']; } $title = $prop['title'] ?: ($prop['name'] != $prop['listname'] || strlen($prop['name']) > 25 ? html_entity_decode($prop['name'], ENT_COMPAT, RCUBE_CHARSET) : ''); $label_id = 'nl:' . $id; $attr = $prop['virtual'] ? array('tabindex' => '0') : array('href' => $this->rc->url(array('_list' => $id))); return html::div(join(' ', $classes), html::a($attr + array('class' => 'listname', 'title' => $title, 'id' => $label_id), $prop['listname'] ?: $prop['name']) . ($prop['virtual'] ? '' : ($checkbox ? html::tag('input', array('type' => 'checkbox', 'name' => '_list[]', 'value' => $id, 'checked' => $prop['active'], 'aria-labelledby' => $label_id)) : '' ) . html::span('handle', '') . html::span('actions', (!$prop['default'] ? html::a(array('href' => '#', 'class' => 'remove', 'title' => $this->plugin->gettext('removelist')), ' ') : '' ) . (isset($prop['subscribed']) ? html::a(array('href' => '#', 'class' => 'subscribed', 'title' => $this->plugin->gettext('foldersubscribe'), 'role' => 'checkbox', 'aria-checked' => $prop['subscribed'] ? 'true' : 'false'), ' ') : '' ) ) ) ); return ''; } public function listing($attrib) { $attrib += array('id' => 'rcmkolabnoteslist'); $this->rc->output->add_gui_object('noteslist', $attrib['id']); return html::tag('ul', $attrib, '', html::$common_attrib); } public function tagslist($attrib) { $attrib += array('id' => 'rcmkolabnotestagslist'); $this->rc->output->add_gui_object('notestagslist', $attrib['id']); return html::tag('ul', $attrib, '', html::$common_attrib); } public function editform($attrib) { $attrib += array('action' => '#', 'id' => 'rcmkolabnoteseditform'); $this->rc->output->add_gui_object('noteseditform', $attrib['id']); $this->rc->output->include_script('tinymce/tinymce.min.js'); $textarea = new html_textarea(array('name' => 'content', 'id' => 'notecontent', 'cols' => 60, 'rows' => 20, 'tabindex' => 0)); return html::tag('form', $attrib, $textarea->show(), array_merge(html::$common_attrib, array('action'))); } public function detailview($attrib) { $attrib += array('id' => 'rcmkolabnotesdetailview'); $this->rc->output->add_gui_object('notesdetailview', $attrib['id']); return html::div($attrib, ''); } public function notetitle($attrib) { $attrib += array('id' => 'rcmkolabnotestitle'); $this->rc->output->add_gui_object('noteviewtitle', $attrib['id']); $summary = new html_inputfield(array('name' => 'summary', 'class' => 'notetitle inline-edit', 'size' => 60, 'tabindex' => 0)); $html = $summary->show(); $html .= html::div(array('class' => 'tagline tagedit', 'style' => 'display:none'), ' '); $html .= html::div(array('class' => 'dates', 'style' => 'display:none'), html::label(array(), $this->plugin->gettext('created')) . html::span('notecreated', '') . html::label(array(), $this->plugin->gettext('changed')) . html::span('notechanged', '') ); return html::div($attrib, $html); } public function attachments_list($attrib) { $attrib += array('id' => 'rcmkolabnotesattachmentslist'); $this->rc->output->add_gui_object('notesattachmentslist', $attrib['id']); return html::tag('ul', $attrib, '', html::$common_attrib); } /** * Render edit for notes lists (folders) */ public function list_editform($action, $list, $folder) { if (is_object($folder)) { $folder_name = $folder->name; // UTF7 } else { $folder_name = ''; } $hidden_fields[] = array('name' => 'oldname', 'value' => $folder_name); $storage = $this->rc->get_storage(); $delim = $storage->get_hierarchy_delimiter(); $form = array(); if (strlen($folder_name)) { $options = $storage->folder_info($folder_name); $path_imap = explode($delim, $folder_name); array_pop($path_imap); // pop off name part $path_imap = implode($path_imap, $delim); } else { $path_imap = ''; $options = array(); } // General tab $form['properties'] = array( 'name' => $this->rc->gettext('properties'), 'fields' => array(), ); // folder name (default field) $input_name = new html_inputfield(array('name' => 'name', 'id' => 'noteslist-name', 'size' => 20)); $form['properties']['fields']['name'] = array( 'label' => $this->plugin->gettext('listname'), 'value' => $input_name->show($list['editname'], array('disabled' => ($options['norename'] || $options['protected']))), 'id' => 'noteslist-name', ); // prevent user from moving folder if (!empty($options) && ($options['norename'] || $options['protected'])) { $hidden_fields[] = array('name' => 'parent', 'value' => $path_imap); } else { $select = kolab_storage::folder_selector('note', array('name' => 'parent', 'id' => 'parent-folder'), $folder_name); $form['properties']['fields']['path'] = array( 'label' => $this->plugin->gettext('parentfolder'), 'value' => $select->show(strlen($folder_name) ? $path_imap : ''), 'id' => 'parent-folder', ); } // add folder ACL tab if ($action != 'form-new') { $form['sharing'] = array( 'name' => rcube::Q($this->plugin->gettext('tabsharing')), 'content' => html::tag('iframe', array( 'src' => $this->rc->url(array('_action' => 'folder-acl', '_folder' => $folder_name, 'framed' => 1)), 'width' => '100%', 'height' => 280, 'border' => 0, 'style' => 'border:0'), '') ); } $form_html = ''; if (is_array($hidden_fields)) { foreach ($hidden_fields as $field) { $hiddenfield = new html_hiddenfield($field); $form_html .= $hiddenfield->show() . "\n"; } } // create form output foreach ($form as $tab) { if (is_array($tab['fields']) && empty($tab['content'])) { $table = new html_table(array('cols' => 2)); foreach ($tab['fields'] as $col => $colprop) { $label = !empty($colprop['label']) ? $colprop['label'] : $this->plugin->gettext($col); $table->add('title', html::label($colprop['id'], rcube::Q($label))); $table->add(null, $colprop['value']); } $content = $table->show(); } else { $content = $tab['content']; } if (!empty($content)) { $form_html .= html::tag('fieldset', null, html::tag('legend', null, rcube::Q($tab['name'])) . $content) . "\n"; } } return html::tag('form', array('action' => "#", 'method' => "post", 'id' => "noteslistpropform"), $form_html); } /** * Handler to render ACL form for a notes folder */ public function folder_acl() { $this->plugin->require_plugin('acl'); $this->rc->output->add_handler('folderacl', array($this, 'folder_acl_form')); $this->rc->output->send('kolab_notes.kolabacl'); } /** * Handler for ACL form template object */ public function folder_acl_form() { $folder = rcube_utils::get_input_value('_folder', rcube_utils::INPUT_GPC); if (strlen($folder)) { $storage = $this->rc->get_storage(); $options = $storage->folder_info($folder); // get sharing UI from acl plugin $acl = $this->rc->plugins->exec_hook('folder_form', array('form' => array(), 'options' => $options, 'name' => $folder)); } return $acl['form']['sharing']['content'] ?: html::div('hint', $this->plugin->gettext('aclnorights')); } /** * Render the template for printing with placeholders */ public function print_template() { header('Content-Type: text/html; charset=' . RCUBE_CHARSET); $this->rc->output->reset(true); echo $this->rc->output->parse('kolab_notes.print', false, false); exit; } } diff --git a/plugins/kolab_notes/skins/larry/templates/notes.html b/plugins/kolab_notes/skins/larry/templates/notes.html index e591ea70..4a6fdccc 100644 --- a/plugins/kolab_notes/skins/larry/templates/notes.html +++ b/plugins/kolab_notes/skins/larry/templates/notes.html @@ -1,232 +1,232 @@ <roundcube:object name="pagetitle" />

      diff --git a/plugins/kolab_tags/skins/larry/templates/ui.html b/plugins/kolab_tags/skins/larry/templates/ui.html index 1df50730..0b8d66a7 100644 --- a/plugins/kolab_tags/skins/larry/templates/ui.html +++ b/plugins/kolab_tags/skins/larry/templates/ui.html @@ -1,45 +1,45 @@ diff --git a/plugins/tasklist/skins/larry/templates/mainview.html b/plugins/tasklist/skins/larry/templates/mainview.html index 7f84cec6..ca40df13 100644 --- a/plugins/tasklist/skins/larry/templates/mainview.html +++ b/plugins/tasklist/skins/larry/templates/mainview.html @@ -1,378 +1,378 @@ <roundcube:object name="pagetitle" />

      \ No newline at end of file diff --git a/plugins/tasklist/tasklist_ui.php b/plugins/tasklist/tasklist_ui.php index 502bb2f0..de23a87c 100644 --- a/plugins/tasklist/tasklist_ui.php +++ b/plugins/tasklist/tasklist_ui.php @@ -1,634 +1,635 @@ * * Copyright (C) 2012-2015, Kolab Systems AG * * 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 . */ class tasklist_ui { private $rc; private $plugin; private $ready = false; private $gui_objects = array(); function __construct($plugin) { $this->plugin = $plugin; $this->rc = $plugin->rc; } /** * Calendar UI initialization and requests handlers */ public function init() { if ($this->ready) { return; } // add taskbar button $this->plugin->add_button(array( 'command' => 'tasks', 'class' => 'button-tasklist', 'classsel' => 'button-tasklist button-selected', 'innerclass' => 'button-inner', 'label' => 'tasklist.navtitle', + 'type' => 'link' ), 'taskbar'); $this->plugin->include_stylesheet($this->plugin->local_skin_path() . '/tasklist.css'); if ($this->rc->task == 'mail' || $this->rc->task == 'tasks') { jqueryui::tagedit(); $this->plugin->include_script('tasklist_base.js'); // copy config to client $this->rc->output->set_env('tasklist_settings', $this->load_settings()); // initialize attendees autocompletion $this->rc->autocomplete_init(); } $this->ready = true; } /** * */ function load_settings() { $settings = array(); $settings['invite_shared'] = (int)$this->rc->config->get('calendar_allow_invite_shared', 0); $settings['itip_notify'] = (int)$this->rc->config->get('calendar_itip_send_option', 3); $settings['sort_col'] = $this->rc->config->get('tasklist_sort_col', ''); $settings['sort_order'] = $this->rc->config->get('tasklist_sort_order', 'asc'); // get user identity to create default attendee foreach ($this->rc->user->list_emails() as $rec) { if (!$identity) $identity = $rec; $identity['emails'][] = $rec['email']; $settings['identities'][$rec['identity_id']] = $rec['email']; } $identity['emails'][] = $this->rc->user->get_username(); $settings['identity'] = array( 'name' => $identity['name'], 'email' => strtolower($identity['email']), 'emails' => ';' . strtolower(join(';', $identity['emails'])) ); if ($list = rcube_utils::get_input_value('_list', rcube_utils::INPUT_GPC)) { $settings['selected_list'] = $list; } if ($list && ($id = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC))) { $settings['selected_id'] = $id; // check if the referenced task is completed $task = $this->plugin->driver->get_task(array('id' => $id, 'list' => $list)); if ($task && $this->plugin->driver->is_complete($task)) { $settings['selected_filter'] = 'complete'; } } else if ($filter = rcube_utils::get_input_value('_filter', rcube_utils::INPUT_GPC)) { $settings['selected_filter'] = $filter; } return $settings; } /** * Render a HTML select box for user identity selection */ function identity_select($attrib = array()) { $attrib['name'] = 'identity'; $select = new html_select($attrib); $identities = $this->rc->user->list_emails(); foreach ($identities as $ident) { $select->add(format_email_recipient($ident['email'], $ident['name']), $ident['identity_id']); } return $select->show(null); } /** * Register handler methods for the template engine */ public function init_templates() { $this->plugin->register_handler('plugin.tasklists', array($this, 'tasklists')); $this->plugin->register_handler('plugin.tasklist_select', array($this, 'tasklist_select')); $this->plugin->register_handler('plugin.status_select', array($this, 'status_select')); $this->plugin->register_handler('plugin.searchform', array($this->rc->output, 'search_form')); $this->plugin->register_handler('plugin.quickaddform', array($this, 'quickadd_form')); $this->plugin->register_handler('plugin.tasks', array($this, 'tasks_resultview')); $this->plugin->register_handler('plugin.tagslist', array($this, 'tagslist')); $this->plugin->register_handler('plugin.tags_editline', array($this, 'tags_editline')); $this->plugin->register_handler('plugin.alarm_select', array($this, 'alarm_select')); $this->plugin->register_handler('plugin.recurrence_form', array($this->plugin->lib, 'recurrence_form')); $this->plugin->register_handler('plugin.attachments_form', array($this, 'attachments_form')); $this->plugin->register_handler('plugin.attachments_list', array($this, 'attachments_list')); $this->plugin->register_handler('plugin.filedroparea', array($this, 'file_drop_area')); $this->plugin->register_handler('plugin.attendees_list', array($this, 'attendees_list')); $this->plugin->register_handler('plugin.attendees_form', array($this, 'attendees_form')); $this->plugin->register_handler('plugin.identity_select', array($this, 'identity_select')); $this->plugin->register_handler('plugin.edit_attendees_notify', array($this, 'edit_attendees_notify')); $this->plugin->register_handler('plugin.task_rsvp_buttons', array($this->plugin->itip, 'itip_rsvp_buttons')); $this->plugin->register_handler('plugin.object_changelog_table', array('libkolab', 'object_changelog_table')); $this->plugin->register_handler('plugin.tasks_export_form', array($this, 'tasks_export_form')); $this->plugin->register_handler('plugin.tasks_import_form', array($this, 'tasks_import_form')); jqueryui::tagedit(); $this->plugin->include_script('tasklist.js'); $this->rc->output->include_script('treelist.js'); // include kolab folderlist widget if available if (in_array('libkolab', $this->plugin->api->loaded_plugins())) { $this->plugin->api->include_script('libkolab/js/folderlist.js'); $this->plugin->api->include_script('libkolab/js/audittrail.js'); } } /** * */ public function tasklists($attrib = array()) { $tree = true; $jsenv = array(); $lists = $this->plugin->driver->get_lists(0, $tree); if (empty($attrib['id'])) { $attrib['id'] = 'rcmtasklistslist'; } // walk folder tree if (is_object($tree)) { $html = $this->list_tree_html($tree, $lists, $jsenv, $attrib); } else { // fall-back to flat folder listing $attrib['class'] .= ' flat'; $html = ''; foreach ((array)$lists as $id => $prop) { if ($attrib['activeonly'] && !$prop['active']) continue; $html .= html::tag('li', array( 'id' => 'rcmlitasklist' . rcube_utils::html_identifier($id), 'class' => $prop['group'], ), $this->tasklist_list_item($id, $prop, $jsenv, $attrib['activeonly']) ); } } $this->rc->output->set_env('source', rcube_utils::get_input_value('source', rcube_utils::INPUT_GET)); $this->rc->output->set_env('tasklists', $jsenv); $this->register_gui_object('tasklistslist', $attrib['id']); return html::tag('ul', $attrib, $html, html::$common_attrib); } /** * Return html for a structured list
        for the folder tree */ public function list_tree_html($node, $data, &$jsenv, $attrib) { $out = ''; foreach ($node->children as $folder) { $id = $folder->id; $prop = $data[$id]; $is_collapsed = false; // TODO: determine this somehow? $content = $this->tasklist_list_item($id, $prop, $jsenv, $attrib['activeonly']); if (!empty($folder->children)) { $content .= html::tag('ul', array('style' => ($is_collapsed ? "display:none;" : null)), $this->list_tree_html($folder, $data, $jsenv, $attrib)); } if (strlen($content)) { $out .= html::tag('li', array( 'id' => 'rcmlitasklist' . rcube_utils::html_identifier($id), 'class' => $prop['group'] . ($prop['virtual'] ? ' virtual' : ''), ), $content); } } return $out; } /** * Helper method to build a tasklist item (HTML content and js data) */ public function tasklist_list_item($id, $prop, &$jsenv, $activeonly = false) { // enrich list properties with settings from the driver if (!$prop['virtual']) { unset($prop['user_id']); $prop['alarms'] = $this->plugin->driver->alarms; $prop['undelete'] = $this->plugin->driver->undelete; $prop['sortable'] = $this->plugin->driver->sortable; $prop['attachments'] = $this->plugin->driver->attachments; $prop['attendees'] = $this->plugin->driver->attendees; $prop['caldavurl'] = $this->plugin->driver->tasklist_caldav_url($prop); $jsenv[$id] = $prop; } $classes = array('tasklist'); $title = $prop['title'] ?: ($prop['name'] != $prop['listname'] || strlen($prop['name']) > 25 ? html_entity_decode($prop['name'], ENT_COMPAT, RCUBE_CHARSET) : ''); if ($prop['virtual']) $classes[] = 'virtual'; else if (!$prop['editable']) $classes[] = 'readonly'; if ($prop['subscribed']) $classes[] = 'subscribed'; if ($prop['class']) $classes[] = $prop['class']; if (!$activeonly || $prop['active']) { $label_id = 'tl:' . $id; return html::div(join(' ', $classes), html::span(array('class' => 'listname', 'title' => $title, 'id' => $label_id), $prop['listname'] ?: $prop['name']) . ($prop['virtual'] ? '' : html::tag('input', array('type' => 'checkbox', 'name' => '_list[]', 'value' => $id, 'checked' => $prop['active'], 'aria-labelledby' => $label_id)) . html::span('actions', ($prop['removable'] ? html::a(array('href' => '#', 'class' => 'remove', 'title' => $this->plugin->gettext('removelist')), ' ') : '') . html::a(array('href' => '#', 'class' => 'quickview', 'title' => $this->plugin->gettext('focusview'), 'role' => 'checkbox', 'aria-checked' => 'false'), ' ') . (isset($prop['subscribed']) ? html::a(array('href' => '#', 'class' => 'subscribed', 'title' => $this->plugin->gettext('tasklistsubscribe'), 'role' => 'checkbox', 'aria-checked' => $prop['subscribed'] ? 'true' : 'false'), ' ') : '') ) ) ); } return ''; } /** * Render HTML form for task status selector */ function status_select($attrib = array()) { $attrib['name'] = 'status'; $select = new html_select($attrib); $select->add('---', ''); $select->add($this->plugin->gettext('status-needs-action'), 'NEEDS-ACTION'); $select->add($this->plugin->gettext('status-in-process'), 'IN-PROCESS'); $select->add($this->plugin->gettext('status-completed'), 'COMPLETED'); $select->add($this->plugin->gettext('status-cancelled'), 'CANCELLED'); return $select->show(null); } /** * Render a HTML select box for list selection */ function tasklist_select($attrib = array()) { if (empty($attrib['name'])) { $attrib['name'] = 'list'; } $attrib['is_escaped'] = true; $select = new html_select($attrib); $default = null; foreach ((array) $attrib['extra'] as $id => $name) { $select->add($name, $id); } foreach ((array)$this->plugin->driver->get_lists() as $id => $prop) { if ($prop['editable'] || strpos($prop['rights'], 'i') !== false) { $select->add($prop['name'], $id); if (!$default || $prop['default']) $default = $id; } } return $select->show($default); } function tasklist_editform($action, $list = array()) { $fields = array( 'name' => array( 'id' => 'taskedit-tasklistame', 'label' => $this->plugin->gettext('listname'), 'value' => html::tag('input', array('id' => 'taskedit-tasklistame', 'name' => 'name', 'type' => 'text', 'class' => 'text', 'size' => 40)), ), /* 'color' => array( 'id' => 'taskedit-color', 'label' => $this->plugin->gettext('color'), 'value' => html::tag('input', array('id' => 'taskedit-color', 'name' => 'color', 'type' => 'text', 'class' => 'text colorpicker', 'size' => 6)), ), */ 'showalarms' => array( 'id' => 'taskedit-showalarms', 'label' => $this->plugin->gettext('showalarms'), 'value' => html::tag('input', array('id' => 'taskedit-showalarms', 'name' => 'color', 'type' => 'checkbox')), ), ); return html::tag('form', array('action' => "#", 'method' => "post", 'id' => 'tasklisteditform'), $this->plugin->driver->tasklist_edit_form($action, $list, $fields) ); } /** * Render HTML form for alarm configuration */ function alarm_select($attrib = array()) { $attrib['_type'] = 'task'; return $this->plugin->lib->alarm_select($attrib, $this->plugin->driver->alarm_types, $this->plugin->driver->alarm_absolute); } /** * */ function quickadd_form($attrib) { $attrib += array('action' => $this->rc->url('add'), 'method' => 'post', 'id' => 'quickaddform'); $label = html::label(array('for' => 'quickaddinput', 'class' => 'voice'), $this->plugin->gettext('quickaddinput')); $input = new html_inputfield(array('name' => 'text', 'id' => 'quickaddinput')); $button = html::tag('input', array('type' => 'submit', 'value' => '+', 'title' => $this->plugin->gettext('createtask'), 'class' => 'button mainaction')); $this->register_gui_object('quickaddform', $attrib['id']); return html::tag('form', $attrib, $label . $input->show() . $button); } /** * The result view */ function tasks_resultview($attrib) { $attrib += array('id' => 'rcmtaskslist'); $this->register_gui_object('resultlist', $attrib['id']); unset($attrib['name']); return html::tag('ul', $attrib, ''); } /** * Container for a tags cloud */ function tagslist($attrib) { $attrib += array('id' => 'rcmtasktagslist'); unset($attrib['name']); $this->register_gui_object('tagslist', $attrib['id']); return html::tag('ul', $attrib, ''); } /** * Interactive UI element to add/remove tags */ function tags_editline($attrib) { $attrib += array('id' => 'rcmtasktagsedit'); $this->register_gui_object('edittagline', $attrib['id']); $input = new html_inputfield(array('name' => 'tags[]', 'class' => 'tag', 'size' => $attrib['size'], 'tabindex' => $attrib['tabindex'])); unset($attrib['tabindex']); return html::div($attrib, $input->show('')); } /** * Generate HTML element for attachments list */ function attachments_list($attrib = array()) { if (!$attrib['id']) $attrib['id'] = 'rcmtaskattachmentlist'; $this->register_gui_object('attachmentlist', $attrib['id']); return html::tag('ul', $attrib, '', html::$common_attrib); } /** * Generate the form for event attachments upload */ function attachments_form($attrib = array()) { // add ID if not given if (!$attrib['id']) $attrib['id'] = 'rcmtaskuploadform'; // Get max filesize, enable upload progress bar $max_filesize = $this->rc->upload_init(); $button = new html_inputfield(array('type' => 'button')); $input = new html_inputfield(array( 'type' => 'file', 'name' => '_attachments[]', 'multiple' => 'multiple', 'size' => $attrib['attachmentfieldsize'], )); return html::div($attrib, html::div(null, $input->show()) . html::div('buttons', $button->show($this->rc->gettext('upload'), array('class' => 'button mainaction', 'onclick' => rcmail_output::JS_OBJECT_NAME . ".upload_file(this.form)"))) . html::div('hint', $this->rc->gettext(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) ); } /** * Register UI object for HTML5 drag & drop file upload */ function file_drop_area($attrib = array()) { if ($attrib['id']) { $this->register_gui_object('filedrop', $attrib['id']); $this->rc->output->set_env('filedrop', array('action' => 'upload', 'fieldname' => '_attachments')); } } /** * */ function attendees_list($attrib = array()) { // add "noreply" checkbox to attendees table only $invitations = strpos($attrib['id'], 'attend') !== false; $invite = new html_checkbox(array('value' => 1, 'id' => 'edit-attendees-invite')); $table = new html_table(array('cols' => 4 + intval($invitations), 'border' => 0, 'cellpadding' => 0, 'class' => 'rectable')); // $table->add_header('role', $this->plugin->gettext('role')); $table->add_header('name', $this->plugin->gettext($attrib['coltitle'] ?: 'attendee')); $table->add_header('confirmstate', $this->plugin->gettext('confirmstate')); if ($invitations) { $table->add_header(array('class' => 'invite', 'title' => $this->plugin->gettext('sendinvitations')), $invite->show(1) . html::label('edit-attendees-invite', $this->plugin->gettext('sendinvitations'))); } $table->add_header('options', ''); // hide invite column if disabled by config $itip_notify = (int)$this->rc->config->get('calendar_itip_send_option', 3); if ($invitations && !($itip_notify & 2)) { $css = sprintf('#%s td.invite, #%s th.invite { display:none !important }', $attrib['id'], $attrib['id']); $this->rc->output->add_footer(html::tag('style', array('type' => 'text/css'), $css)); } return $table->show($attrib); } /** * */ function attendees_form($attrib = array()) { $input = new html_inputfield(array('name' => 'participant', 'id' => 'edit-attendee-name', 'size' => 30)); $textarea = new html_textarea(array('name' => 'comment', 'id' => 'edit-attendees-comment', 'rows' => 4, 'cols' => 55, 'title' => $this->plugin->gettext('itipcommenttitle'))); return html::div($attrib, html::div(null, $input->show() . " " . html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-attendee-add', 'value' => $this->plugin->gettext('addattendee'))) // . " " . html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-attendee-schedule', 'value' => $this->plugin->gettext('scheduletime').'...')) ) . html::p('attendees-commentbox', html::label(null, $this->plugin->gettext('itipcomment') . $textarea->show())) ); } /** * */ function edit_attendees_notify($attrib = array()) { $checkbox = new html_checkbox(array('name' => '_notify', 'id' => 'edit-attendees-donotify', 'value' => 1)); return html::div($attrib, html::label(null, $checkbox->show(1) . ' ' . $this->plugin->gettext('sendnotifications'))); } /** * Form for uploading and importing tasks */ function tasks_import_form($attrib = array()) { if (!$attrib['id']) { $attrib['id'] = 'rcmImportForm'; } // Get max filesize, enable upload progress bar $max_filesize = $this->rc->upload_init(); $accept = '.ics, text/calendar, text/x-vcalendar, application/ics'; if (class_exists('ZipArchive', false)) { $accept .= ', .zip, application/zip'; } $input = new html_inputfield(array( 'type' => 'file', 'name' => '_data', 'size' => $attrib['uploadfieldsize'], 'accept' => $accept )); $html = html::div('form-section', html::div(null, $input->show()) . html::div('hint', $this->rc->gettext(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) ); $html .= html::div('form-section', html::label('task-import-list', $this->plugin->gettext('list')) . $this->tasklist_select(array('name' => 'source', 'id' => 'task-import-list', 'editable' => true)) ); $this->rc->output->add_gui_object('importform', $attrib['id']); $this->rc->output->add_label('import', 'importerror'); return html::tag('form', array( 'action' => $this->rc->url(array('task' => 'tasklist', 'action' => 'import')), 'method' => 'post', 'enctype' => 'multipart/form-data', 'id' => $attrib['id'] ), $html ); } /** * Form to select options for exporting tasks */ function tasks_export_form($attrib = array()) { if (!$attrib['id']) { $attrib['id'] = 'rcmTaskExportForm'; } $html .= html::div('form-section', html::label('task-export-list', $this->plugin->gettext('list')) . $this->tasklist_select(array( 'name' => 'source', 'id' => 'task-export-list', 'extra' => array('' => '- ' . $this->plugin->gettext('currentview') . ' -'), )) ); $checkbox = new html_checkbox(array('name' => 'attachments', 'id' => 'task-export-attachments', 'value' => 1)); $html .= html::div('form-section', html::label('task-export-attachments', $this->plugin->gettext('exportattachments')) . $checkbox->show(1) ); $this->register_gui_object('exportform', $attrib['id']); return html::tag('form', array('action' => $this->rc->url(array('task' => 'tasklist', 'action' => 'export')), 'method' => "post", 'id' => $attrib['id']), $html ); } /** * Wrapper for rcube_output_html::add_gui_object() */ function register_gui_object($name, $id) { $this->gui_objects[$name] = $id; $this->rc->output->add_gui_object($name, $id); } /** * Getter for registered gui objects. * (for manual registration when loading the inline UI) */ function get_gui_objects() { return $this->gui_objects; } }