1<?php 2/** 3 * User Interface class for the Calendar plugin 4 * 5 * @author Lazlo Westerhof <hello@lazlo.me> 6 * @author Thomas Bruederli <bruederli@kolabsys.com> 7 * 8 * Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me> 9 * Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com> 10 * 11 * This program is free software: you can redistribute it and/or modify 12 * it under the terms of the GNU Affero General Public License as 13 * published by the Free Software Foundation, either version 3 of the 14 * License, or (at your option) any later version. 15 * 16 * This program is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU Affero General Public License for more details. 20 * 21 * You should have received a copy of the GNU Affero General Public License 22 * along with this program. If not, see <http://www.gnu.org/licenses/>. 23 */ 24 25 26class calendar_ui 27{ 28 private $rc; 29 private $cal; 30 private $ready = false; 31 public $screen; 32 33 function __construct($cal) 34 { 35 $this->cal = $cal; 36 $this->rc = $cal->rc; 37 $this->screen = $this->rc->task == 'calendar' ? ($this->rc->action ? $this->rc->action: 'calendar') : 'other'; 38 } 39 40 /** 41 * Calendar UI initialization and requests handlers 42 */ 43 public function init() 44 { 45 if ($this->ready) // already done 46 return; 47 48 // add taskbar button 49 $this->cal->add_button(array( 50 'command' => 'calendar', 51 'class' => 'button-calendar', 52 'classsel' => 'button-calendar button-selected', 53 'innerclass' => 'button-inner', 54 'label' => 'calendar.calendar', 55 'type' => 'link' 56 ), 'taskbar'); 57 58 // load basic client script 59 if ($this->rc->action != 'print') { 60 $this->cal->include_script('calendar_base.js'); 61 } 62 63 $this->addCSS(); 64 65 $this->ready = true; 66 } 67 68 /** 69 * Register handler methods for the template engine 70 */ 71 public function init_templates() 72 { 73 $this->cal->register_handler('plugin.calendar_css', array($this, 'calendar_css')); 74 $this->cal->register_handler('plugin.calendar_list', array($this, 'calendar_list')); 75 $this->cal->register_handler('plugin.calendar_select', array($this, 'calendar_select')); 76 $this->cal->register_handler('plugin.identity_select', array($this, 'identity_select')); 77 $this->cal->register_handler('plugin.category_select', array($this, 'category_select')); 78 $this->cal->register_handler('plugin.status_select', array($this, 'status_select')); 79 $this->cal->register_handler('plugin.freebusy_select', array($this, 'freebusy_select')); 80 $this->cal->register_handler('plugin.priority_select', array($this, 'priority_select')); 81 $this->cal->register_handler('plugin.sensitivity_select', array($this, 'sensitivity_select')); 82 $this->cal->register_handler('plugin.alarm_select', array($this, 'alarm_select')); 83 $this->cal->register_handler('plugin.recurrence_form', array($this->cal->lib, 'recurrence_form')); 84 $this->cal->register_handler('plugin.attendees_list', array($this, 'attendees_list')); 85 $this->cal->register_handler('plugin.attendees_form', array($this, 'attendees_form')); 86 $this->cal->register_handler('plugin.resources_form', array($this, 'resources_form')); 87 $this->cal->register_handler('plugin.resources_list', array($this, 'resources_list')); 88 $this->cal->register_handler('plugin.resources_searchform', array($this, 'resources_search_form')); 89 $this->cal->register_handler('plugin.resource_info', array($this, 'resource_info')); 90 $this->cal->register_handler('plugin.resource_calendar', array($this, 'resource_calendar')); 91 $this->cal->register_handler('plugin.attendees_freebusy_table', array($this, 'attendees_freebusy_table')); 92 $this->cal->register_handler('plugin.edit_attendees_notify', array($this, 'edit_attendees_notify')); 93 $this->cal->register_handler('plugin.edit_recurrence_sync', array($this, 'edit_recurrence_sync')); 94 $this->cal->register_handler('plugin.edit_recurring_warning', array($this, 'recurring_event_warning')); 95 $this->cal->register_handler('plugin.event_rsvp_buttons', array($this, 'event_rsvp_buttons')); 96 $this->cal->register_handler('plugin.agenda_options', array($this, 'agenda_options')); 97 $this->cal->register_handler('plugin.events_import_form', array($this, 'events_import_form')); 98 $this->cal->register_handler('plugin.events_export_form', array($this, 'events_export_form')); 99 $this->cal->register_handler('plugin.object_changelog_table', array('libkolab', 'object_changelog_table')); 100 $this->cal->register_handler('plugin.searchform', array($this->rc->output, 'search_form')); // use generic method from rcube_template 101 102 kolab_attachments_handler::ui(); 103 } 104 105 /** 106 * Adds CSS stylesheets to the page header 107 */ 108 public function addCSS() 109 { 110 $skin_path = $this->cal->local_skin_path(); 111 112 if ($this->rc->task == 'calendar' && (!$this->rc->action || in_array($this->rc->action, array('index', 'print')))) { 113 // Include fullCalendar style before skin file for simpler style overriding 114 $this->cal->include_stylesheet($skin_path . '/fullcalendar.css'); 115 } 116 117 $this->cal->include_stylesheet($skin_path . '/calendar.css'); 118 119 if ($this->rc->task == 'calendar' && $this->rc->action == 'print') { 120 $this->cal->include_stylesheet($skin_path . '/print.css'); 121 } 122 } 123 124 /** 125 * Adds JS files to the page header 126 */ 127 public function addJS() 128 { 129 $this->cal->include_script('lib/js/moment.js'); 130 $this->cal->include_script('lib/js/fullcalendar.js'); 131 132 if ($this->rc->task == 'calendar' && $this->rc->action == 'print') { 133 $this->cal->include_script('print.js'); 134 } 135 else { 136 $this->rc->output->include_script('treelist.js'); 137 $this->cal->api->include_script('libkolab/libkolab.js'); 138 $this->cal->include_script('calendar_ui.js'); 139 jqueryui::miniColors(); 140 } 141 } 142 143 /** 144 * 145 */ 146 function calendar_css($attrib = array()) 147 { 148 $categories = $this->cal->driver->list_categories(); 149 $js_categories = array(); 150 $mode = $this->rc->config->get('calendar_event_coloring', $this->cal->defaults['calendar_event_coloring']); 151 $css = "\n"; 152 153 foreach ((array)$categories as $class => $color) { 154 if (!empty($color)) { 155 $js_categories[$class] = $color; 156 157 $color = ltrim($color, '#'); 158 $class = 'cat-' . asciiwords(strtolower($class), true); 159 $css .= ".$class { color: #$color; }\n"; 160 } 161 } 162 163 $this->rc->output->set_env('calendar_categories', $js_categories); 164 165 $calendars = $this->cal->driver->list_calendars(); 166 foreach ((array)$calendars as $id => $prop) { 167 if ($prop['color']) { 168 $css .= $this->calendar_css_classes($id, $prop, $mode, $attrib); 169 } 170 } 171 172 return html::tag('style', array('type' => 'text/css'), $css); 173 } 174 175 /** 176 * 177 */ 178 public function calendar_css_classes($id, $prop, $mode, $attrib = array()) 179 { 180 $color = $folder_color = $prop['color']; 181 182 // replace white with skin-defined color 183 if (!empty($attrib['folder-fallback-color']) && preg_match('/^f+$/i', $folder_color)) { 184 $folder_color = ltrim($attrib['folder-fallback-color'], '#'); 185 } 186 187 $class = 'cal-' . asciiwords($id, true); 188 $css = str_replace('$class', $class, $attrib['folder-class']) ?: "li .$class"; 189 $css .= " { color: #$folder_color; }\n"; 190 191 return $css . ".$class .handle { background-color: #$color; }\n"; 192 } 193 194 /** 195 * 196 */ 197 function calendar_list($attrib = array(), $js_only = false) 198 { 199 $html = ''; 200 $jsenv = array(); 201 $tree = true; 202 $calendars = $this->cal->driver->list_calendars(0, $tree); 203 204 // walk folder tree 205 if (is_object($tree)) { 206 $html = $this->list_tree_html($tree, $calendars, $jsenv, $attrib); 207 208 // append birthdays calendar which isn't part of $tree 209 if ($bdaycal = $calendars[calendar_driver::BIRTHDAY_CALENDAR_ID]) { 210 $calendars = array(calendar_driver::BIRTHDAY_CALENDAR_ID => $bdaycal); 211 } 212 else { 213 $calendars = array(); // clear array for flat listing 214 } 215 } 216 else { 217 // fall-back to flat folder listing 218 $attrib['class'] .= ' flat'; 219 } 220 221 foreach ((array)$calendars as $id => $prop) { 222 if ($attrib['activeonly'] && !$prop['active']) 223 continue; 224 225 $html .= html::tag('li', array('id' => 'rcmlical' . $id, 'class' => $prop['group']), 226 $content = $this->calendar_list_item($id, $prop, $jsenv, $attrib['activeonly']) 227 ); 228 } 229 230 $this->rc->output->set_env('calendars', $jsenv); 231 232 if ($js_only) { 233 return; 234 } 235 236 $this->rc->output->set_env('source', rcube_utils::get_input_value('source', rcube_utils::INPUT_GET)); 237 $this->rc->output->add_gui_object('calendarslist', $attrib['id'] ?: 'unknown'); 238 239 return html::tag('ul', $attrib, $html, html::$common_attrib); 240 } 241 242 /** 243 * Return html for a structured list <ul> for the folder tree 244 */ 245 public function list_tree_html($node, $data, &$jsenv, $attrib) 246 { 247 $out = ''; 248 foreach ($node->children as $folder) { 249 $id = $folder->id; 250 $prop = $data[$id]; 251 $is_collapsed = false; // TODO: determine this somehow? 252 253 $content = $this->calendar_list_item($id, $prop, $jsenv, $attrib['activeonly']); 254 255 if (!empty($folder->children)) { 256 $content .= html::tag('ul', array('style' => ($is_collapsed ? "display:none;" : null)), 257 $this->list_tree_html($folder, $data, $jsenv, $attrib)); 258 } 259 260 if (strlen($content)) { 261 $out .= html::tag('li', array( 262 'id' => 'rcmlical' . rcube_utils::html_identifier($id), 263 'class' => $prop['group'] . ($prop['virtual'] ? ' virtual' : ''), 264 ), 265 $content); 266 } 267 } 268 269 return $out; 270 } 271 272 /** 273 * Helper method to build a calendar list item (HTML content and js data) 274 */ 275 public function calendar_list_item($id, $prop, &$jsenv, $activeonly = false) 276 { 277 // enrich calendar properties with settings from the driver 278 if (!$prop['virtual']) { 279 unset($prop['user_id']); 280 $prop['alarms'] = $this->cal->driver->alarms; 281 $prop['attendees'] = $this->cal->driver->attendees; 282 $prop['freebusy'] = $this->cal->driver->freebusy; 283 $prop['attachments'] = $this->cal->driver->attachments; 284 $prop['undelete'] = $this->cal->driver->undelete; 285 $prop['feedurl'] = $this->cal->get_url(array('_cal' => $this->cal->ical_feed_hash($id) . '.ics', 'action' => 'feed')); 286 287 $jsenv[$id] = $prop; 288 } 289 290 $classes = array('calendar', 'cal-' . asciiwords($id, true)); 291 $title = $prop['title'] ?: ($prop['name'] != $prop['listname'] || strlen($prop['name']) > 25 ? 292 html_entity_decode($prop['name'], ENT_COMPAT, RCUBE_CHARSET) : ''); 293 294 if ($prop['virtual']) 295 $classes[] = 'virtual'; 296 else if (!$prop['editable']) 297 $classes[] = 'readonly'; 298 if ($prop['subscribed']) 299 $classes[] = 'subscribed'; 300 if ($prop['subscribed'] === 2) 301 $classes[] = 'partial'; 302 if ($prop['class']) 303 $classes[] = $prop['class']; 304 305 $content = ''; 306 if (!$activeonly || $prop['active']) { 307 $label_id = 'cl:' . $id; 308 $content = html::div(join(' ', $classes), 309 html::a(array('class' => 'calname', 'id' => $label_id, 'title' => $title, 'href' => '#'), rcube::Q($prop['editname'] ?: $prop['listname'])) 310 . ($prop['virtual'] ? '' : 311 html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => $prop['active'], 'aria-labelledby' => $label_id)) . 312 html::span('actions', 313 ($prop['removable'] ? html::a(array('href' => '#', 'class' => 'remove', 'title' => $this->cal->gettext('removelist')), ' ') : '') . 314 html::a(array('href' => '#', 'class' => 'quickview', 'title' => $this->cal->gettext('quickview'), 'role' => 'checkbox', 'aria-checked' => 'false'), '') . 315 (isset($prop['subscribed']) ? html::a(array('href' => '#', 'class' => 'subscribed', 'title' => $this->cal->gettext('calendarsubscribe'), 'role' => 'checkbox', 'aria-checked' => $prop['subscribed'] ? 'true' : 'false'), ' ') : '') 316 ) . 317 html::span(array('class' => 'handle', 'style' => "background-color: #" . ($prop['color'] ?: 'f00')), ' ') 318 ) 319 ); 320 } 321 322 return $content; 323 } 324 325 /** 326 * Render a HTML for agenda options form 327 */ 328 function agenda_options($attrib = array()) 329 { 330 $attrib += array('id' => 'agendaoptions'); 331 $attrib['style'] .= 'display:none'; 332 333 $select_range = new html_select(array('name' => 'listrange', 'id' => 'agenda-listrange', 'class' => 'form-control custom-select')); 334 $select_range->add(1 . ' ' . preg_replace('/\(.+\)/', '', $this->cal->lib->gettext('days')), $days); 335 foreach (array(2,5,7,14,30,60,90,180,365) as $days) 336 $select_range->add($days . ' ' . preg_replace('/\(|\)/', '', $this->cal->lib->gettext('days')), $days); 337 338 $html = html::span('input-group', 339 html::label(array('for' => 'agenda-listrange', 'class' => 'input-group-prepend'), 340 html::span('input-group-text', $this->cal->gettext('listrange'))) 341 . $select_range->show($this->rc->config->get('calendar_agenda_range', $this->cal->defaults['calendar_agenda_range'])) 342 ); 343 344 return html::div($attrib, $html); 345 } 346 347 /** 348 * Render a HTML select box for calendar selection 349 */ 350 function calendar_select($attrib = array()) 351 { 352 $attrib['name'] = 'calendar'; 353 $attrib['is_escaped'] = true; 354 $select = new html_select($attrib); 355 356 foreach ((array)$this->cal->driver->list_calendars() as $id => $prop) { 357 if ($prop['editable'] || strpos($prop['rights'], 'i') !== false) 358 $select->add($prop['name'], $id); 359 } 360 361 return $select->show(null); 362 } 363 364 /** 365 * Render a HTML select box for user identity selection 366 */ 367 function identity_select($attrib = array()) 368 { 369 $attrib['name'] = 'identity'; 370 $select = new html_select($attrib); 371 $identities = $this->rc->user->list_emails(); 372 373 foreach ($identities as $ident) { 374 $select->add(format_email_recipient($ident['email'], $ident['name']), $ident['identity_id']); 375 } 376 377 return $select->show(null); 378 } 379 380 /** 381 * Render a HTML select box to select an event category 382 */ 383 function category_select($attrib = array()) 384 { 385 $attrib['name'] = 'categories'; 386 $select = new html_select($attrib); 387 $select->add('---', ''); 388 foreach (array_keys((array)$this->cal->driver->list_categories()) as $cat) { 389 $select->add($cat, $cat); 390 } 391 392 return $select->show(null); 393 } 394 395 /** 396 * Render a HTML select box for status property 397 */ 398 function status_select($attrib = array()) 399 { 400 $attrib['name'] = 'status'; 401 $select = new html_select($attrib); 402 $select->add('---', ''); 403 $select->add($this->cal->gettext('status-confirmed'), 'CONFIRMED'); 404 $select->add($this->cal->gettext('status-cancelled'), 'CANCELLED'); 405 $select->add($this->cal->gettext('status-tentative'), 'TENTATIVE'); 406 return $select->show(null); 407 } 408 409 /** 410 * Render a HTML select box for free/busy/out-of-office property 411 */ 412 function freebusy_select($attrib = array()) 413 { 414 $attrib['name'] = 'freebusy'; 415 $select = new html_select($attrib); 416 $select->add($this->cal->gettext('free'), 'free'); 417 $select->add($this->cal->gettext('busy'), 'busy'); 418 // out-of-office is not supported by libkolabxml (#3220) 419 // $select->add($this->cal->gettext('outofoffice'), 'outofoffice'); 420 $select->add($this->cal->gettext('tentative'), 'tentative'); 421 return $select->show(null); 422 } 423 424 /** 425 * Render a HTML select for event priorities 426 */ 427 function priority_select($attrib = array()) 428 { 429 $attrib['name'] = 'priority'; 430 $select = new html_select($attrib); 431 $select->add('---', '0'); 432 $select->add('1 '.$this->cal->gettext('highest'), '1'); 433 $select->add('2 '.$this->cal->gettext('high'), '2'); 434 $select->add('3 ', '3'); 435 $select->add('4 ', '4'); 436 $select->add('5 '.$this->cal->gettext('normal'), '5'); 437 $select->add('6 ', '6'); 438 $select->add('7 ', '7'); 439 $select->add('8 '.$this->cal->gettext('low'), '8'); 440 $select->add('9 '.$this->cal->gettext('lowest'), '9'); 441 return $select->show(null); 442 } 443 444 /** 445 * Render HTML input for sensitivity selection 446 */ 447 function sensitivity_select($attrib = array()) 448 { 449 $attrib['name'] = 'sensitivity'; 450 $select = new html_select($attrib); 451 $select->add($this->cal->gettext('public'), 'public'); 452 $select->add($this->cal->gettext('private'), 'private'); 453 $select->add($this->cal->gettext('confidential'), 'confidential'); 454 return $select->show(null); 455 } 456 457 /** 458 * Render HTML form for alarm configuration 459 */ 460 function alarm_select($attrib = array()) 461 { 462 return $this->cal->lib->alarm_select($attrib, $this->cal->driver->alarm_types, $this->cal->driver->alarm_absolute); 463 } 464 465 /** 466 * Render HTML for attendee notification warning 467 */ 468 function edit_attendees_notify($attrib = array()) 469 { 470 $checkbox = new html_checkbox(array('name' => '_notify', 'id' => 'edit-attendees-donotify', 'value' => 1, 'class' => 'pretty-checkbox')); 471 return html::div($attrib, html::label(null, $checkbox->show(1) . ' ' . $this->cal->gettext('sendnotifications'))); 472 } 473 474 /** 475 * Render HTML for recurrence option to align start date with the recurrence rule 476 */ 477 function edit_recurrence_sync($attrib = array()) 478 { 479 $checkbox = new html_checkbox(array('name' => '_start_sync', 'value' => 1, 'class' => 'pretty-checkbox')); 480 return html::div($attrib, html::label(null, $checkbox->show(1) . ' ' . $this->cal->gettext('eventstartsync'))); 481 } 482 483 /** 484 * Generate the form for recurrence settings 485 */ 486 function recurring_event_warning($attrib = array()) 487 { 488 $attrib['id'] = 'edit-recurring-warning'; 489 490 $radio = new html_radiobutton(array('name' => '_savemode', 'class' => 'edit-recurring-savemode')); 491 $form = html::label(null, $radio->show('', array('value' => 'current')) . $this->cal->gettext('currentevent')) . ' ' . 492 html::label(null, $radio->show('', array('value' => 'future')) . $this->cal->gettext('futurevents')) . ' ' . 493 html::label(null, $radio->show('all', array('value' => 'all')) . $this->cal->gettext('allevents')) . ' ' . 494 html::label(null, $radio->show('', array('value' => 'new')) . $this->cal->gettext('saveasnew')); 495 496 return html::div($attrib, html::div('message', $this->cal->gettext('changerecurringeventwarning')) . html::div('savemode', $form)); 497 } 498 499 /** 500 * Form for uploading and importing events 501 */ 502 function events_import_form($attrib = array()) 503 { 504 if (!$attrib['id']) 505 $attrib['id'] = 'rcmImportForm'; 506 507 // Get max filesize, enable upload progress bar 508 $max_filesize = $this->rc->upload_init(); 509 510 $accept = '.ics, text/calendar, text/x-vcalendar, application/ics'; 511 if (class_exists('ZipArchive', false)) { 512 $accept .= ', .zip, application/zip'; 513 } 514 515 $input = new html_inputfield(array( 516 'id' => 'importfile', 517 'type' => 'file', 518 'name' => '_data', 519 'size' => $attrib['uploadfieldsize'], 520 'accept' => $accept 521 )); 522 523 $select = new html_select(array('name' => '_range', 'id' => 'event-import-range')); 524 $select->add(array( 525 $this->cal->gettext('onemonthback'), 526 $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>2))), 527 $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>3))), 528 $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>6))), 529 $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>12))), 530 $this->cal->gettext('all'), 531 ), 532 array('1','2','3','6','12',0)); 533 534 $html = html::div('form-section form-group row', 535 html::label(array('class' => 'col-sm-4 col-form-label', 'for' => 'importfile'), rcube::Q($this->rc->gettext('importfromfile'))) 536 . html::div('col-sm-8', $input->show() 537 . html::div('hint', $this->rc->gettext(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize))))) 538 ); 539 540 $html .= html::div('form-section form-group row', 541 html::label(array('for' => 'event-import-calendar', 'class' => 'col-form-label col-sm-4'), $this->cal->gettext('calendar')) 542 . html::div('col-sm-8', $this->calendar_select(array('name' => 'calendar', 'id' => 'event-import-calendar'))) 543 ); 544 545 $html .= html::div('form-section form-group row', 546 html::label(array('for' => 'event-import-range', 'class' => 'col-form-label col-sm-4'), $this->cal->gettext('importrange')) 547 . html::div('col-sm-8', $select->show(1)) 548 ); 549 550 $this->rc->output->add_gui_object('importform', $attrib['id']); 551 $this->rc->output->add_label('import'); 552 553 return html::tag('p', null, $this->cal->gettext('importtext')) 554 . html::tag('form', array( 555 'action' => $this->rc->url(array('task' => 'calendar', 'action' => 'import_events')), 556 'method' => 'post', 557 'enctype' => 'multipart/form-data', 558 'id' => $attrib['id'] 559 ), $html); 560 } 561 562 /** 563 * Form to select options for exporting events 564 */ 565 function events_export_form($attrib = array()) 566 { 567 if (!$attrib['id']) 568 $attrib['id'] = 'rcmExportForm'; 569 570 $html = html::div('form-section form-group row', 571 html::label(array('for' => 'event-export-calendar', 'class' => 'col-sm-4 col-form-label'), $this->cal->gettext('calendar')) 572 . html::div('col-sm-8', $this->calendar_select(array('name' => 'calendar', 'id' => 'event-export-calendar', 'class' => 'form-control custom-select')))); 573 574 $select = new html_select(array('name' => 'range', 'id' => 'event-export-range', 'class' => 'form-control custom-select rounded-right')); 575 $select->add(array( 576 $this->cal->gettext('all'), 577 $this->cal->gettext('onemonthback'), 578 $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>2))), 579 $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>3))), 580 $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>6))), 581 $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>12))), 582 $this->cal->gettext('customdate'), 583 ), 584 array(0,'1','2','3','6','12','custom')); 585 586 $startdate = new html_inputfield(array('name' => 'start', 'size' => 11, 'id' => 'event-export-startdate', 'style' => 'display:none')); 587 588 $html .= html::div('form-section form-group row', 589 html::label(array('for' => 'event-export-range', 'class' => 'col-sm-4 col-form-label'), $this->cal->gettext('exportrange')) 590 . html::div('col-sm-8 input-group', $select->show(0) . $startdate->show())); 591 592 $checkbox = new html_checkbox(array('name' => 'attachments', 'id' => 'event-export-attachments', 'value' => 1, 'class' => 'form-check-input pretty-checkbox')); 593 $html .= html::div('form-section form-check row', 594 html::label(array('for' => 'event-export-attachments', 'class' => 'col-sm-4 col-form-label'), $this->cal->gettext('exportattachments')) 595 . html::div('col-sm-8', $checkbox->show(1))); 596 597 $this->rc->output->add_gui_object('exportform', $attrib['id']); 598 599 return html::tag('form', $attrib + array( 600 'action' => $this->rc->url(array('task' => 'calendar', 'action' => 'export_events')), 601 'method' => "post", 602 'id' => $attrib['id'] 603 ), 604 $html 605 ); 606 } 607 608 /** 609 * Handler for calendar form template. 610 * The form content could be overriden by the driver 611 */ 612 function calendar_editform($action, $calendar = array()) 613 { 614 $this->action = $action; 615 $this->calendar = $calendar; 616 617 // load miniColors js/css files 618 jqueryui::miniColors(); 619 620 $this->rc->output->set_env('pagetitle', $this->cal->gettext('calendarprops')); 621 $this->rc->output->add_handler('folderform', array($this, 'calendarform')); 622 $this->rc->output->send('libkolab.folderform'); 623 } 624 625 /** 626 * Handler for calendar form template. 627 * The form content could be overriden by the driver 628 */ 629 function calendarform($attrib) 630 { 631 // compose default calendar form fields 632 $input_name = new html_inputfield(array('name' => 'name', 'id' => 'calendar-name', 'size' => 20)); 633 $input_color = new html_inputfield(array('name' => 'color', 'id' => 'calendar-color', 'size' => 7, 'class' => 'colors')); 634 635 $formfields = array( 636 'name' => array( 637 'label' => $this->cal->gettext('name'), 638 'value' => $input_name->show($calendar['name']), 639 'id' => 'calendar-name', 640 ), 641 'color' => array( 642 'label' => $this->cal->gettext('color'), 643 'value' => $input_color->show($calendar['color']), 644 'id' => 'calendar-color', 645 ), 646 ); 647 648 if ($this->cal->driver->alarms) { 649 $checkbox = new html_checkbox(array('name' => 'showalarms', 'id' => 'calendar-showalarms', 'value' => 1)); 650 $formfields['showalarms'] = array( 651 'label' => $this->cal->gettext('showalarms'), 652 'value' => $checkbox->show($this->calendar['showalarms'] ? 1 :0), 653 'id' => 'calendar-showalarms', 654 ); 655 } 656 657 // allow driver to extend or replace the form content 658 return html::tag('form', $attrib + array('action' => "#", 'method' => "get", 'id' => 'calendarpropform'), 659 $this->cal->driver->calendar_form($this->action, $this->calendar, $formfields) 660 ); 661 } 662 663 /** 664 * 665 */ 666 function attendees_list($attrib = array()) 667 { 668 // add "noreply" checkbox to attendees table only 669 $invitations = strpos($attrib['id'], 'attend') !== false; 670 671 $invite = new html_checkbox(array('value' => 1, 'id' => 'edit-attendees-invite')); 672 $table = new html_table(array('cols' => 5 + intval($invitations), 'border' => 0, 'cellpadding' => 0, 'class' => 'rectable')); 673 674 $table->add_header('role', $this->cal->gettext('role')); 675 $table->add_header('name', $this->cal->gettext($attrib['coltitle'] ?: 'attendee')); 676 $table->add_header('availability', $this->cal->gettext('availability')); 677 $table->add_header('confirmstate', $this->cal->gettext('confirmstate')); 678 if ($invitations) { 679 $table->add_header(array('class' => 'invite', 'title' => $this->cal->gettext('sendinvitations')), 680 $invite->show(1) . html::label('edit-attendees-invite', html::span('inner', $this->cal->gettext('sendinvitations')))); 681 } 682 $table->add_header('options', ''); 683 684 // hide invite column if disabled by config 685 $itip_notify = (int)$this->rc->config->get('calendar_itip_send_option', $this->cal->defaults['calendar_itip_send_option']); 686 if ($invitations && !($itip_notify & 2)) { 687 $css = sprintf('#%s td.invite, #%s th.invite { display:none !important }', $attrib['id'], $attrib['id']); 688 $this->rc->output->add_footer(html::tag('style', array('type' => 'text/css'), $css)); 689 } 690 691 return $table->show($attrib); 692 } 693 694 /** 695 * 696 */ 697 function attendees_form($attrib = array()) 698 { 699 $input = new html_inputfield(array('name' => 'participant', 'id' => 'edit-attendee-name', 'class' => 'form-control')); 700 $textarea = new html_textarea(array('name' => 'comment', 'id' => 'edit-attendees-comment', 'class' => 'form-control', 701 'rows' => 4, 'cols' => 55, 'title' => $this->cal->gettext('itipcommenttitle'))); 702 703 return html::div($attrib, 704 html::div('form-searchbar', $input->show() . " " . 705 html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-attendee-add', 'value' => $this->cal->gettext('addattendee'))) . " " . 706 html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-attendee-schedule', 'value' => $this->cal->gettext('scheduletime').'...'))) . 707 html::p('attendees-commentbox', html::label('edit-attendees-comment', $this->cal->gettext('itipcomment')) . $textarea->show()) 708 ); 709 } 710 711 /** 712 * 713 */ 714 function resources_form($attrib = array()) 715 { 716 $input = new html_inputfield(array('name' => 'resource', 'id' => 'edit-resource-name', 'class' => 'form-control')); 717 718 return html::div($attrib, 719 html::div('form-searchbar', $input->show() . " " . 720 html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-resource-add', 'value' => $this->cal->gettext('addresource'))) . " " . 721 html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-resource-find', 'value' => $this->cal->gettext('findresources').'...'))) 722 ); 723 } 724 725 /** 726 * 727 */ 728 function resources_list($attrib = array()) 729 { 730 $attrib += array('id' => 'calendar-resources-list'); 731 732 $this->rc->output->add_gui_object('resourceslist', $attrib['id']); 733 734 return html::tag('ul', $attrib, '', html::$common_attrib); 735 } 736 737 /** 738 * 739 */ 740 public function resource_info($attrib = array()) 741 { 742 $attrib += array('id' => 'calendar-resources-info'); 743 744 $this->rc->output->add_gui_object('resourceinfo', $attrib['id']); 745 $this->rc->output->add_gui_object('resourceownerinfo', $attrib['id'] . '-owner'); 746 747 // copy address book labels for owner details to client 748 $this->rc->output->add_label('name','firstname','surname','department','jobtitle','email','phone','address'); 749 750 $table_attrib = array('id','class','style','width','summary','cellpadding','cellspacing','border'); 751 752 return html::tag('table', $attrib, 753 html::tag('tbody', null, ''), $table_attrib) . 754 755 html::tag('table', array('id' => $attrib['id'] . '-owner', 'style' => 'display:none') + $attrib, 756 html::tag('thead', null, 757 html::tag('tr', null, 758 html::tag('td', array('colspan' => 2), rcube::Q($this->cal->gettext('resourceowner'))) 759 ) 760 ) . 761 html::tag('tbody', null, ''), 762 $table_attrib); 763 } 764 765 /** 766 * 767 */ 768 public function resource_calendar($attrib = array()) 769 { 770 $attrib += array('id' => 'calendar-resources-calendar'); 771 772 $this->rc->output->add_gui_object('resourceinfocalendar', $attrib['id']); 773 774 return html::div($attrib, ''); 775 } 776 777 /** 778 * GUI object 'searchform' for the resource finder dialog 779 * 780 * @param array Named parameters 781 * @return string HTML code for the gui object 782 */ 783 function resources_search_form($attrib) 784 { 785 $attrib += array( 786 'command' => 'search-resource', 787 'reset-command' => 'reset-resource-search', 788 'id' => 'rcmcalresqsearchbox', 789 'autocomplete' => 'off', 790 'form-name' => 'rcmcalresoursqsearchform', 791 'gui-object' => 'resourcesearchform', 792 ); 793 794 // add form tag around text field 795 return $this->rc->output->search_form($attrib); 796 } 797 798 /** 799 * 800 */ 801 function attendees_freebusy_table($attrib = array()) 802 { 803 $table = new html_table(array('cols' => 2, 'border' => 0, 'cellspacing' => 0)); 804 $table->add('attendees', 805 html::tag('h3', 'boxtitle', $this->cal->gettext('tabattendees')) . 806 html::div('timesheader', ' ') . 807 html::div(array('id' => 'schedule-attendees-list', 'class' => 'attendees-list'), '') 808 ); 809 $table->add('times', 810 html::div('scroll', 811 html::tag('table', array('id' => 'schedule-freebusy-times', 'border' => 0, 'cellspacing' => 0), html::tag('thead') . html::tag('tbody')) . 812 html::div(array('id' => 'schedule-event-time', 'style' => 'display:none'), ' ') 813 ) 814 ); 815 816 return $table->show($attrib); 817 } 818 819 /** 820 * 821 */ 822 function event_invitebox($attrib = array()) 823 { 824 if ($this->cal->event) { 825 return html::div($attrib, 826 $this->cal->itip->itip_object_details_table($this->cal->event, $this->cal->itip->gettext('itipinvitation')) . 827 $this->cal->invitestatus 828 ); 829 } 830 831 return ''; 832 } 833 834 function event_rsvp_buttons($attrib = array()) 835 { 836 $actions = array('accepted','tentative','declined'); 837 if ($attrib['delegate'] !== 'false') 838 $actions[] = 'delegated'; 839 840 return $this->cal->itip->itip_rsvp_buttons($attrib, $actions); 841 } 842 843} 844