1<?php 2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project 3// 4// All Rights Reserved. See copyright.txt for details and a complete list of authors. 5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details. 6// $Id$ 7 8use Tiki\Package\VendorHelper; 9 10function wikiplugin_trackercalendar_info() 11{ 12 global $prefs; 13 14 return [ 15 'name' => tr('Tracker Calendar'), 16 'description' => tr('Create and display a calendar using tracker data'), 17 'prefs' => ['wikiplugin_trackercalendar', 'calendar_fullcalendar'], 18 'packages_required' => ['fullcalendar/fullcalendar-scheduler' => VendorHelper::getAvailableVendorPath('fullcalendarscheduler', 'fullcalendar/fullcalendar-scheduler/dist/scheduler.min.js')], 19 'format' => 'html', 20 'iconname' => 'calendar', 21 'introduced' => 10, 22 'params' => [ 23 'trackerId' => [ 24 'name' => tr('Tracker ID'), 25 'description' => tr('Tracker to search from'), 26 'since' => '10.0', 27 'required' => false, 28 'default' => 0, 29 'filter' => 'int', 30 'profile_reference' => 'tracker', 31 ], 32 'begin' => [ 33 'name' => tr('Begin Date Field'), 34 'description' => tr('Permanent name of the field to use for event beginning'), 35 'since' => '10.0', 36 'required' => true, 37 'filter' => 'word', 38 ], 39 'end' => [ 40 'name' => tr('End Date Field'), 41 'description' => tr('Permanent name of the field to use for event ending'), 42 'since' => '10.0', 43 'required' => true, 44 'filter' => 'word', 45 ], 46 'resource' => [ 47 'name' => tr('Resource Descriptor Field'), 48 'description' => tr('Permanent name of the field to use as the resource indicator'), 49 'since' => '10.0', 50 'required' => false, 51 'filter' => 'word', 52 ], 53 'coloring' => [ 54 'name' => tr('Coloring Discriminator Field'), 55 'description' => tr('Permanent name of the field to use to segment the information into color schemes.'), 56 'since' => '10.0', 57 'required' => false, 58 'filter' => 'word', 59 ], 60 'external' => [ 61 'required' => false, 62 'name' => tra('External Link'), 63 'description' => tra('Follow external link when event item is clicked. Useful for supporting links to 64 pretty tracker supported pages.'), 65 'since' => '12.4', 66 'filter' => 'alpha', 67 'default' => 'n', 68 'options' => [ 69 ['text' => '', 'value' => ''], 70 ['text' => tra('Yes'), 'value' => 'y'], 71 ['text' => tra('No'), 'value' => 'n'] 72 ] 73 ], 74 'url' => [ 75 'required' => false, 76 'name' => tra('URL'), 77 'description' => tra('Complete URL, internal or external.'), 78 'since' => '12.4', 79 'filter' => 'url', 80 'default' => '', 81 'parentparam' => ['name' => 'external', 'value' => 'y'], 82 ], 83 'trkitemid' => [ 84 'required' => false, 85 'name' => tra('Tracker Item Id'), 86 'description' => tr('If Yes (%0y%1) the item id will be passed as %0itemId%1, which can be used 87 by Tracker plugins. Will be passed as %0itemid%1 if No (%0n%1)', '<code>', '</code>'), 88 'since' => '12.4', 89 'filter' => 'alpha', 90 'default' => 'n', 91 'options' => [ 92 ['text' => '', 'value' => ''], 93 ['text' => tra('Yes'), 'value' => 'y'], 94 ['text' => tra('No'), 'value' => 'n'] 95 ], 96 'parentparam' => ['name' => 'external', 'value' => 'y'], 97 ], 98 'addAllFields' => [ 99 'required' => false, 100 'name' => tra('Add All Fields'), 101 'description' => tr('If Yes (%0y%1) all fields in the tracker will be added to the URL, not just the 102 itemId', '<code>', '</code>'), 103 'since' => '12.4', 104 'filter' => 'alpha', 105 'default' => 'y', 106 'options' => [ 107 ['text' => '', 'value' => ''], 108 ['text' => tra('Yes'), 'value' => 'y'], 109 ['text' => tra('No'), 'value' => 'n'] 110 ], 111 'parentparam' => ['name' => 'external', 'value' => 'y'], 112 ], 113 'useSessionStorage' => [ 114 'required' => false, 115 'name' => tra('Use Session Storage'), 116 'description' => tr('If Yes (%0y%1) copy all the field values into window.sessionStorage so it can be 117 accessed via JavaScript.', '<code>', '</code>'), 118 'since' => '12.4', 119 'filter' => 'alpha', 120 'default' => 'y', 121 'options' => [ 122 ['text' => '', 'value' => ''], 123 ['text' => tra('Yes'), 'value' => 'y'], 124 ['text' => tra('No'), 'value' => 'n'] 125 ], 126 'parentparam' => ['name' => 'addAllFields', 'value' => 'y'], 127 ], 128 'amonth' => [ 129 'required' => false, 130 'name' => tra('Agenda by Months'), 131 'description' => tra('Display the option to change the view to agenda by months'), 132 'since' => '12.1', 133 'filter' => 'alpha', 134 'default' => 'y', 135 'options' => [ 136 ['text' => '', 'value' => ''], 137 ['text' => tra('Yes'), 'value' => 'y'], 138 ['text' => tra('No'), 'value' => 'n'] 139 ] 140 ], 141 'aweek' => [ 142 'required' => false, 143 'name' => tra('Agenda by Weeks'), 144 'description' => tra('Display the option to change the view to agenda by weeks'), 145 'since' => '12.1', 146 'filter' => 'alpha', 147 'default' => 'y', 148 'options' => [ 149 ['text' => '', 'value' => ''], 150 ['text' => tra('Yes'), 'value' => 'y'], 151 ['text' => tra('No'), 'value' => 'n'] 152 ] 153 ], 154 'aday' => [ 155 'required' => false, 156 'name' => tra('Agenda by Days'), 157 'description' => tra('Display the option to change the view to agenda by days'), 158 'since' => '12.1', 159 'filter' => 'alpha', 160 'default' => 'y', 161 'options' => [ 162 ['text' => '', 'value' => ''], 163 ['text' => tra('Yes'), 'value' => 'y'], 164 ['text' => tra('No'), 'value' => 'n'] 165 ] 166 ], 167 'lyear' => [ 168 'required' => false, 169 'name' => tra('List by Years'), 170 'description' => tra('Display the option to change the view to list by years'), 171 'since' => '20.1', 172 'filter' => 'alpha', 173 'default' => 'y', 174 'options' => [ 175 ['text' => '', 'value' => ''], 176 ['text' => tra('Yes'), 'value' => 'y'], 177 ['text' => tra('No'), 'value' => 'n'] 178 ] 179 ], 180 'lmonth' => [ 181 'required' => false, 182 'name' => tra('List by Months'), 183 'description' => tra('Display the option to change the view to list by months'), 184 'since' => '20.1', 185 'filter' => 'alpha', 186 'default' => 'y', 187 'options' => [ 188 ['text' => '', 'value' => ''], 189 ['text' => tra('Yes'), 'value' => 'y'], 190 ['text' => tra('No'), 'value' => 'n'] 191 ] 192 ], 193 'lweek' => [ 194 'required' => false, 195 'name' => tra('List by Weeks'), 196 'description' => tra('Display the option to change the view to list by weeks'), 197 'since' => '20.1', 198 'filter' => 'alpha', 199 'default' => 'y', 200 'options' => [ 201 ['text' => '', 'value' => ''], 202 ['text' => tra('Yes'), 'value' => 'y'], 203 ['text' => tra('No'), 'value' => 'n'] 204 ] 205 ], 206 'lday' => [ 207 'required' => false, 208 'name' => tra('List by Days'), 209 'description' => tra('Display the option to change the view to list by days'), 210 'since' => '20.1', 211 'filter' => 'alpha', 212 'default' => 'y', 213 'options' => [ 214 ['text' => '', 'value' => ''], 215 ['text' => tra('Yes'), 'value' => 'y'], 216 ['text' => tra('No'), 'value' => 'n'] 217 ] 218 ], 219 'ryear' => [ 220 'required' => false, 221 'name' => tra('Resources by Years'), 222 'description' => tra('Display the option to change the view to resources by years'), 223 'since' => '20.1', 224 'filter' => 'alpha', 225 'default' => 'y', 226 'options' => [ 227 ['text' => '', 'value' => ''], 228 ['text' => tra('Yes'), 'value' => 'y'], 229 ['text' => tra('No'), 'value' => 'n'] 230 ] 231 ], 232 'rmonth' => [ 233 'required' => false, 234 'name' => tra('Resources by Months'), 235 'description' => tra('Display the option to change the view to resources by months'), 236 'since' => '12.1', 237 'filter' => 'alpha', 238 'default' => 'y', 239 'options' => [ 240 ['text' => '', 'value' => ''], 241 ['text' => tra('Yes'), 'value' => 'y'], 242 ['text' => tra('No'), 'value' => 'n'] 243 ] 244 ], 245 'rweek' => [ 246 'required' => false, 247 'name' => tra('Resources by Weeks'), 248 'description' => tra('Display the option to change the view to resources by weeks'), 249 'since' => '12.1', 250 'filter' => 'alpha', 251 'default' => 'y', 252 'options' => [ 253 ['text' => '', 'value' => ''], 254 ['text' => tra('Yes'), 'value' => 'y'], 255 ['text' => tra('No'), 'value' => 'n'] 256 ] 257 ], 258 'rday' => [ 259 'required' => false, 260 'name' => tra('Resources by Days'), 261 'description' => tra('Display the option to change the view to resources by days'), 262 'since' => '12.1', 263 'filter' => 'alpha', 264 'default' => 'y', 265 'options' => [ 266 ['text' => '', 'value' => ''], 267 ['text' => tra('Yes'), 'value' => 'y'], 268 ['text' => tra('No'), 'value' => 'n'] 269 ] 270 ], 271 'dView' => [ 272 'required' => false, 273 'name' => tra('Default View'), 274 'description' => tra('Choose the default view for the Tracker Calendar'), 275 'since' => '12.1', 276 'filter' => 'alpha', 277 'default' => 'month', 278 'options' => [ 279 ['text' => '', 'value' => ''], 280 ['text' => tra('Agenda by Months'), 'value' => 'month'], 281 ['text' => tra('Agenda by Weeks'), 'value' => 'agendaWeek'], 282 ['text' => tra('Agenda by Days'), 'value' => 'agendaDay'], 283 ['text' => tra('List'), 'value' => 'list'], 284 ['text' => tra('List by Months'), 'value' => 'listMonth'], 285 ['text' => tra('List by Weeks'), 'value' => 'listWeek'], 286 ['text' => tra('List by Days'), 'value' => 'listDay'], 287 ['text' => tra('Resources by Years'), 'value' => 'timelineYear'], 288 ['text' => tra('Resources by Months'), 'value' => 'timelineMonth'], 289 ['text' => tra('Resources by Weeks'), 'value' => 'timelineWeek'], 290 ['text' => tra('Resources by Days'), 'value' => 'timelineDay'] 291 ] 292 ], 293 'dYear' => [ 294 'required' => false, 295 'name' => tra('Default Year'), 296 'description' => tra('Choose the default year (yyyy) to use for the display'), 297 'since' => '12.1', 298 'default' => 0, 299 'filter' => 'int', 300 ], 301 'dMonth' => [ 302 'required' => false, 303 'name' => tra('Default Month'), 304 'description' => tra('Choose the default month (mm, as numeric value) to use for the display. Numeric 305 values here are 1-based, meaning January=1, February=2, etc'), 306 'since' => '12.1', 307 'default' => 0, 308 'filter' => 'int', 309 ], 310 'dDay' => [ 311 'required' => false, 312 'name' => tra('Default Day'), 313 'description' => tra('Choose the default day (dd) to use for the display'), 314 'since' => '12.1', 315 'default' => 0, 316 'filter' => 'int', 317 ], 318 'colormap' => [ 319 'required' => false, 320 'name' => tra('Colormap for coloring'), 321 'description' => tr('Colormap to be used when segmenting the information using the coloring field. 322 Each map is composed of value and color separated with a comma, use pipes to separate multiple colormaps: %0', '<code>1,#6cf|2,#6fc</code>'), 323 'since' => '18.0', 324 'filter' => 'text', 325 ], 326 'fDayofWeek' => [ 327 'required' => false, 328 'name' => tra('First day of the Week'), 329 'description' => tr('Choose the day that each week begins with, for the tracker calendar display. 330 The value must be a number that represents the day of the week: Sunday=0, Monday=1, Tuesday=2, 331 etc. Default: %0 (Sunday)', '<code>0</code>'), 332 'since' => '12.1', 333 'default' => 0, 334 'filter' => 'int', 335 ], 336 'weekends' => [ 337 'required' => false, 338 'name' => tra('Show Weekends'), 339 'description' => tra('Display Saturdays and Sundays (shown by default)'), 340 'filter' => 'alpha', 341 'default' => 'y', 342 'options' => [ 343 ['text' => '', 'value' => ''], 344 ['text' => tra('Yes'), 'value' => 'y'], 345 ['text' => tra('No'), 'value' => 'n'] 346 ] 347 ], 348 'minHourOfDay' => [ 349 'required' => false, 350 'name' => tra('Day Start'), 351 'description' => tr('First time slot that will be displayed for each day, e.g. %0', '07:00:00'), 352 'since' => '19.1', 353 'filter' => 'text', 354 'default' => '07:00:00', 355 ], 356 'maxHourOfDay' => [ 357 'required' => false, 358 'name' => tra('Day End'), 359 'description' => tr('Last time slot that will be displayed for each day, e.g. %0', '24:00:00'), 360 'since' => '19.1', 361 'filter' => 'text', 362 'default' => '24:00:00', 363 ], 364 'slotDuration' => [ 365 'required' => false, 366 'name' => tra('Slot Duration'), 367 'description' => tr('Frequency for displayting time slots, e.g. %0 (defaults to the calendar_timespan preference)', "00:{$prefs['calendar_timespan']}:00"), 368 'since' => '19.1', 369 'filter' => 'text', 370 'default' => "00:{$prefs['calendar_timespan']}:00", 371 ], 372 'eventOverlap' => [ 373 'required' => false, 374 'name' => tra('Overlapping allowed'), 375 'description' => tra('Allow resources to overlap in time.'), 376 'since' => '20.1', 377 'filter' => 'alpha', 378 'default' => 'y', 379 'options' => [ 380 ['text' => '', 'value' => ''], 381 ['text' => tra('Yes'), 'value' => 'y'], 382 ['text' => tra('No'), 'value' => 'n'] 383 ] 384 ], 385 ], 386 ]; 387} 388 389function wikiplugin_trackercalendar($data, $params) 390{ 391 global $prefs; 392 393 static $id = 0; 394 $headerlib = TikiLib::lib('header'); 395 $vendorPath = VendorHelper::getAvailableVendorPath('fullcalendarscheduler', 'fullcalendar/fullcalendar-scheduler/dist/scheduler.min.js', false); 396 397 if (! $vendorPath) { 398 return WikiParser_PluginOutput::userError(tr('To view Tracker Calendar Tiki needs the fullcalendar/fullcalendar-scheduler package. If you do not have permission to install this package, ask the site administrator.')); 399 } 400 401 $headerlib->add_cssfile($vendorPath . '/fullcalendar/fullcalendar/dist/fullcalendar.min.css'); 402 // Disable fullcalendar's force events to be one-line tall 403 $headerlib->add_css('.fc-day-grid-event > .fc-content, .fc-timeline-event > .fc-content { white-space: normal; }'); 404 $headerlib->add_cssfile($vendorPath . '/fullcalendar/fullcalendar-scheduler/dist/scheduler.min.css'); 405 $headerlib->add_jsfile($vendorPath . '/moment/moment/min/moment.min.js', true); 406 $headerlib->add_jsfile($vendorPath . '/fullcalendar/fullcalendar/dist/fullcalendar.min.js', true); 407 $headerlib->add_jsfile($vendorPath . '/fullcalendar/fullcalendar-scheduler/dist/scheduler.min.js', true); 408 409 $jit = new JitFilter($params); 410 $definition = Tracker_Definition::get($jit->trackerId->int()); 411 $itemObject = Tracker_Item::newItem($jit->trackerId->int()); 412 413 if (! $definition) { 414 return WikiParser_PluginOutput::userError(tr('Tracker not found.')); 415 } 416 417 $beginField = $definition->getFieldFromPermName($jit->begin->word()); 418 $endField = $definition->getFieldFromPermName($jit->end->word()); 419 420 if (! $beginField || ! $endField) { 421 return WikiParser_PluginOutput::userError(tr('Fields not found.')); 422 } 423 424 $views = []; 425 if (! empty($params['amonth']) and $params['amonth'] != 'y') { 426 $amonth = 'n'; 427 } else { 428 $amonth = 'y'; 429 $views[] = 'month'; 430 } 431 if (! empty($params['aweek']) and $params['aweek'] != 'y') { 432 $aweek = 'n'; 433 } else { 434 $aweek = 'y'; 435 $views[] = 'agendaWeek'; 436 } 437 if (! empty($params['aday']) and $params['aday'] != 'y') { 438 $aday = 'n'; 439 } else { 440 $aday = 'y'; 441 $views[] = 'agendaDay'; 442 } 443 if (! empty($params['lyear']) and $params['lyear'] != 'y') { 444 $lyear = 'n'; 445 } else { 446 $lyear = 'y'; 447 $views[] = 'listYear'; 448 } 449 if (! empty($params['lmonth']) and $params['lmonth'] != 'y') { 450 $lmonth = 'n'; 451 } else { 452 $lmonth = 'y'; 453 $views[] = 'listMonth'; 454 } 455 if (! empty($params['lweek']) and $params['lweek'] != 'y') { 456 $lweek = 'n'; 457 } else { 458 $lweek = 'y'; 459 $views[] = 'listWeek'; 460 } 461 if (! empty($params['lday']) and $params['lday'] != 'y') { 462 $lday = 'n'; 463 } else { 464 $lday = 'y'; 465 $views[] = 'listDay'; 466 } 467 468 $resources = []; 469 if ($resourceField = $jit->resource->word()) { 470 $field = $definition->getFieldFromPermName($resourceField); 471 $resources = wikiplugin_trackercalendar_get_resources($field); 472 473 if (! empty($params['ryear']) and $params['ryear'] != 'y') { 474 $ryear = 'n'; 475 } else { 476 $ryear = 'y'; 477 $views[] = 'timelineYear'; 478 } 479 if (! empty($params['rmonth']) and $params['rmonth'] != 'y') { 480 $rmonth = 'n'; 481 } else { 482 $rmonth = 'y'; 483 $views[] = 'timelineMonth'; 484 } 485 if (! empty($params['rweek']) and $params['rweek'] != 'y') { 486 $rweek = 'n'; 487 } else { 488 $rweek = 'y'; 489 $views[] = 'timelineWeek'; 490 } 491 if (! empty($params['rday']) and $params['rday'] != 'y') { 492 $rday = 'n'; 493 } else { 494 $rday = 'y'; 495 $views[] = 'timelineDay'; 496 } 497 } 498 499 // Define the default View (dView) 500 if (! empty($params['dView'])) { 501 $dView = $params['dView'] == 'resourceWeek' ? 'timelineWeek' : $params['dView']; 502 } else { 503 $dView = 'month'; 504 } 505 506 // Define the default date (dYear, dMonth, dDay) 507 if (! empty($params['dYear'])) { 508 $dYear = $params['dYear']; 509 } else { 510 $dYear = (int) date('Y'); 511 } 512 if (! empty($params['dMonth']) and $params['dMonth'] > 0 and $params['dMonth'] < 13) { 513 $dMonth = $params['dMonth']; 514 } else { 515 $dMonth = (int) date('n'); 516 } 517 if (! empty($params['dDay']) and $params['dDay'] > 0 and $params['dDay'] < 32) { 518 $dDay = $params['dDay']; 519 } else { 520 $dDay = (int) date('j'); 521 } 522 // day duration 523 if (! empty($params['minHourOfDay'])) { 524 $minHourOfDay = $params['minHourOfDay']; 525 } else { 526 $minHourOfDay = '07:00:00'; 527 } 528 if (! empty($params['maxHourOfDay'])) { 529 $maxHourOfDay = $params['maxHourOfDay']; 530 } else { 531 $maxHourOfDay = '24:00:00'; 532 } 533 if (! empty($params['slotDuration'])) { 534 $slotDuration = $params['slotDuration']; 535 } else { 536 $slotDuration = "00:{$prefs['calendar_timespan']}:00"; 537 } 538 if (! empty($params['eventOverlap']) and $params['eventOverlap'] != 'y') { 539 $eventOverlap = false; 540 } else { 541 $eventOverlap = true; 542 } 543 544 // Format the default date as Y-m-d instead of Y-n-d, required by MomentJs 545 $dDate = (new DateTime($dYear . '-' . $dMonth . '-' . $dDay))->format('Y-m-d'); 546 547 if (! empty($params['fDayofWeek']) and $params['fDayofWeek'] > -1 and $params['fDayofWeek'] < 7) { 548 $firstDayofWeek = $params['fDayofWeek']; 549 } elseif ($prefs['calendar_firstDayofWeek'] !== 'user') { 550 $firstDayofWeek = $prefs['calendar_firstDayofWeek']; 551 } else { 552 $firstDayofWeek = 0; 553 } 554 555 $params['addAllFields'] = empty($params['addAllFields']) ? 'y' : $params['addAllFields']; 556 $params['useSessionStorage'] = empty($params['useSessionStorage']) ? 'y' : $params['useSessionStorage']; 557 $params['weekends'] = empty($params['weekends']) ? 'y' : $params['weekends']; 558 $params['external'] = $params['external'] ?? 'n'; 559 560 $matches = WikiParser_PluginMatcher::match($data); 561 $builder = new Search_Formatter_Builder; 562 $builder->apply($matches); 563 $formatter = $builder->getFormatter(); 564 $filters = str_replace(['~np~', '~/np~'], '', $formatter->renderFilters()); 565 566 $smarty = TikiLib::lib('smarty'); 567 $smarty->assign( 568 'trackercalendar', 569 [ 570 'id' => 'trackercalendar' . ++$id, 571 'trackerId' => $jit->trackerId->int(), 572 'colormap' => base64_encode($jit->colormap->none()), 573 'begin' => $jit->begin->word(), 574 'end' => $jit->end->word(), 575 'resource' => $resourceField, 576 'resourceList' => $resources, 577 'coloring' => $jit->coloring->word(), 578 'beginFieldName' => 'ins_' . $beginField['fieldId'], 579 'endFieldName' => 'ins_' . $endField['fieldId'], 580 'firstDayofWeek' => $firstDayofWeek, 581 'views' => implode(',', $views), 582 'viewyear' => $dYear, 583 'viewmonth' => $dMonth, 584 'viewday' => $dDay, 585 'dDate' => $dDate, 586 'minHourOfDay' => $minHourOfDay, 587 'maxHourOfDay' => $maxHourOfDay, 588 'slotDuration' => $slotDuration, 589 'addTitle' => tr('Insert'), 590 'canInsert' => $itemObject->canModify(), 591 'dView' => $dView, 592 'eventOverlap' => $eventOverlap, 593 'body' => $data, 594 'filterValues' => $_REQUEST, 595 'url' => $params['external'] === 'y' ? $params['url'] : '', 596 'trkitemid' => $params['external'] === 'y' ? $params['trkitemid'] : '', 597 'addAllFields' => $params['external'] === 'y' ? $params['addAllFields'] : '', 598 'useSessionStorage' => $params['external'] === 'y' ? $params['useSessionStorage'] : '', 599 'timeFormat' => $prefs['display_12hr_clock'] === 'y' ? 'h(:mm)TT' : 'HH:mm', 600 'weekends' => $params['weekends'] === 'y' ? 1 : 0, 601 'utcOffset' => TikiDate::tzServerOffset(TikiLib::lib('tiki')->get_display_timezone()) / 60, // In minutes 602 ] 603 ); 604 $smarty->assign('filters', $filters); 605 return $smarty->fetch('wiki-plugins/trackercalendar.tpl'); 606} 607 608function wikiplugin_trackercalendar_get_resources($field) 609{ 610 $db = TikiDb::get(); 611 612 return $db->fetchAll('SELECT DISTINCT LOWER(value) as id, value as title FROM tiki_tracker_item_fields WHERE fieldId = ? ORDER BY value', $field['fieldId']); 613} 614