tr('Tracker Calendar'), 'description' => tr('Create and display a calendar using tracker data'), 'prefs' => ['wikiplugin_trackercalendar'], 'packages_required' => ['npm-asset/fullcalendar-scheduler' => VendorHelper::getAvailableVendorPath('fullcalendarscheduler', 'npm-asset/fullcalendar-scheduler/dist/scheduler.min.js')], 'format' => 'html', 'iconname' => 'calendar', 'introduced' => 10, 'params' => [ 'trackerId' => [ 'name' => tr('Tracker ID'), 'description' => tr('Tracker to search from'), 'since' => '10.0', 'required' => false, 'default' => 0, 'filter' => 'int', 'profile_reference' => 'tracker', ], 'begin' => [ 'name' => tr('Begin Date Field'), 'description' => tr('Permanent name of the field to use for event beginning'), 'since' => '10.0', 'required' => true, 'filter' => 'word', ], 'end' => [ 'name' => tr('End Date Field'), 'description' => tr('Permanent name of the field to use for event ending'), 'since' => '10.0', 'required' => true, 'filter' => 'word', ], 'resource' => [ 'name' => tr('Resource Descriptor Field'), 'description' => tr('Permanent name of the field to use as the resource indicator'), 'since' => '10.0', 'required' => false, 'filter' => 'word', ], 'coloring' => [ 'name' => tr('Coloring Discriminator Field'), 'description' => tr('Permanent name of the field to use to segment the information into color schemes.'), 'since' => '10.0', 'required' => false, 'filter' => 'word', ], 'external' => [ 'required' => false, 'name' => tra('External Link'), 'description' => tra('Follow external link when event item is clicked. Useful for supporting links to pretty tracker supported pages.'), 'since' => '12.4', 'filter' => 'alpha', 'default' => 'n', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ] ], 'url' => [ 'required' => false, 'name' => tra('URL'), 'description' => tra('Complete URL, internal or external.'), 'since' => '12.4', 'filter' => 'url', 'default' => '', 'parentparam' => ['name' => 'external', 'value' => 'y'], ], 'trkitemid' => [ 'required' => false, 'name' => tra('Tracker Item Id'), 'description' => tr('If Yes (%0y%1) the item id will be passed as %0itemId%1, which can be used by Tracker plugins. Will be passed as %0itemid%1 if No (%0n%1)', '', ''), 'since' => '12.4', 'filter' => 'alpha', 'default' => 'n', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ], 'parentparam' => ['name' => 'external', 'value' => 'y'], ], 'addAllFields' => [ 'required' => false, 'name' => tra('Add All Fields'), 'description' => tr('If Yes (%0y%1) all fields in the tracker will be added to the URL, not just the itemId', '', ''), 'since' => '12.4', 'filter' => 'alpha', 'default' => 'y', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ], 'parentparam' => ['name' => 'external', 'value' => 'y'], ], 'useSessionStorage' => [ 'required' => false, 'name' => tra('Use Session Storage'), 'description' => tr('If Yes (%0y%1) copy all the field values into window.sessionStorage so it can be accessed via JavaScript.', '', ''), 'since' => '12.4', 'filter' => 'alpha', 'default' => 'y', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ], 'parentparam' => ['name' => 'addAllFields', 'value' => 'y'], ], 'amonth' => [ 'required' => false, 'name' => tra('Agenda by Months'), 'description' => tra('Display the option to change the view to agenda by months'), 'since' => '12.1', 'filter' => 'alpha', 'default' => 'y', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ] ], 'aweek' => [ 'required' => false, 'name' => tra('Agenda by Weeks'), 'description' => tra('Display the option to change the view to agenda by weeks'), 'since' => '12.1', 'filter' => 'alpha', 'default' => 'y', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ] ], 'aday' => [ 'required' => false, 'name' => tra('Agenda by Days'), 'description' => tra('Display the option to change the view to agenda by days'), 'since' => '12.1', 'filter' => 'alpha', 'default' => 'y', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ] ], 'lyear' => [ 'required' => false, 'name' => tra('List by Years'), 'description' => tra('Display the option to change the view to list by years'), 'since' => '20.1', 'filter' => 'alpha', 'default' => 'n', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ] ], 'lmonth' => [ 'required' => false, 'name' => tra('List by Months'), 'description' => tra('Display the option to change the view to list by months'), 'since' => '20.1', 'filter' => 'alpha', 'default' => 'n', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ] ], 'lweek' => [ 'required' => false, 'name' => tra('List by Weeks'), 'description' => tra('Display the option to change the view to list by weeks'), 'since' => '20.1', 'filter' => 'alpha', 'default' => 'n', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ] ], 'lday' => [ 'required' => false, 'name' => tra('List by Days'), 'description' => tra('Display the option to change the view to list by days'), 'since' => '20.1', 'filter' => 'alpha', 'default' => 'n', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ] ], 'ryear' => [ 'required' => false, 'name' => tra('Resources by Years'), 'description' => tra('Display the option to change the view to resources by years'), 'since' => '20.1', 'filter' => 'alpha', 'default' => 'n', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ] ], 'rmonth' => [ 'required' => false, 'name' => tra('Resources by Months'), 'description' => tra('Display the option to change the view to resources by months'), 'since' => '12.1', 'filter' => 'alpha', 'default' => 'y', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ] ], 'rweek' => [ 'required' => false, 'name' => tra('Resources by Weeks'), 'description' => tra('Display the option to change the view to resources by weeks'), 'since' => '12.1', 'filter' => 'alpha', 'default' => 'y', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ] ], 'rday' => [ 'required' => false, 'name' => tra('Resources by Days'), 'description' => tra('Display the option to change the view to resources by days'), 'since' => '12.1', 'filter' => 'alpha', 'default' => 'y', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ] ], 'dView' => [ 'required' => false, 'name' => tra('Default View'), 'description' => tra('Choose the default view for the Tracker Calendar'), 'since' => '12.1', 'filter' => 'alpha', 'default' => 'month', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Agenda by Months'), 'value' => 'month'], ['text' => tra('Agenda by Weeks'), 'value' => 'agendaWeek'], ['text' => tra('Agenda by Days'), 'value' => 'agendaDay'], ['text' => tra('List'), 'value' => 'list'], ['text' => tra('List by Months'), 'value' => 'listMonth'], ['text' => tra('List by Weeks'), 'value' => 'listWeek'], ['text' => tra('List by Days'), 'value' => 'listDay'], ['text' => tra('Resources by Years'), 'value' => 'timelineYear'], ['text' => tra('Resources by Months'), 'value' => 'timelineMonth'], ['text' => tra('Resources by Weeks'), 'value' => 'timelineWeek'], ['text' => tra('Resources by Days'), 'value' => 'timelineDay'] ] ], 'dYear' => [ 'required' => false, 'name' => tra('Default Year'), 'description' => tra('Choose the default year (yyyy) to use for the display'), 'since' => '12.1', 'default' => 0, 'filter' => 'int', ], 'dMonth' => [ 'required' => false, 'name' => tra('Default Month'), 'description' => tra('Choose the default month (mm, as numeric value) to use for the display. Numeric values here are 1-based, meaning January=1, February=2, etc'), 'since' => '12.1', 'default' => 0, 'filter' => 'int', ], 'dDay' => [ 'required' => false, 'name' => tra('Default Day'), 'description' => tra('Choose the default day (dd) to use for the display'), 'since' => '12.1', 'default' => 0, 'filter' => 'int', ], 'colormap' => [ 'required' => false, 'name' => tra('Colormap for coloring'), 'description' => tr('Colormap to be used when segmenting the information using the coloring field. Each map is composed of value and color separated with a comma, use pipes to separate multiple colormaps: %0', '1,#6cf|2,#6fc'), 'since' => '18.0', 'filter' => 'text', ], 'fDayofWeek' => [ 'required' => false, 'name' => tra('First day of the Week'), 'description' => tr('Choose the day that each week begins with, for the tracker calendar display. The value must be a number that represents the day of the week: Sunday=0, Monday=1, Tuesday=2, etc. Default: %0 (Sunday)', '0'), 'since' => '12.1', 'default' => 0, 'filter' => 'int', ], 'weekends' => [ 'required' => false, 'name' => tra('Show Weekends'), 'description' => tra('Display Saturdays and Sundays (shown by default)'), 'filter' => 'alpha', 'default' => 'y', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ] ], 'minHourOfDay' => [ 'required' => false, 'name' => tra('Day Start'), 'description' => tr('First time slot that will be displayed for each day, e.g. %0', '07:00:00'), 'since' => '19.1', 'filter' => 'text', 'default' => '07:00:00', ], 'maxHourOfDay' => [ 'required' => false, 'name' => tra('Day End'), 'description' => tr('Last time slot that will be displayed for each day, e.g. %0', '24:00:00'), 'since' => '19.1', 'filter' => 'text', 'default' => '24:00:00', ], 'slotDuration' => [ 'required' => false, 'name' => tra('Slot Duration'), 'description' => tr('Frequency for displayting time slots, e.g. %0 (defaults to the calendar_timespan preference)', "00:{$prefs['calendar_timespan']}:00"), 'since' => '19.1', 'filter' => 'text', 'default' => "00:{$prefs['calendar_timespan']}:00", ], 'eventOverlap' => [ 'required' => false, 'name' => tra('Overlapping allowed'), 'description' => tra('Allow resources to overlap in time.'), 'since' => '20.1', 'filter' => 'alpha', 'default' => 'y', 'options' => [ ['text' => '', 'value' => ''], ['text' => tra('Yes'), 'value' => 'y'], ['text' => tra('No'), 'value' => 'n'] ] ], 'maxEvents' => [ 'required' => false, 'name' => tra('The maximum of events to retrieve'), 'description' => tra('Adjust the maximum number of events to display in calendar views'), 'since' => '22', 'filter' => 'int', 'default' => 200, ], ], ]; } function wikiplugin_trackercalendar($data, $params) { global $prefs; static $id = 0; $headerlib = TikiLib::lib('header'); $vendorPath = VendorHelper::getAvailableVendorPath('fullcalendarscheduler', 'npm-asset/fullcalendar-scheduler/main.min.js', false); if (! $vendorPath) { $errorMessage = tr('To view Tracker Calendar Tiki needs the latest npm-asset/fullcalendar-scheduler package.'); if (Perms::get()->admin) { $errorMessage .= '
' . tr('Use the Package Manager to install it %0here%1.', '', ''); } else { $errorMessage .= '
' . tr('Contact the site administrator.'); } return WikiParser_PluginOutput::internalError($errorMessage); } // Disable fullcalendar's force events to be one-line tall $headerlib->add_css('.fc-day-grid-event > .fc-content, .fc-timeline-event > .fc-content { white-space: normal; }'); $headerlib->add_cssfile('vendor_bundled/vendor/npm-asset/fullcalendar/main.css'); $headerlib->add_cssfile($vendorPath . '/npm-asset/fullcalendar-scheduler/main.css'); // Disable fullcalendar's force events to be one-line tall $headerlib->add_css('.fc-day-grid-event > .fc-content { white-space: normal; }'); $headerlib->add_jsfile('vendor_bundled/vendor/moment/moment/min/moment.min.js', true); $headerlib->add_jsfile('vendor_bundled/vendor/npm-asset/fullcalendar/main.js', true); $headerlib->add_jsfile($vendorPath . '/npm-asset/fullcalendar-scheduler/main.min.js', true); $headerlib->add_jsfile('lib/jquery_tiki/wikiplugin-trackercalendar.js'); $jit = new JitFilter($params); $definition = Tracker_Definition::get($jit->trackerId->int()); $itemObject = Tracker_Item::newItem($jit->trackerId->int()); if (! $definition) { return WikiParser_PluginOutput::userError(tr('Tracker not found.')); } $beginField = $definition->getFieldFromPermName($jit->begin->word()); $endField = $definition->getFieldFromPermName($jit->end->word()); if (! $beginField || ! $endField) { return WikiParser_PluginOutput::userError(tr('Fields not found.')); } //set defaults $plugininfo = wikiplugin_trackercalendar_info(); $defaults = []; foreach ($plugininfo['params'] as $key => $param) { $defaults[$key] = $param['default'] ?? null; } $params = array_merge($defaults, $params); $views = []; if ($params['amonth'] === 'y') { $views[] = 'dayGridMonth'; } if ($params['aweek'] === 'y') { $views[] = 'resourceTimeGridWeek'; } if ($params['aday'] === 'y') { $views[] = 'resourceTimeGridDay'; } if ($params['lyear'] === 'y') { $views[] = 'listYear'; } if ($params['lmonth'] === 'y') { $views[] = 'listMonth'; } if ($params['lweek'] === 'y') { $views[] = 'listWeek'; } if ($params['lday'] === 'y') { $views[] = 'listDay'; } $resources = []; if ($resourceField = $jit->resource->word()) { $field = $definition->getFieldFromPermName($resourceField); $resources = wikiplugin_trackercalendar_get_resources($field); if ($params['ryear'] === 'y') { $views[] = 'resourceTimelineYear'; } if ($params['rmonth'] === 'y') { $views[] = 'resourceTimelineMonth'; } if ($params['rweek'] === 'y') { $views[] = 'resourceTimelineWeek'; } if ($params['rday'] === 'y') { $views[] = 'resourceTimelineDay'; } } $dViewMap = [ 'month' => 'dayGridMonth', 'agendaWeek' => 'resourceTimeGridWeek', 'agendaDay' => 'resourceTimeGridDay', 'list' => 'list', 'listMonth' => 'listMonth', 'listWeek' => 'listWeek', 'listDay' => 'listDay', 'timelineYear' => 'resourceTimelineYear', 'timelineMonth' => 'resourceTimelineMonth', 'timelineWeek' => 'resourceTimelineWeek', 'resourceWeek' => 'resourceTimelineWeek', // Old value on fullcalendar resources 'timelineDay' => 'resourceTimelineDay' ]; $dView = $dViewMap[$params['dView']]; // Define the default date (dYear, dMonth, dDay) if (! empty($params['dYear'])) { $dYear = $params['dYear']; } else { $dYear = (int) date('Y'); } if ($params['dMonth'] > 0 and $params['dMonth'] < 13) { $dMonth = $params['dMonth']; } else { $dMonth = (int) date('n'); } if ($params['dDay'] > 0 and $params['dDay'] < 32) { $dDay = $params['dDay']; } else { $dDay = (int) date('j'); } // day duration $minHourOfDay = $params['minHourOfDay']; $maxHourOfDay = $params['maxHourOfDay']; $slotDuration = $params['slotDuration']; $eventOverlap = $params['eventOverlap'] === 'y'; $maxEvents = $params['maxEvents']; // Format the default date as Y-m-d instead of Y-n-d, required by MomentJs $dDate = (new DateTime($dYear . '-' . $dMonth . '-' . $dDay))->format('Y-m-d'); if ($params['fDayofWeek'] > -1 and $params['fDayofWeek'] < 7) { $firstDayofWeek = $params['fDayofWeek']; } elseif ($prefs['calendar_firstDayofWeek'] !== 'user') { $firstDayofWeek = $prefs['calendar_firstDayofWeek']; } else { $firstDayofWeek = 0; } $matches = WikiParser_PluginMatcher::match($data); $builder = new Search_Formatter_Builder(); $builder->apply($matches); $formatter = $builder->getFormatter(); $filters = str_replace(['~np~', '~/np~'], '', $formatter->renderFilters()); $smarty = TikiLib::lib('smarty'); $smarty->assign( 'trackercalendarData', [ 'id' => 'trackercalendar' . ++$id, 'trackerId' => $jit->trackerId->int(), 'colormap' => base64_encode($jit->colormap->none()), 'begin' => $jit->begin->word(), 'end' => $jit->end->word(), 'resource' => $resourceField, 'resourceList' => $resources, 'coloring' => $jit->coloring->word(), 'beginFieldName' => 'ins_' . $beginField['fieldId'], 'endFieldName' => 'ins_' . $endField['fieldId'], 'firstDayofWeek' => $firstDayofWeek, 'views' => implode(',', $views), 'initialDate' => $dDate, 'minHourOfDay' => $minHourOfDay, 'maxHourOfDay' => $maxHourOfDay, 'slotDuration' => $slotDuration, 'addTitle' => tr('Insert'), 'canInsert' => $itemObject->canModify(), 'dView' => $dView, 'eventOverlap' => $eventOverlap, 'body' => $data, 'filterValues' => $_REQUEST, 'url' => $params['external'] === 'y' ? $params['url'] : '', 'trkitemid' => $params['external'] === 'y' ? $params['trkitemid'] : '', 'addAllFields' => $params['external'] === 'y' ? $params['addAllFields'] : '', 'useSessionStorage' => $params['external'] === 'y' ? $params['useSessionStorage'] : '', 'timeFormat' => $prefs['display_12hr_clock'] === 'y', 'weekends' => $params['weekends'] === 'y' ? 1 : 0, 'utcOffset' => TikiDate::tzServerOffset(TikiLib::lib('tiki')->get_display_timezone()) / 60, // In minutes 'maxEvents' => $maxEvents, 'display_timezone' => $prefs['display_timezone'], 'labelResDay' => tr('resource day'), 'labelResWeek' => tr('resource week'), 'labelResMonth' => tr('resource month'), 'labelResYear' => tr('resource year'), 'labelListDay' => tr('list day'), 'labelListWeek' => tr('list week'), 'labelListMonth' => tr('list month'), 'labelListYear' => tr('list year'), 'labelAgendaWeek' => tr('week'), 'labelAgendaDay' => tr('day'), 'labelToday' => tr('today'), 'labelAllDay' => tr('all-day'), ] ); $smarty->assign('filters', $filters); return $smarty->fetch('wiki-plugins/trackercalendar.tpl'); } function wikiplugin_trackercalendar_get_resources($field) { $db = TikiDb::get(); return $db->fetchAll('SELECT DISTINCT LOWER(value) as id, value as title FROM tiki_tracker_item_fields WHERE fieldId = ? ORDER BY value', $field['fieldId']); }