* @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 ?: 'calendar') : 'other'; } /** * Calendar UI initialization and requests handlers */ public function init() { if ($this->ready) { // already done return; } // add taskbar button $this->cal->add_button([ 'command' => 'calendar', 'class' => 'button-calendar', 'classsel' => 'button-calendar button-selected', 'innerclass' => 'button-inner', 'label' => 'calendar.calendar', 'type' => 'link' ], 'taskbar' ); // load basic client script if ($this->rc->action != 'print') { $this->cal->include_script('calendar_base.js'); } $this->addCSS(); $this->ready = true; } /** * Register handler methods for the template engine */ public function init_templates() { $this->cal->register_handler('plugin.calendar_css', [$this, 'calendar_css']); $this->cal->register_handler('plugin.calendar_list', [$this, 'calendar_list']); $this->cal->register_handler('plugin.calendar_select', [$this, 'calendar_select']); $this->cal->register_handler('plugin.identity_select', [$this, 'identity_select']); $this->cal->register_handler('plugin.category_select', [$this, 'category_select']); $this->cal->register_handler('plugin.status_select', [$this, 'status_select']); $this->cal->register_handler('plugin.freebusy_select', [$this, 'freebusy_select']); $this->cal->register_handler('plugin.priority_select', [$this, 'priority_select']); $this->cal->register_handler('plugin.sensitivity_select', [$this, 'sensitivity_select']); $this->cal->register_handler('plugin.alarm_select', [$this, 'alarm_select']); $this->cal->register_handler('plugin.recurrence_form', [$this->cal->lib, 'recurrence_form']); $this->cal->register_handler('plugin.attendees_list', [$this, 'attendees_list']); $this->cal->register_handler('plugin.attendees_form', [$this, 'attendees_form']); $this->cal->register_handler('plugin.resources_form', [$this, 'resources_form']); $this->cal->register_handler('plugin.resources_list', [$this, 'resources_list']); $this->cal->register_handler('plugin.resources_searchform', [$this, 'resources_search_form']); $this->cal->register_handler('plugin.resource_info', [$this, 'resource_info']); $this->cal->register_handler('plugin.resource_calendar', [$this, 'resource_calendar']); $this->cal->register_handler('plugin.attendees_freebusy_table', [$this, 'attendees_freebusy_table']); $this->cal->register_handler('plugin.edit_attendees_notify', [$this, 'edit_attendees_notify']); $this->cal->register_handler('plugin.edit_recurrence_sync', [$this, 'edit_recurrence_sync']); $this->cal->register_handler('plugin.edit_recurring_warning', [$this, 'recurring_event_warning']); $this->cal->register_handler('plugin.event_rsvp_buttons', [$this, 'event_rsvp_buttons']); $this->cal->register_handler('plugin.agenda_options', [$this, 'agenda_options']); $this->cal->register_handler('plugin.events_import_form', [$this, 'events_import_form']); $this->cal->register_handler('plugin.events_export_form', [$this, 'events_export_form']); $this->cal->register_handler('plugin.object_changelog_table', ['libkolab', 'object_changelog_table']); $this->cal->register_handler('plugin.searchform', [$this->rc->output, 'search_form']); kolab_attachments_handler::ui(); } /** * Adds CSS stylesheets to the page header */ public function addCSS() { $skin_path = $this->cal->local_skin_path(); if ( $this->rc->task == 'calendar' && (!$this->rc->action || in_array($this->rc->action, ['index', 'print'])) ) { // Include fullCalendar style before skin file for simpler style overriding $this->cal->include_stylesheet($skin_path . '/fullcalendar.css'); } $this->cal->include_stylesheet($skin_path . '/calendar.css'); if ($this->rc->task == 'calendar' && $this->rc->action == 'print') { $this->cal->include_stylesheet($skin_path . '/print.css'); } } /** * Adds JS files to the page header */ public function addJS() { $this->cal->include_script('lib/js/moment.js'); $this->cal->include_script('lib/js/fullcalendar.js'); if ($this->rc->task == 'calendar' && $this->rc->action == 'print') { $this->cal->include_script('print.js'); } else { $this->rc->output->include_script('treelist.js'); $this->cal->api->include_script('libkolab/libkolab.js'); $this->cal->include_script('calendar_ui.js'); jqueryui::miniColors(); } } /** * Add custom style for the calendar UI */ function calendar_css($attrib = []) { $categories = $this->cal->driver->list_categories(); $calendars = $this->cal->driver->list_calendars(); $js_categories = []; $mode = $this->rc->config->get('calendar_event_coloring', $this->cal->defaults['calendar_event_coloring']); $css = "\n"; foreach ((array) $categories as $class => $color) { if (!empty($color)) { $js_categories[$class] = $color; $color = ltrim($color, '#'); $class = 'cat-' . asciiwords(strtolower($class), true); $css .= ".$class { color: #$color; }\n"; } } $this->rc->output->set_env('calendar_categories', $js_categories); foreach ((array) $calendars as $id => $prop) { if (!empty($prop['color'])) { $css .= $this->calendar_css_classes($id, $prop, $mode, $attrib); } } return html::tag('style', ['type' => 'text/css'], $css); } /** * Calendar folder specific CSS classes */ public function calendar_css_classes($id, $prop, $mode, $attrib = []) { $color = $folder_color = $prop['color']; // replace white with skin-defined color if (!empty($attrib['folder-fallback-color']) && preg_match('/^f+$/i', $folder_color)) { $folder_color = ltrim($attrib['folder-fallback-color'], '#'); } $class = 'cal-' . asciiwords($id, true); $css = "li .$class"; if (!empty($attrib['folder-class'])) { $css = str_replace('$class', $class, $attrib['folder-class']); } $css .= " { color: #$folder_color; }\n"; return $css . ".$class .handle { background-color: #$color; }\n"; } /** * Generate HTML content of the calendars list (or metadata only) */ function calendar_list($attrib = [], $js_only = false) { $html = ''; $jsenv = []; $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 (!empty($calendars[calendar_driver::BIRTHDAY_CALENDAR_ID])) { $bdaycal = $calendars[calendar_driver::BIRTHDAY_CALENDAR_ID]; $calendars = [calendar_driver::BIRTHDAY_CALENDAR_ID => $bdaycal]; } else { $calendars = []; // clear array for flat listing } } else if (isset($attrib['class'])) { // fall-back to flat folder listing $attrib['class'] .= ' flat'; } foreach ((array) $calendars as $id => $prop) { if (!empty($attrib['activeonly']) && empty($prop['active'])) { continue; } $li_content = $this->calendar_list_item($id, $prop, $jsenv, !empty($attrib['activeonly'])); $li_attr = [ 'id' => 'rcmlical' . $id, 'class' => isset($prop['group']) ? $prop['group'] : null, ]; $html .= html::tag('li', $li_attr, $li_content); } $this->rc->output->set_env('calendars', $jsenv); if ($js_only) { return; } $this->rc->output->set_env('source', rcube_utils::get_input_value('source', rcube_utils::INPUT_GET)); $this->rc->output->add_gui_object('calendarslist', !empty($attrib['id']) ? $attrib['id'] : 'rccalendarlist'); 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, !empty($attrib['activeonly'])); if (!empty($folder->children)) { $content .= html::tag('ul', ['style' => $is_collapsed ? "display:none;" : null], $this->list_tree_html($folder, $data, $jsenv, $attrib) ); } if (strlen($content)) { $li_attr = [ 'id' => 'rcmlical' . rcube_utils::html_identifier($id), 'class' => $prop['group'] . (!empty($prop['virtual']) ? ' virtual' : ''), ]; $out .= html::tag('li', $li_attr, $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 (empty($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([ '_cal' => $this->cal->ical_feed_hash($id) . '.ics', 'action' => 'feed' ] ); $jsenv[$id] = $prop; } if (!empty($prop['title'])) { $title = $prop['title']; } else if ($prop['name'] != $prop['listname'] || strlen($prop['name']) > 25) { $title = html_entity_decode($prop['name'], ENT_COMPAT, RCUBE_CHARSET); } else { $title = ''; } $classes = ['calendar', 'cal-' . asciiwords($id, true)]; if (!empty($prop['virtual'])) { $classes[] = 'virtual'; } else if (empty($prop['editable'])) { $classes[] = 'readonly'; } if (!empty($prop['subscribed'])) { $classes[] = 'subscribed'; if ($prop['subscribed'] === 2) { $classes[] = 'partial'; } } if (!empty($prop['class'])) { $classes[] = $prop['class']; } $content = ''; if (!$activeonly || !empty($prop['active'])) { $label_id = 'cl:' . $id; $content = html::a( ['class' => 'calname', 'id' => $label_id, 'title' => $title, 'href' => '#'], rcube::Q(!empty($prop['editname']) ? $prop['editname'] : $prop['listname']) ); if (empty($prop['virtual'])) { $color = !empty($prop['color']) ? $prop['color'] : 'f00'; $actions = ''; if (!EMPTY($prop['removable'])) { $actions .= html::a([ 'href' => '#', 'class' => 'remove', 'title' => $this->cal->gettext('removelist') ], ' ' ); } $actions .= html::a([ 'href' => '#', 'class' => 'quickview', 'title' => $this->cal->gettext('quickview'), 'role' => 'checkbox', 'aria-checked' => 'false' ], '' ); if (!empty($prop['subscribed'])) { $actions .= html::a([ 'href' => '#', 'class' => 'subscribed', 'title' => $this->cal->gettext('calendarsubscribe'), 'role' => 'checkbox', 'aria-checked' => !empty($prop['subscribed']) ? 'true' : 'false' ], ' ' ); } $content .= html::tag('input', [ 'type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => !empty($prop['active']), 'aria-labelledby' => $label_id ]) . html::span('actions', $actions) . html::span(['class' => 'handle', 'style' => "background-color: #$color"], ' '); } $content = html::div(join(' ', $classes), $content); } return $content; } /** * Render a HTML for agenda options form */ function agenda_options($attrib = []) { $attrib += ['id' => 'agendaoptions']; $attrib['style'] = 'display:none'; $select_range = new html_select(['name' => 'listrange', 'id' => 'agenda-listrange', 'class' => 'form-control custom-select']); $select_range->add(1 . ' ' . preg_replace('/\(.+\)/', '', $this->cal->lib->gettext('days')), ''); foreach ([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::span('input-group', html::label(['for' => 'agenda-listrange', 'class' => 'input-group-prepend'], html::span('input-group-text', $this->cal->gettext('listrange')) ) . $select_range->show($this->rc->config->get('calendar_agenda_range', $this->cal->defaults['calendar_agenda_range'])) ); return html::div($attrib, $html); } /** * Render a HTML select box for calendar selection */ function calendar_select($attrib = []) { $attrib['name'] = 'calendar'; $attrib['is_escaped'] = true; $select = new html_select($attrib); foreach ((array) $this->cal->driver->list_calendars() as $id => $prop) { if ( !empty($prop['editable']) || (!empty($prop['rights']) && 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 = []) { $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 = []) { $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 = []) { $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 = []) { $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 = []) { $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 = []) { $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 = []) { return $this->cal->lib->alarm_select($attrib, $this->cal->driver->alarm_types, $this->cal->driver->alarm_absolute); } /** * Render HTML for attendee notification warning */ function edit_attendees_notify($attrib = []) { $checkbox = new html_checkbox(['name' => '_notify', 'id' => 'edit-attendees-donotify', 'value' => 1, 'class' => 'pretty-checkbox']); return html::div($attrib, html::label(null, $checkbox->show(1) . ' ' . $this->cal->gettext('sendnotifications'))); } /** * Render HTML for recurrence option to align start date with the recurrence rule */ function edit_recurrence_sync($attrib = []) { $checkbox = new html_checkbox(['name' => '_start_sync', 'value' => 1, 'class' => 'pretty-checkbox']); return html::div($attrib, html::label(null, $checkbox->show(1) . ' ' . $this->cal->gettext('eventstartsync'))); } /** * Generate the form for recurrence settings */ function recurring_event_warning($attrib = []) { $attrib['id'] = 'edit-recurring-warning'; $radio = new html_radiobutton(['name' => '_savemode', 'class' => 'edit-recurring-savemode']); $form = html::label(null, $radio->show('', ['value' => 'current']) . $this->cal->gettext('currentevent')) . ' ' . html::label(null, $radio->show('', ['value' => 'future']) . $this->cal->gettext('futurevents')) . ' ' . html::label(null, $radio->show('all', ['value' => 'all']) . $this->cal->gettext('allevents')) . ' ' . html::label(null, $radio->show('', ['value' => 'new']) . $this->cal->gettext('saveasnew')); return html::div($attrib, html::div('message', $this->cal->gettext('changerecurringeventwarning')) . html::div('savemode', $form) ); } /** * Form for uploading and importing events */ function events_import_form($attrib = []) { if (empty($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([ 'id' => 'importfile', 'type' => 'file', 'name' => '_data', 'size' => !empty($attrib['uploadfieldsize']) ? $attrib['uploadfieldsize'] : null, 'accept' => $accept ]); $select = new html_select(['name' => '_range', 'id' => 'event-import-range']); $select->add([ $this->cal->gettext('onemonthback'), $this->cal->gettext(['name' => 'nmonthsback', 'vars' => ['nr'=>2]]), $this->cal->gettext(['name' => 'nmonthsback', 'vars' => ['nr'=>3]]), $this->cal->gettext(['name' => 'nmonthsback', 'vars' => ['nr'=>6]]), $this->cal->gettext(['name' => 'nmonthsback', 'vars' => ['nr'=>12]]), $this->cal->gettext('all'), ], ['1','2','3','6','12',0] ); $html = html::div('form-section form-group row', html::label(['class' => 'col-sm-4 col-form-label', 'for' => 'importfile'], rcube::Q($this->rc->gettext('importfromfile')) ) . html::div('col-sm-8', $input->show() . html::div('hint', $this->rc->gettext(['name' => 'maxuploadsize', 'vars' => ['size' => $max_filesize]])) ) ); $html .= html::div('form-section form-group row', html::label(['for' => 'event-import-calendar', 'class' => 'col-form-label col-sm-4'], $this->cal->gettext('calendar') ) . html::div('col-sm-8', $this->calendar_select(['name' => 'calendar', 'id' => 'event-import-calendar'])) ); $html .= html::div('form-section form-group row', html::label(['for' => 'event-import-range', 'class' => 'col-form-label col-sm-4'], $this->cal->gettext('importrange') ) . html::div('col-sm-8', $select->show(1)) ); $this->rc->output->add_gui_object('importform', $attrib['id']); $this->rc->output->add_label('import'); return html::tag('p', null, $this->cal->gettext('importtext')) . html::tag('form', [ 'action' => $this->rc->url(['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 = []) { if (empty($attrib['id'])) { $attrib['id'] = 'rcmExportForm'; } $html = html::div('form-section form-group row', html::label(['for' => 'event-export-calendar', 'class' => 'col-sm-4 col-form-label'], $this->cal->gettext('calendar') ) . html::div('col-sm-8', $this->calendar_select(['name' => 'calendar', 'id' => 'event-export-calendar', 'class' => 'form-control custom-select'])) ); $select = new html_select([ 'name' => 'range', 'id' => 'event-export-range', 'class' => 'form-control custom-select rounded-right' ]); $select->add([ $this->cal->gettext('all'), $this->cal->gettext('onemonthback'), $this->cal->gettext(['name' => 'nmonthsback', 'vars' => ['nr' => 2]]), $this->cal->gettext(['name' => 'nmonthsback', 'vars' => ['nr' => 3]]), $this->cal->gettext(['name' => 'nmonthsback', 'vars' => ['nr' => 6]]), $this->cal->gettext(['name' => 'nmonthsback', 'vars' => ['nr' => 12]]), $this->cal->gettext('customdate'), ], [0,'1','2','3','6','12','custom'] ); $startdate = new html_inputfield([ 'name' => 'start', 'size' => 11, 'id' => 'event-export-startdate', 'style' => 'display:none' ]); $html .= html::div('form-section form-group row', html::label(['for' => 'event-export-range', 'class' => 'col-sm-4 col-form-label'], $this->cal->gettext('exportrange') ) . html::div('col-sm-8 input-group', $select->show(0) . $startdate->show()) ); $checkbox = new html_checkbox([ 'name' => 'attachments', 'id' => 'event-export-attachments', 'value' => 1, 'class' => 'form-check-input pretty-checkbox' ]); $html .= html::div('form-section form-check row', html::label(['for' => 'event-export-attachments', 'class' => 'col-sm-4 col-form-label'], $this->cal->gettext('exportattachments') ) . html::div('col-sm-8', $checkbox->show(1)) ); $this->rc->output->add_gui_object('exportform', $attrib['id']); return html::tag('form', $attrib + [ 'action' => $this->rc->url(['task' => 'calendar', 'action' => 'export_events']), 'method' => 'post', 'id' => $attrib['id'] ], $html ); } /** * Handler for calendar form template. * The form content could be overriden by the driver */ function calendar_editform($action, $calendar = []) { $this->action = $action; $this->calendar = $calendar; // load miniColors js/css files jqueryui::miniColors(); $this->rc->output->set_env('pagetitle', $this->cal->gettext('calendarprops')); $this->rc->output->add_handler('folderform', [$this, 'calendarform']); $this->rc->output->send('libkolab.folderform'); } /** * Handler for calendar form template. * The form content could be overriden by the driver */ function calendarform($attrib) { // compose default calendar form fields $input_name = new html_inputfield(['name' => 'name', 'id' => 'calendar-name', 'size' => 20]); $input_color = new html_inputfield(['name' => 'color', 'id' => 'calendar-color', 'size' => 7, 'class' => 'colors']); $formfields = [ 'name' => [ 'label' => $this->cal->gettext('name'), 'value' => $input_name->show(isset($this->calendar['name']) ? $this->calendar['name'] : ''), 'id' => 'calendar-name', ], 'color' => [ 'label' => $this->cal->gettext('color'), 'value' => $input_color->show(isset($this->calendar['color']) ? $this->calendar['color'] : ''), 'id' => 'calendar-color', ], ]; if (!empty($this->cal->driver->alarms)) { $checkbox = new html_checkbox(['name' => 'showalarms', 'id' => 'calendar-showalarms', 'value' => 1]); $formfields['showalarms'] = [ 'label' => $this->cal->gettext('showalarms'), 'value' => $checkbox->show(!empty($this->calendar['showalarms']) ? 1 : 0), 'id' => 'calendar-showalarms', ]; } // allow driver to extend or replace the form content return html::tag('form', $attrib + ['action' => '#', 'method' => 'get', 'id' => 'calendarpropform'], $this->cal->driver->calendar_form($this->action, $this->calendar, $formfields) ); } /** * Render HTML for attendees table */ function attendees_list($attrib = []) { // add "noreply" checkbox to attendees table only $invitations = strpos($attrib['id'], 'attend') !== false; $invite = new html_checkbox(['value' => 1, 'id' => 'edit-attendees-invite']); $table = new html_table(['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(!empty($attrib['coltitle']) ? $attrib['coltitle'] : 'attendee')); $table->add_header('availability', $this->cal->gettext('availability')); $table->add_header('confirmstate', $this->cal->gettext('confirmstate')); if ($invitations) { $table->add_header(['class' => 'invite', 'title' => $this->cal->gettext('sendinvitations')], $invite->show(1) . html::label('edit-attendees-invite', html::span('inner', $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', ['type' => 'text/css'], $css)); } return $table->show($attrib); } /** * Render HTML for attendees adding form */ function attendees_form($attrib = []) { $input = new html_inputfield([ 'name' => 'participant', 'id' => 'edit-attendee-name', 'class' => 'form-control' ]); $textarea = new html_textarea([ 'name' => 'comment', 'id' => 'edit-attendees-comment', 'class' => 'form-control', 'rows' => 4, 'cols' => 55, 'title' => $this->cal->gettext('itipcommenttitle') ]); return html::div($attrib, html::div('form-searchbar', $input->show() . ' ' . html::tag('input', [ 'type' => 'button', 'class' => 'button', 'id' => 'edit-attendee-add', 'value' => $this->cal->gettext('addattendee') ]) . ' ' . html::tag('input', [ 'type' => 'button', 'class' => 'button', 'id' => 'edit-attendee-schedule', 'value' => $this->cal->gettext('scheduletime') . '...' ]) ) . html::p('attendees-commentbox', html::label('edit-attendees-comment', $this->cal->gettext('itipcomment')) . $textarea->show()) ); } /** * Render HTML for resources adding form */ function resources_form($attrib = []) { $input = new html_inputfield(['name' => 'resource', 'id' => 'edit-resource-name', 'class' => 'form-control']); return html::div($attrib, html::div('form-searchbar', $input->show() . ' ' . html::tag('input', [ 'type' => 'button', 'class' => 'button', 'id' => 'edit-resource-add', 'value' => $this->cal->gettext('addresource') ]) . ' ' . html::tag('input', [ 'type' => 'button', 'class' => 'button', 'id' => 'edit-resource-find', 'value' => $this->cal->gettext('findresources') . '...' ]) ) ); } /** * Render HTML for resources list */ function resources_list($attrib = []) { $attrib += ['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 = []) { $attrib += ['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 = ['id','class','style','width','summary','cellpadding','cellspacing','border']; return html::tag('table', $attrib, html::tag('tbody', null, ''), $table_attrib) . html::tag('table', ['id' => $attrib['id'] . '-owner', 'style' => 'display:none'] + $attrib, html::tag('thead', null, html::tag('tr', null, html::tag('td', ['colspan' => 2], rcube::Q($this->cal->gettext('resourceowner'))) ) ) . html::tag('tbody', null, ''), $table_attrib ); } /** * */ public function resource_calendar($attrib = []) { $attrib += ['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 $attrib Named parameters * * @return string HTML code for the gui object */ function resources_search_form($attrib) { $attrib += [ 'command' => 'search-resource', 'reset-command' => 'reset-resource-search', 'id' => 'rcmcalresqsearchbox', 'autocomplete' => 'off', 'form-name' => 'rcmcalresoursqsearchform', 'gui-object' => 'resourcesearchform', ]; // add form tag around text field return $this->rc->output->search_form($attrib); } /** * */ function attendees_freebusy_table($attrib = []) { $table = new html_table(['cols' => 2, 'border' => 0, 'cellspacing' => 0]); $table->add('attendees', html::tag('h3', 'boxtitle', $this->cal->gettext('tabattendees')) . html::div('timesheader', ' ') . html::div(['id' => 'schedule-attendees-list', 'class' => 'attendees-list'], '') ); $table->add('times', html::div('scroll', html::tag('table', ['id' => 'schedule-freebusy-times', 'border' => 0, 'cellspacing' => 0], html::tag('thead') . html::tag('tbody') ) . html::div(['id' => 'schedule-event-time', 'style' => 'display:none'], ' ') ) ); return $table->show($attrib); } /** * */ function event_invitebox($attrib = []) { if (!empty($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 = []) { $actions = ['accepted', 'tentative', 'declined']; if (empty($attrib['delegate']) || $attrib['delegate'] !== 'false') { $actions[] = 'delegated'; } return $this->cal->itip->itip_rsvp_buttons($attrib, $actions); } }