From 12ffdfd8f1a0cec673af8aed55aa81bdae9853aa Mon Sep 17 00:00:00 2001 From: Curtis Conard Date: Thu, 18 Apr 2024 22:11:27 -0400 Subject: [PATCH] Planning Twig UI --- CHANGELOG.md | 2 + ajax/planningcheck.php | 80 -- css/legacy/includes/_planning.scss | 7 - js/planning.js | 34 +- .../View/Extension/DataHelpersExtension.php | 8 + .../View/Extension/ItemtypeExtension.php | 6 +- src/Planning.php | 813 ++++++------------ src/PlanningExternalEvent.php | 206 +---- src/PlanningRecall.php | 60 +- .../components/form/fields_macros.html.twig | 14 + .../planning/add_classic_event.html.twig | 113 +++ .../planning/availability.html.twig | 172 ++++ .../planning/external_event.html.twig | 207 +++++ .../assistance/planning/filters.html.twig | 63 ++ .../planning/single_filter.html.twig | 108 +++ 15 files changed, 983 insertions(+), 910 deletions(-) delete mode 100644 ajax/planningcheck.php create mode 100644 templates/pages/assistance/planning/add_classic_event.html.twig create mode 100644 templates/pages/assistance/planning/availability.html.twig create mode 100644 templates/pages/assistance/planning/external_event.html.twig create mode 100644 templates/pages/assistance/planning/filters.html.twig create mode 100644 templates/pages/assistance/planning/single_filter.html.twig diff --git a/CHANGELOG.md b/CHANGELOG.md index 6435d3439606..ed914188fed9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -266,6 +266,7 @@ The present file will list all changes made to the project; according to the - `NetworkPort::getNetworkPortInstantiationsWithNames()` - `NetworkPort::resetConnections()` - `PlanningExternalEvent::addVisibilityRestrict()` +- `PlanningRecall::specificForm()` - `Plugin::migrateItemType()` - `ProfileRight::updateProfileRightAsOtherRight()` - `ProfileRight::updateProfileRightsAsOtherRights()` @@ -316,6 +317,7 @@ The present file will list all changes made to the project; according to the - `$CFG_GLPI['debug_sql']` and `$CFG_GLPI['debug_vars']` configuration options. - `DropdownTranslation::getTranslationByName()` - `addgroup` and `deletegroup` actions in `front/user.form.php`. +- AJAX file `ajax/planningcheck.php` (Replaced with `Planning::showPlanningCheck`). ## [10.0.15] unreleased diff --git a/ajax/planningcheck.php b/ajax/planningcheck.php deleted file mode 100644 index 0e601c2930c5..000000000000 --- a/ajax/planningcheck.php +++ /dev/null @@ -1,80 +0,0 @@ -. - * - * --------------------------------------------------------------------- - */ - -/** - * @since 0.83 - */ - -/** @var array $CFG_GLPI */ -global $CFG_GLPI; - -// Direct access to file -if (strpos($_SERVER['PHP_SELF'], "planningcheck.php")) { - $AJAX_INCLUDE = 1; - include('../inc/includes.php'); - header("Content-Type: text/html; charset=UTF-8"); - Html::header_nocache(); -} - -Session::checkLoginUser(); - -$append_params = [ - "checkavailability" => "checkavailability", -]; - -if (isset($_POST['users_id']) && ($_POST['users_id'] > 0)) { - $append_params["itemtype"] = User::class; - $append_params[User::getForeignKeyField()] = $_POST['users_id']; -} elseif ( - isset($_POST['parent_itemtype']) && class_exists($_POST['parent_itemtype']) - && isset($_POST['parent_items_id']) && ($_POST['parent_items_id'] > 0) - && isset($_POST['parent_fk_field']) && ($_POST['parent_fk_field'] != '') -) { - $append_params["itemtype"] = $_POST['parent_itemtype']; - $append_params[$_POST['parent_fk_field']] = $_POST['parent_items_id']; -} - -if (count($append_params) > 1) { - $rand = mt_rand(); - echo ""; - echo ""; - echo "" . __('Availability') . ""; - echo ""; - Ajax::createIframeModalWindow( - 'planningcheck' . $rand, - $CFG_GLPI["root_doc"] . "/front/planning.php?" . Toolbox::append_params($append_params), - ['title' => __('Availability')] - ); -} diff --git a/css/legacy/includes/_planning.scss b/css/legacy/includes/_planning.scss index 4668859db822..f99ec2a1465c 100644 --- a/css/legacy/includes/_planning.scss +++ b/css/legacy/includes/_planning.scss @@ -93,13 +93,6 @@ flex-wrap: wrap; align-items: center; - .actor_icon { - padding-bottom: 2px; - vertical-align: top; - font-size: 14px; - margin-left: 5px; - } - label { padding-left: 5px; line-height: 16px; diff --git a/js/planning.js b/js/planning.js index 4057275d1288..bd5ce0a79b23 100644 --- a/js/planning.js +++ b/js/planning.js @@ -726,21 +726,7 @@ var GLPIPlanning = { }); $('#planning_filter .delete_planning').on( 'click', function() { - var deleted = $(this); - var li = deleted.closest('ul.filters > li'); - $.ajax({ - url: CFG_GLPI.root_doc+"/ajax/planning.php", - type: 'POST', - data: { - action: 'delete_filter', - filter: deleted.attr('value'), - type: li.attr('event_type') - }, - success: function() { - li.remove(); - GLPIPlanning.refresh(); - } - }); + GLPIPlanning.deletePlanning(this); }); var sendDisplayEvent = function(current_checkbox, refresh_planning) { @@ -841,6 +827,24 @@ var GLPIPlanning = { }); }, + deletePlanning: (trigger_element) => { + const deleted = $(trigger_element); + const li = deleted.closest('ul.filters > li'); + $.ajax({ + url: CFG_GLPI.root_doc+"/ajax/planning.php", + type: 'POST', + data: { + action: 'delete_filter', + filter: deleted.attr('value'), + type: li.attr('event_type') + }, + success: function() { + li.remove(); + GLPIPlanning.refresh(); + } + }); + }, + // send ajax for event storage (on event drag/resize) editEventTimes: function(info, move_instance) { move_instance = move_instance || false; diff --git a/src/Application/View/Extension/DataHelpersExtension.php b/src/Application/View/Extension/DataHelpersExtension.php index fbe005e0522e..6e4c4d976d93 100644 --- a/src/Application/View/Extension/DataHelpersExtension.php +++ b/src/Application/View/Extension/DataHelpersExtension.php @@ -41,6 +41,7 @@ use Toolbox; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; +use Twig\TwigTest; /** * @since 10.0.0 @@ -67,6 +68,13 @@ public function getFilters(): array ]; } + public function getTests(): array + { + return [ + new TwigTest('url_safe', \Toolbox::isUrlSafe(...)), + ]; + } + /** * Return date formatted to user preferred format. * diff --git a/src/Application/View/Extension/ItemtypeExtension.php b/src/Application/View/Extension/ItemtypeExtension.php index 599e7b2f29d5..e1e4b6d1bd17 100644 --- a/src/Application/View/Extension/ItemtypeExtension.php +++ b/src/Application/View/Extension/ItemtypeExtension.php @@ -266,7 +266,11 @@ private function getItemInstance($item, ?int $id = null): ?CommonDBTM return $instance ?: null; } - public function getItemtypeForeignKey(string $itemtype): ?string + /** + * @param class-string|object $itemtype The itemtype or an item instance. + * @return string|null The foreign key field name of the itemtype or null if not found. + */ + public function getItemtypeForeignKey(string|object $itemtype): ?string { if (is_a($itemtype, CommonDBTM::class, true)) { return $itemtype::getForeignKeyField(); diff --git a/src/Planning.php b/src/Planning.php index 98a7a9f5f8c5..5caded9cd395 100644 --- a/src/Planning.php +++ b/src/Planning.php @@ -33,6 +33,7 @@ * --------------------------------------------------------------------- */ +use Glpi\Application\View\TemplateRenderer; use Glpi\DBAL\QueryExpression; use Glpi\Application\ErrorHandler; use Glpi\DBAL\QueryFunction; @@ -407,34 +408,15 @@ public static function checkAvailability($params = []) return; } if ( - !isset($params[$item->getForeignKeyField()]) - || !$item->getFromDB($params[$item->getForeignKeyField()]) + !isset($params[$item::getForeignKeyField()]) + || !$item->getFromDB($params[$item::getForeignKeyField()]) ) { return; } - // No limit by default - if (!isset($params['limitto'])) { - $params['limitto'] = 0; - } - if (isset($params['begin']) && !empty($params['begin'])) { - $begin = $params['begin']; - } else { - $begin = date("Y-m-d"); - } - if (isset($params['end']) && !empty($params['end'])) { - $end = $params['end']; - } else { - $end = date("Y-m-d"); - } - - if ($end < $begin) { - $end = $begin; - } - $realbegin = $begin . " " . $CFG_GLPI["planning_begin"]; - $realend = $end . " " . $CFG_GLPI["planning_end"]; - if ($CFG_GLPI["planning_end"] == "24:00") { - $realend = $end . " 23:59:59"; - } + // No limit by default + $params['limitto'] = $params['limitto'] ?? 0; + $begin = $params['begin'] ?? date('Y-m-d'); + $end = max($params['end'] ?? date('Y-m-d'), $begin); $users = []; @@ -459,13 +441,9 @@ public static function checkAvailability($params = []) } } } - if ($itemtype = 'Ticket') { - $task = new TicketTask(); - } else if ($itemtype = 'Problem') { - $task = new ProblemTask(); - } + $task = new ($item::getTaskClass()); if ($task->getFromDBByCrit(['tickets_id' => $item->fields['id']])) { - $users['users_id'] = getUserName($task->fields['users_id_tech']); + $users[$task->fields['users_id_tech']] = getUserName($task->fields['users_id_tech']); $group_id = $task->fields['groups_id_tech']; if ($group_id) { foreach (Group_User::getGroupUsers($group_id) as $data2) { @@ -481,50 +459,6 @@ public static function checkAvailability($params = []) break; } asort($users); - // Use get method to check availability - echo "
\n"; - echo ""; - $colspan = 5; - if (count($users) > 1) { - $colspan++; - } - echo ""; - echo ""; - - echo ""; - echo "\n"; - echo "\n"; - echo "\n"; - echo "\n"; - if (count($users) > 1) { - echo ""; - } - - echo "\n"; - - echo ""; - echo "
" . __('Availability') . "
" . __('Start') . ""; - Html::showDateField("begin", ['value' => $begin, - 'maybeempty' => false - ]); - echo "" . __('End') . ""; - Html::showDateField("end", ['value' => $end, - 'maybeempty' => false - ]); - echo ""; - $data = [0 => __('All')]; - $data += $users; - Dropdown::showFromArray('limitto', $data, ['width' => '100%', - 'value' => $params['limitto'] - ]); - echo ""; - echo "getID() . "\">"; - echo "getType() . "\">"; - echo ""; - echo "
"; - Html::closeForm(); - echo "
\n"; if (($params['limitto'] > 0) && isset($users[$params['limitto']])) { $displayuser[$params['limitto']] = $users[$params['limitto']]; @@ -532,143 +466,14 @@ public static function checkAvailability($params = []) $displayuser = $users; } - if (count($displayuser)) { - foreach ($displayuser as $who => $whoname) { - $params = [ - 'who' => $who, - 'whogroup' => 0, - 'begin' => $realbegin, - 'end' => $realend - ]; - - $interv = []; - foreach ($CFG_GLPI['planning_types'] as $itemtype) { - $interv = array_merge($interv, $itemtype::populatePlanning($params)); - if (method_exists($itemtype, 'populateNotPlanned')) { - $interv = array_merge($interv, $itemtype::populateNotPlanned($params)); - } - } - - // Print Headers - echo "
"; - $colnumber = 1; - $plan_begin = explode(":", $CFG_GLPI["planning_begin"]); - $plan_end = explode(":", $CFG_GLPI["planning_end"]); - $begin_hour = intval($plan_begin[0]); - $end_hour = intval($plan_end[0]); - if ($plan_end[1] != 0) { - $end_hour++; - } - $colsize = floor((100 - 15) / ($end_hour - $begin_hour)); - $timeheader = ''; - for ($i = $begin_hour; $i < $end_hour; $i++) { - $from = ($i < 10 ? '0' : '') . $i; - $timeheader .= ""; - $colnumber += 4; - } - - // Print Headers - echo ""; - echo ""; - echo $timeheader; - echo ""; - - $day_begin = strtotime($realbegin); - $day_end = strtotime($realend); - - for ($time = $day_begin; $time < $day_end; $time += DAY_TIMESTAMP) { - $current_day = date('Y-m-d', $time); - echo ""; - $begin_quarter = $begin_hour * 4; - $end_quarter = $end_hour * 4; - for ($i = $begin_quarter; $i < $end_quarter; $i++) { - $begin_time = date("Y-m-d H:i:s", strtotime($current_day) + ($i) * HOUR_TIMESTAMP / 4); - $end_time = date("Y-m-d H:i:s", strtotime($current_day) + ($i + 1) * HOUR_TIMESTAMP / 4); - // Init activity interval - $begin_act = $end_time; - $end_act = $begin_time; - - reset($interv); - while ($data = current($interv)) { - if ( - ($data["begin"] >= $begin_time) - && ($data["end"] <= $end_time) - ) { - // In - if ($begin_act > $data["begin"]) { - $begin_act = $data["begin"]; - } - if ($end_act < $data["end"]) { - $end_act = $data["end"]; - } - unset($interv[key($interv)]); - } else if ( - ($data["begin"] < $begin_time) - && ($data["end"] > $end_time) - ) { - // Through - $begin_act = $begin_time; - $end_act = $end_time; - next($interv); - } else if ( - ($data["begin"] >= $begin_time) - && ($data["begin"] < $end_time) - ) { - // Begin - if ($begin_act > $data["begin"]) { - $begin_act = $data["begin"]; - } - $end_act = $end_time; - next($interv); - } else if ( - ($data["end"] > $begin_time) - && ($data["end"] <= $end_time) - ) { - //End - $begin_act = $begin_time; - if ($end_act < $data["end"]) { - $end_act = $data["end"]; - } - unset($interv[key($interv)]); - } else { // Defautl case - next($interv); - } - } - if ($begin_act < $end_act) { - if ( - ($begin_act <= $begin_time) - && ($end_act >= $end_time) - ) { - // Activity in quarter - echo ""; - } else { - // Not all the quarter - if ($begin_act <= $begin_time) { - echo ""; - } else { - echo ""; - } - } - } else { - // No activity - echo ""; - } - } - echo ""; - } - echo ""; - echo "
" . $from . ":00
"; - echo $whoname; - echo "
 
" . Html::convDate($current_day) . "    
 
"; - } - } - echo "
"; - echo ""; - echo ""; - echo ""; - echo ""; - echo ""; - echo "
" . __('Caption') . "" . __('Available') . "" . __('Unavailable') . "
"; + TemplateRenderer::getInstance()->display('pages/assistance/planning/availability.html.twig', [ + 'begin' => $begin, + 'end' => $end, + 'item' => $item, + 'users' => $users, + 'displayed_users' => $displayuser, + 'params' => $params + ]); } /** @@ -687,13 +492,8 @@ public static function showPlanning($fullview = true) self::initSessionForCurrentUser(); - echo ""; - - // define options for current page - $rand = ''; + // define options for current page if ($fullview) { - // full planning view (Assistance > Planning) - Planning::showPlanningFilter(); $options = [ 'full_view' => true, 'default_view' => $_SESSION['glpi_plannings']['lastview'] ?? 'timeGridWeek', @@ -701,30 +501,35 @@ public static function showPlanning($fullview = true) 'now' => date("Y-m-d H:i:s"), 'can_create' => PlanningExternalEvent::canCreate(), 'can_delete' => PlanningExternalEvent::canPurge(), + 'rand' => mt_rand(), ]; } else { - // short view (on Central page) - $rand = rand(); + // short view (on Central page) $options = [ 'full_view' => false, 'default_view' => 'listFull', 'header' => false, 'height' => 'auto', - 'rand' => $rand, + 'rand' => mt_rand(), 'now' => date("Y-m-d H:i:s"), ]; } - // display planning (and call js from js/planning.js) - echo "
"; - echo ""; - - echo Html::scriptBlock("$(function() { - GLPIPlanning.display(" . json_encode($options) . "); - GLPIPlanning.planningFilters(); - });"); - - return; + // language=Twig + echo TemplateRenderer::getInstance()->renderFromStringTemplate(<< + {% if options.full_view %} + {{ include('pages/assistance/planning/filters.html.twig') }} + {% endif %} +
+ + +TWIG, ['options' => $options]); } public static function getTimelineResources() @@ -900,48 +705,7 @@ public static function initSessionForCurrentUser() */ public static function showPlanningFilter() { - /** @var array $CFG_GLPI */ - global $CFG_GLPI; - - $headings = ['filters' => __("Events type"), - 'plannings' => __('Plannings') - ]; - - echo "
"; - - echo "
"; - echo ""; - echo "
"; - - echo "
"; - foreach ($_SESSION['glpi_plannings'] as $filter_heading => $filters) { - if (!in_array($filter_heading, array_keys($headings))) { - continue; - } - - echo "
"; - echo "

"; - echo $headings[$filter_heading]; - if ($filter_heading == "plannings") { - echo ""; - echo ""; - echo ""; - } - echo "

"; - echo "
    "; - foreach ($filters as $filter_key => $filter_data) { - self::showSingleLinePlanningFilter( - $filter_key, - $filter_data, - ['filter_color_index' => 0] - ); - } - echo "
"; - echo "
"; - } - echo "
"; // #planning_filter_content - echo "
"; // #planning_filter + TemplateRenderer::getInstance()->display('pages/assistance/planning/filters.html.twig'); } /** @@ -955,6 +719,8 @@ public static function showPlanningFilter() * @param $options * * @return void + * @used-by templates/pages/assistance/planning/filters.html.twig + * @used-by templates/pages/assistance/planning/single_filter.html.twig */ public static function showSingleLinePlanningFilter($filter_key, $filter_data, $options = []) { @@ -1032,33 +798,6 @@ public static function showSingleLinePlanningFilter($filter_key, $filter_data, $ } } - echo "
  • "; - Html::showCheckbox([ - 'name' => 'filters[]', - 'value' => $filter_key, - 'id' => $filter_key, - 'title' => $title, - 'checked' => $filter_data['display'] - ]); - - if ($filter_data['type'] !== 'event_filter') { - $exploded = explode('_', $filter_data['type']); - $icon = "user"; - if ($exploded[0] === 'group') { - $icon = "users"; - } - echo ""; - } - - echo ""; if (!empty($filter_data['color'])) { $color = $filter_data['color']; @@ -1067,84 +806,44 @@ class='" . $filter_data['type'] . $expanded . "'>"; $color = self::getPaletteColor('bg', $params['filter_color_index']); } - echo ""; - // colors not for groups - if ($filter_data['type'] !== 'group_users' && $filter_key !== 'OnlyBgEvents') { - echo ""; - Html::showColorField( - $filter_key . "_color", - ['value' => $color] - ); - echo ""; - } - - if ($filter_data['type'] === 'group_users') { - echo ""; - } - if ($filter_data['type'] !== 'event_filter') { - echo ""; - echo ""; - echo ""; - echo ""; - } - echo ""; - - if ($caldav_item_url !== '' && $filter_data['type'] === 'group_users') { - echo "
      "; - foreach ($filter_data['users'] as $user_key => $userdata) { - self::showSingleLinePlanningFilter( - $user_key, - $userdata, - ['show_delete' => false, - 'filter_color_index' => $params['filter_color_index'] - ] - ); } - echo "
    "; } - echo "
  • "; + TemplateRenderer::getInstance()->display('pages/assistance/planning/single_filter.html.twig', [ + 'filter_key' => $filter_key, + 'filter_data' => $filter_data, + 'expanded' => $expanded, + 'title' => $title, + 'warning' => sprintf(__('URL "%s" is not allowed by your administrator.'), $filter_data['url'] ?? ''), + 'params' => $params, + 'color' => $color, + 'delete_msg' => __('Delete'), + 'export_msg' => _x('button', 'Export'), + 'copy_caldav_msg' => __('Copy CalDAV URL to clipboard'), + 'formats' => [ + 'ical' => __('Ical'), + 'webcal' => __('Webcal'), + 'csv' => __('CSV') + ], + 'uID' => $uID, + 'gID' => $gID, + 'login_user' => $loginUser ?? null, + 'url' => $url ?? null, + 'url_port' => $url_port ?? null, + 'caldav_item_url' => $caldav_item_url ?? null, + ]); } /** @@ -1154,40 +853,42 @@ class='" . $filter_data['type'] . $expanded . "'>"; */ public static function showAddPlanningForm() { - /** @var array $CFG_GLPI */ - global $CFG_GLPI; - - $rand = mt_rand(); - echo ""; - echo __s("Actor") . ":
    "; - $planning_types = ['user' => User::getTypeName(1)]; - if (Session::haveRightsOr('planning', [self::READGROUP, self::READALL])) { $planning_types['group_users'] = __('All users of a group'); $planning_types['group'] = Group::getTypeName(1); } - $planning_types['external'] = __('External calendar'); - Dropdown::showFromArray( - 'planning_type', - $planning_types, - ['display_emptychoice' => true, - 'rand' => $rand - ] - ); - echo Html::scriptBlock(" - $(function() { - $('#dropdown_planning_type$rand').on( 'change', function( e ) { - var planning_type = $(this).val(); - $('#add_planning_subform$rand').load('" . $CFG_GLPI['root_doc'] . "/ajax/planning.php', - {action: 'add_'+planning_type+'_form'}); - }); - });"); - echo "

    "; - echo "
    "; - Html::closeForm(); + + $twig_params = [ + 'planning_types' => $planning_types, + 'rand' => mt_rand(), + 'label' => __('Actor'), + ]; + // language=Twig + echo TemplateRenderer::getInstance()->renderFromStringTemplate(<< + {{ fields.dropdownArrayField('planning_type', 0, planning_types, label, { + display_emptychoice: true, + rand: rand + }) }} + + +

    +
    + +TWIG, $twig_params); } /** @@ -1205,15 +906,14 @@ public static function showAddUserForm() $used[] = $actor[1]; } } - echo User::getTypeName(1) . " :
    "; - // show only users with right to add planning events + // show only users with right to add planning events $rights = ['change', 'problem', 'reminder', 'task', 'projecttask']; - // Can we see only personnal planning ? + // Can we see only personnal planning ? if (!Session::haveRightsOr('planning', [self::READALL, self::READGROUP])) { $rights = 'id'; } - // Can we see user of my groups ? + // Can we see user of my groups ? if ( Session::haveRight('planning', self::READGROUP) && !Session::haveRight('planning', self::READALL) @@ -1221,14 +921,24 @@ public static function showAddUserForm() $rights = 'groups'; } - User::dropdown(['entity' => $_SESSION['glpiactive_entity'], - 'entity_sons' => $_SESSION['glpiactive_entity_recursive'], - 'right' => $rights, - 'used' => $used - ]); - echo "

    "; - echo Html::hidden('action', ['value' => 'send_add_user_form']); - echo Html::submit(_sx('button', 'Add')); + $twig_params = [ + 'add_msg' => _sx('button', 'Add'), + 'rights' => $rights, + 'used' => $used, + ]; + // language=Twig + echo TemplateRenderer::getInstance()->renderFromStringTemplate(<< + {{ inputs.submit('submit', add_msg, 1) }} +TWIG, $twig_params); } /** @@ -1259,22 +969,28 @@ public static function sendAddUserForm($params = []) */ public static function showAddGroupUsersForm() { - echo htmlspecialchars(Group::getTypeName(1)) . " :
    "; - $condition = []; // filter groups - if (!Session::haveRight('planning', self::READALL)) { + if (!Session::haveRight('planning', self::READALL) && count($_SESSION['glpigroups'])) { $condition['id'] = $_SESSION['glpigroups']; } - Group::dropdown([ - 'entity' => $_SESSION['glpiactive_entity'], - 'entity_sons' => $_SESSION['glpiactive_entity_recursive'], - 'condition' => $condition - ]); - echo "

    "; - echo Html::hidden('action', ['value' => 'send_add_group_users_form']); - echo Html::submit(_sx('button', 'Add')); + $twig_params = [ + 'add_msg' => _sx('button', 'Add'), + 'condition' => $condition + ]; + // language=Twig + echo TemplateRenderer::getInstance()->renderFromStringTemplate(<< + {{ inputs.submit('submit', add_msg, 1) }} +TWIG, $twig_params); } /** @@ -1366,19 +1082,26 @@ public static function showAddGroupForm() { $condition = ['is_task' => 1]; // filter groups - if (!Session::haveRight('planning', self::READALL)) { + if (!Session::haveRight('planning', self::READALL) && count($_SESSION['glpigroups'])) { $condition['id'] = $_SESSION['glpigroups']; } - echo Group::getTypeName(1) . " :
    "; - Group::dropdown([ - 'entity' => $_SESSION['glpiactive_entity'], - 'entity_sons' => $_SESSION['glpiactive_entity_recursive'], - 'condition' => $condition - ]); - echo "

    "; - echo Html::hidden('action', ['value' => 'send_add_group_form']); - echo Html::submit(_sx('button', 'Add')); + $twig_params = [ + 'add_msg' => _sx('button', 'Add'), + 'condition' => $condition + ]; + // language=Twig + echo TemplateRenderer::getInstance()->renderFromStringTemplate(<< + {{ inputs.submit('submit', add_msg, 1) }} +TWIG, $twig_params); } /** @@ -1416,27 +1139,21 @@ public static function sendAddGroupForm($params = []) */ public static function showAddExternalForm() { - $rand = mt_rand(); - - echo ' '; - echo '
    '; - echo Html::input( - 'name', - [ - 'value' => '', - 'id' => 'name' . $rand, - ] - ); - echo '
    '; - echo '
    '; - - echo ' '; - echo '
    '; - echo ''; - echo '

    '; - - echo Html::hidden('action', ['value' => 'send_add_external_form']); - echo Html::submit(_sx('button', 'Add')); + $twig_params = [ + 'add_msg' => _sx('button', 'Add'), + 'name_label' => __('Calendar name'), + 'url_label' => __('Calendar URL'), + ]; + // language=Twig + echo TemplateRenderer::getInstance()->renderFromStringTemplate(<< + {{ inputs.submit('submit', add_msg, 1) }} +TWIG, $twig_params); } /** @@ -1484,33 +1201,40 @@ public static function showAddEventForm($params = []) $params['itemtype'] = $CFG_GLPI['planning_add_types'][0]; self::showAddEventSubForm($params); } else { - $rand = mt_rand(); $select_options = []; foreach ($CFG_GLPI['planning_add_types'] as $add_types) { $select_options[$add_types] = $add_types::getTypeName(1); } - echo __s("Event type") . " :
    "; - Dropdown::showFromArray( - 'itemtype', - $select_options, - ['display_emptychoice' => true, - 'rand' => $rand - ] - ); - echo Html::scriptBlock(" - $(function() { - $('#dropdown_itemtype$rand').on('change', function() { - var current_itemtype = $(this).val(); - $('#add_planning_subform$rand').load('" . $CFG_GLPI['root_doc'] . "/ajax/planning.php', - {action: 'add_event_sub_form', - itemtype: current_itemtype, - begin: '" . $params['begin'] . "', - end: '" . $params['end'] . "'}); - }); - });"); - echo "

    "; - echo "
    "; + $twig_params = [ + 'label' => __('Event type'), + 'select_options' => $select_options, + 'params' => $params, + ]; + // language=Twig + echo TemplateRenderer::getInstance()->renderFromStringTemplate(<< + $(() => { + $('#dropdown_itemtype{{ rand }}').on('change', function() { + const current_itemtype = $(this).val(); + $('#add_planning_subform{{ rand }}').load('{{ path('ajax/planning.php') }}', { + action: 'add_event_sub_form', + itemtype: current_itemtype, + begin: '{{ params.begin }}', + end: '{{ params.end }}' + }); + }); + }); + +
    +TWIG, $twig_params); } } @@ -1552,7 +1276,7 @@ public static function showAddEventSubForm($params = []) /** * Former front/planning.php before 9.1. - * Display a classic form to plan an event (with begin fiel and duration) + * Display a classic form to plan an event (with begin field and duration) * * @since 9.1 * @@ -1584,7 +1308,7 @@ public static function showAddEventClassicForm($params = []) $mintime = $begintime; } } else { - $ts = $CFG_GLPI['time_step'] * 60; // passage en minutes + $ts = $CFG_GLPI['time_step'] * 60; // passage in minutes $time = time() + $ts - 60; $time = floor($time / $ts) * $ts; $begin = date("Y-m-d H:i", $time); @@ -1596,90 +1320,57 @@ public static function showAddEventClassicForm($params = []) $end = date("Y-m-d H:i:s", strtotime($begin) + HOUR_TIMESTAMP); } - echo ""; - - if ($display_dates) { - echo ""; - } - - echo "\n"; + $append_params = [ + "checkavailability" => "checkavailability", + ]; - if ( - (!isset($params["id"]) || ((int) $params["id"] === 0)) - && isset($params['itemtype']) - && PlanningRecall::isAvailable() + if (isset($data['users_id']) && ($data['users_id'] > 0)) { + $append_params["itemtype"] = User::class; + $append_params[User::getForeignKeyField()] = $data['users_id']; + } elseif ( + isset($data['parent_itemtype'], $data['parent_items_id'], $data['parent_fk_field']) + && class_exists($data['parent_itemtype']) && ($data['parent_items_id'] > 0) && ($data['parent_fk_field'] !== '') ) { - echo ""; + $append_params["itemtype"] = $data['parent_itemtype']; + $append_params[$data['parent_fk_field']] = $data['parent_items_id']; + } + + if (count($append_params) > 1) { + $rand = mt_rand(); + echo ""; + echo ""; + echo "" . __s('Availability') . ""; + echo ""; + Ajax::createIframeModalWindow( + 'planningcheck' . $rand, + $CFG_GLPI["root_doc"] . "/front/planning.php?" . Toolbox::append_params($append_params), + ['title' => __s('Availability')] + ); } - echo "
    " . __s('Start date') . ""; - Html::showDateTimeField("plan[begin]", [ - 'value' => $begin, - 'maybeempty' => false, - 'canedit' => true, - 'mindate' => '', - 'maxdate' => '', - 'mintime' => $mintime, - 'maxtime' => $CFG_GLPI["planning_end"], - 'rand' => $rand, - ]); - echo "
    " . __s('Period') . " "; - - if (isset($params["rand_user"])) { - $_POST['parent_itemtype'] = $params["parent_itemtype"] ?? ''; - $_POST['parent_items_id'] = $params["parent_items_id"] ?? ''; - $_POST['parent_fk_field'] = $params["parent_fk_field"] ?? ''; - echo ""; - include_once(GLPI_ROOT . '/ajax/planningcheck.php'); - echo ""; - } - - echo ""; - - $empty_label = Dropdown::EMPTY_VALUE; $default_delay = $params['duration'] ?? 0; if ($display_dates) { - $empty_label = __('Specify an end date'); $default_delay = floor((strtotime($end) - strtotime($begin)) / $CFG_GLPI['time_step'] / MINUTE_TIMESTAMP) * $CFG_GLPI['time_step'] * MINUTE_TIMESTAMP; } - Dropdown::showTimeStamp("plan[_duration]", [ - 'min' => 0, - 'max' => 50 * HOUR_TIMESTAMP, - 'value' => $default_delay, - 'emptylabel' => $empty_label, - 'rand' => $rand, + TemplateRenderer::getInstance()->display('pages/assistance/planning/add_classic_event.html.twig', [ + 'params' => $params, + 'begin' => $begin, + 'end' => $end, + 'mintime' => $mintime, + 'default_delay' => $default_delay, ]); - echo "
    "; - - $event_options = [ - 'duration' => '__VALUE__', - 'end' => $end, - 'name' => "plan[end]", - 'global_begin' => $CFG_GLPI["planning_begin"], - 'global_end' => $CFG_GLPI["planning_end"] - ]; - - if ($display_dates) { - Ajax::updateItemOnSelectEvent( - "dropdown_plan[_duration]$rand", - "date_end$rand", - $CFG_GLPI["root_doc"] . "/ajax/planningend.php", - $event_options - ); + } - if ($default_delay === 0) { - $params['duration'] = 0; - Ajax::updateItem("date_end$rand", $CFG_GLPI["root_doc"] . "/ajax/planningend.php", $params); - } - } + /** + * @param array $data + * @return void + * @used-by templates/pages/assistance/planning/add_classic_event.html.twig + */ + public static function showPlanningCheck(array $data): void + { + /** @var array $CFG_GLPI */ + global $CFG_GLPI; - echo "
    " . _sx('Planning', 'Reminder') . ""; - PlanningRecall::dropdown([ - 'itemtype' => $params['itemtype'], - 'items_id' => $params['items_id'], - 'rand' => $rand, - ]); - echo "
    \n"; } /** @@ -2486,9 +2177,6 @@ public static function displayPlanningItem(array $val, $who, $type = "", $comple **/ public static function showCentral($who) { - /** @var array $CFG_GLPI */ - global $CFG_GLPI; - if ( !Session::haveRight(self::$rightname, self::READMY) || ($who <= 0) @@ -2496,20 +2184,23 @@ public static function showCentral($who) return; } - echo "
    "; - echo ""; - echo ""; - echo ""; - echo ""; - - echo ""; - echo ""; - echo "
    "; - echo "" . __('Your planning') . ""; - echo "
    "; - self::showPlanning(false); - echo "
    "; - echo "
    "; + // language=Twig + echo TemplateRenderer::getInstance()->renderFromStringTemplate(<< + + + + + + + + + + + +
    {{ msg }}
    {% do call('Planning::showPlanning', [false]) %}
    + +TWIG, ['msg' => __('Your planning')]); } //******************************************************************************************************************************* diff --git a/src/PlanningExternalEvent.php b/src/PlanningExternalEvent.php index 3ee24f45f9c0..05b941dad209 100644 --- a/src/PlanningExternalEvent.php +++ b/src/PlanningExternalEvent.php @@ -33,6 +33,7 @@ * --------------------------------------------------------------------- */ +use Glpi\Application\View\TemplateRenderer; use Glpi\CalDAV\Contracts\CalDAVCompatibleItemInterface; use Glpi\CalDAV\Traits\VobjectConverterTrait; use Glpi\RichText\RichText; @@ -131,10 +132,7 @@ public function post_getFromDB() public function showForm($ID, array $options = []) { - /** @var array $CFG_GLPI */ - global $CFG_GLPI; - - $canedit = $this->can($ID, UPDATE); + $options['canedit'] = $this->can($ID, UPDATE); $rand = mt_rand(); $rand_plan = mt_rand(); $rand_rrule = mt_rand(); @@ -145,8 +143,6 @@ public function showForm($ID, array $options = []) ) { $options['no_header'] = true; } - $this->initForm($ID, $options); - $this->showFormHeader($options); $is_ajax = isset($options['from_planning_edit_ajax']) && $options['from_planning_edit_ajax']; $is_rrule = ($this->fields['rrule'] ?? '') !== ''; @@ -156,195 +152,6 @@ public function showForm($ID, array $options = []) $this->fields['users_id'] = $options['res_items_id']; } - if ($canedit) { - $tpl_class = 'PlanningExternalEventTemplate'; - echo ""; - echo "" . $tpl_class::getTypeName() . ""; - echo ""; - $tpl_class::dropdown([ - 'value' => $this->fields['planningexternaleventtemplates_id'], - 'entity' => $this->getEntityID(), - 'rand' => $rand, - 'on_change' => "template_update$rand(this.value)" - ]); - - $ajax_url = $CFG_GLPI["root_doc"] . "/ajax/planning.php"; - $JS = << 0) { - $("#textfield_name{$rand}").val(data.name); - } - $("#dropdown_state{$rand}").trigger("setValue", data.state); - if (data.planningeventcategories_id > 0) { - $("#dropdown_planningeventcategories_id{$rand}") - .trigger("setValue", data.planningeventcategories_id); - } - $("#dropdown_background{$rand}").trigger("setValue", data.background); - if (data.text.length > 0) { - setRichTextEditorContent("text{$rand}", data.text); - } - - // set planification fields - if (data.duration > 0) { - $("#dropdown_plan__duration_{$rand_plan}").trigger("setValue", data.duration); - } - $("#dropdown__planningrecall_before_time_{$rand_plan}") - .trigger("setValue", data.before_time); - - // set rrule fields - if (data.rrule != null - && data.rrule.freq != null ) { - $("#dropdown_rrule_freq_{$rand_rrule}").trigger("setValue", data.rrule.freq); - $("#dropdown_rrule_interval_{$rand_rrule}").trigger("setValue", data.rrule.interval); - $("#showdate{$rand_rrule}").val(data.rrule.until); - $("#dropdown_rrule_byday_{$rand_rrule}").val(data.rrule.byday).trigger('change'); - $("#dropdown_rrule_bymonth_{$rand_rrule}").val(data.rrule.bymonth).trigger('change'); - } - }); - } -JAVASCRIPT; - echo Html::scriptBlock($JS); - echo ""; - } - - echo "" . __('Title') . ""; - echo ""; - if (isset($options['start'])) { - echo Html::hidden('day', ['value' => $options['start']]); - } - if ($canedit) { - echo Html::input( - 'name', - [ - 'value' => $this->fields['name'], - 'id' => "textfield_name$rand", - ] - ); - } else { - echo $this->fields['name']; - } - if (isset($options['from_planning_edit_ajax']) && $options['from_planning_edit_ajax']) { - echo Html::hidden('from_planning_edit_ajax'); - } - echo ""; - echo ""; - - echo "" . User::getTypeName(1) . ""; - echo ""; - User::dropdown([ - 'name' => 'users_id', - 'right' => 'all', - 'value' => $this->fields['users_id'] - ]); - echo ""; - echo ""; - - echo "" . __('Guests') . ""; - echo ""; - User::dropdown([ - 'name' => 'users_id_guests[]', - 'right' => 'all', - 'value' => $this->fields['users_id_guests'], - 'multiple' => true, - ]); - echo "
    " . - __("Each guest will have a read-only copy of this event") . - "
    "; - echo ""; - echo ""; - - echo ""; - echo "" . __('Status') . ""; - echo ""; - if ($canedit) { - Planning::dropdownState("state", $this->fields["state"], true, [ - 'rand' => $rand, - ]); - } else { - echo Planning::getState($this->fields["state"]); - } - echo ""; - echo ""; - - echo ""; - echo ""; - echo "" . _n('Category', 'Categories', 1) . ""; - echo ""; - if ($canedit) { - PlanningEventCategory::dropdown([ - 'value' => $this->fields['planningeventcategories_id'], - 'rand' => $rand - ]); - } else { - echo Dropdown::getDropdownName( - PlanningEventCategory::getTable(), - $this->fields['planningeventcategories_id'] - ); - } - echo ""; - echo ""; - - echo ""; - echo "" . __('Background event') . ""; - echo ""; - if ($canedit) { - Dropdown::showYesNo('background', $this->fields['background'], -1, [ - 'rand' => $rand, - ]); - } else { - echo Dropdown::getYesNo($this->fields['background']); - } - echo ""; - echo ""; - - echo "" . _n('Calendar', 'Calendars', 1) . ""; - echo ""; - Planning::showAddEventClassicForm([ - 'items_id' => $this->fields['id'], - 'itemtype' => $this->getType(), - 'begin' => $this->fields['begin'], - 'end' => $this->fields['end'], - 'rand_user' => $this->fields['users_id'], - 'rand' => $rand_plan, - ]); - echo ""; - - echo "" . __('Repeat') . ""; - echo ""; - echo self::showRepetitionForm($this->fields['rrule'] ?? '', [ - 'rand' => $rand_rrule - ]); - echo ""; - - echo "" . __('Description') . "" . - ""; - - if ($canedit) { - Html::textarea([ - 'name' => 'text', - 'value' => RichText::getSafeHtml($this->fields["text"], true), - 'enable_richtext' => true, - 'enable_fileupload' => true, - 'rand' => $rand, - 'editor_id' => 'text' . $rand, - ]); - } else { - echo "
    "; - echo RichText::getEnhancedHtml($this->fields["text"]); - echo "
    "; - } - - echo ""; - if ($is_ajax && $is_rrule) { $options['candel'] = false; $options['addbuttons'] = []; @@ -367,8 +174,13 @@ function template_update{$rand}(value) { } } - $this->showFormButtons($options); - + TemplateRenderer::getInstance()->display('pages/assistance/planning/external_event.html.twig', [ + 'item' => $this, + 'rand' => $rand, + 'rand_plan' => $rand_plan, + 'rand_rrule' => $rand_rrule, + 'params' => $options, + ]); return true; } diff --git a/src/PlanningRecall.php b/src/PlanningRecall.php index 5979063c317a..536a2ac4dd6b 100644 --- a/src/PlanningRecall.php +++ b/src/PlanningRecall.php @@ -33,6 +33,7 @@ * --------------------------------------------------------------------- */ +use Glpi\Application\View\TemplateRenderer; use Glpi\DBAL\QueryExpression; use Glpi\DBAL\QueryFunction; @@ -264,7 +265,7 @@ public static function getForItem(string $itemtype, int $items_id, int $users_id * - value : integer preselected value for before_time * - field : string field used as time mark (default begin) * - * @return void|boolean print out an HTML select box or return false if mandatory fields are not ok + * @return void|false print out an HTML select box or return false if mandatory fields are not ok **/ public static function dropdown($options = []) { @@ -281,12 +282,12 @@ public static function dropdown($options = []) $p[$key] = $val; } } - if (!($item = getItemForItemtype($p['itemtype']))) { + if (!(getItemForItemtype($p['itemtype']))) { return false; } $pr = new self(); - // Get recall for item and user + // Get recall for item and user if ($pr->getFromDBForItemAndUser($p['itemtype'], $p['items_id'], $p['users_id'])) { $p['value'] = $pr->fields['before_time']; } @@ -321,52 +322,13 @@ public static function dropdown($options = []) 'value' => $p['value'], 'rand' => $p['rand'], ]); - echo ""; - echo ""; - echo ""; - echo ""; - return true; - } - - /** - * Dispaly specific form when no edit right - * - * Mandatory options : itemtype, items_id - * - * @param array $options array of possible options: - * - itemtype : string itemtype - * - items_id : integer id of the item - * - users_id : integer id of the user (if not set used login user) - * - value : integer preselected value for before_time - * - field : string field used as time mark (default begin) - * - * @return void|boolean print out an HTML select box or return false if mandatory fields are not ok - **/ - public static function specificForm($options = []) - { - // Default values - $p['itemtype'] = ''; - $p['items_id'] = 0; - $p['users_id'] = Session::getLoginUserID(); - $p['value'] = Entity::CONFIG_NEVER; - $p['field'] = 'begin'; - - if (is_array($options) && count($options)) { - foreach ($options as $key => $val) { - $p[$key] = $val; - } - } - if (!($item = getItemForItemtype($p['itemtype']))) { - return false; - } - - echo "
    "; - echo "
    "; - self::dropdown($options); - echo " "; - echo ""; - echo "
    "; - Html::closeForm(); + // language=Twig + echo TemplateRenderer::getInstance()->renderFromStringTemplate(<< + + + +TWIG, $p); } /** diff --git a/templates/components/form/fields_macros.html.twig b/templates/components/form/fields_macros.html.twig index 9732b2af1aa7..d10ba04ea7e1 100644 --- a/templates/components/form/fields_macros.html.twig +++ b/templates/components/form/fields_macros.html.twig @@ -107,6 +107,20 @@ {{ _self.field(name, field, label, options) }} {% endmacro %} +{% macro urlField(name, value, label = '', options = {}) %} + {% set options = { + 'id': '%id%' + }|merge(options) %} + + {% set field %} + {% import 'components/form/basic_inputs_macros.html.twig' as _inputs %} + {{ _inputs.input(name, value, options|merge({ + 'type': 'url' + })) }} + {% endset %} + + {{ _self.field(name, field, label, options) }} +{% endmacro %} {% macro checkboxField(name, value, label = '', options = {}) %} {% set options = { diff --git a/templates/pages/assistance/planning/add_classic_event.html.twig b/templates/pages/assistance/planning/add_classic_event.html.twig new file mode 100644 index 000000000000..40a63abddf6a --- /dev/null +++ b/templates/pages/assistance/planning/add_classic_event.html.twig @@ -0,0 +1,113 @@ +{# + # --------------------------------------------------------------------- + # + # GLPI - Gestionnaire Libre de Parc Informatique + # + # http://glpi-project.org + # + # @copyright 2015-2024 Teclib' and contributors. + # @copyright 2003-2014 by the INDEPNET Development Team. + # @licence https://www.gnu.org/licenses/gpl-3.0.html + # + # --------------------------------------------------------------------- + # + # LICENSE + # + # This file is part of GLPI. + # + # This program is free software: you can redistribute it and/or modify + # it under the terms of the GNU 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 General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with this program. If not, see . + # + # --------------------------------------------------------------------- + #} + +{% import 'components/form/fields_macros.html.twig' as fields %} + +{% if params.id is defined and params.id > 0 %} + +{% endif %} + +{% set rand = params.rand|default(random()) %} +{% set display_dates = params._display_dates|default(true) %} + +
    +
    + {% if display_dates %} + {{ fields.datetimeField('plan[begin]', begin, __('Start date'), { + maybeempty: false, + canedit: true, + mindate: '', + maxdate: '', + mintime: mintime, + maxtime: config('planning_end'), + rand: rand, + field_class: 'col-12', + }) }} + {% endif %} + + {% set period_field %} + {% if params.rand_user %} + + {% do call('Planning::showCheckPlanning', [_post|merge({ + parent_itemtype: params.parent_itemtype|default(''), + parent_items_id: params.parent_items_id|default(''), + parent_fk_field: params.parent_fk_field|default(''), + })]) %} + + {% endif %} + {% do call('Dropdown::showTimeStamp', ['plan[_duration]', { + min: 0, + max: 50 * constant('HOUR_TIMESTAMP'), + value: default_delay, + emptylabel: display_dates ? __('Specify an end date') : constant('Dropdown::EMPTY_VALUE'), + }]) %} +
    +
    + + {% if display_dates %} + {% if default_delay == 0 %} + {% set params = params|merge({ + duration: 0, + }) %} + + {% endif %} + + {% endif %} + {% endset %} + {{ fields.htmlField('', period_field, __('Period'), { + field_class: 'col-12', + }) }} + + {% if (params.id is not defined or params.id == 0) and params.itemtype is defined and call('PlanningRecall::isAvailable') %} + {{ fields.dropdownField('PlanningRecall', 'planningrecalls_id', 0, _x('Planning', 'Reminder'), { + itemtype: params.itemtype, + items_id: params.items_id, + rand: rand, + field_class: 'col-12', + }) }} + {% endif %} +
    +
    diff --git a/templates/pages/assistance/planning/availability.html.twig b/templates/pages/assistance/planning/availability.html.twig new file mode 100644 index 000000000000..4a763645020f --- /dev/null +++ b/templates/pages/assistance/planning/availability.html.twig @@ -0,0 +1,172 @@ +{# + # --------------------------------------------------------------------- + # + # GLPI - Gestionnaire Libre de Parc Informatique + # + # http://glpi-project.org + # + # @copyright 2015-2024 Teclib' and contributors. + # @copyright 2003-2014 by the INDEPNET Development Team. + # @licence https://www.gnu.org/licenses/gpl-3.0.html + # + # --------------------------------------------------------------------- + # + # LICENSE + # + # This file is part of GLPI. + # + # This program is free software: you can redistribute it and/or modify + # it under the terms of the GNU 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 General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with this program. If not, see . + # + # --------------------------------------------------------------------- + #} + +{% import 'components/form/fields_macros.html.twig' as fields %} +{% import 'components/form/basic_inputs_macros.html.twig' as inputs %} + +{% set begin_hour = config('planning_begin')|split(':', 2)[0] %} +{% set end_hour = config('planning_end')|split(':', 2)[0] %} +{% set real_begin = begin ~ ' ' ~ config('planning_begin') %} +{% set real_end = end ~ ' ' ~ (config('planning_end') == '24:00' ? '23:59:59' : config('planning_end')) %} + +{% set col_size = (100 - 15) // (end_hour - begin_hour) %} + +
    + +
    + {{ fields.datetimeField('begin', begin, __('Start'), { + field_class: 'col-12 col-sm-6 col-lg-4', + is_horizontal: false + }) }} + {{ fields.datetimeField('end', end, __('End'), { + field_class: 'col-12 col-sm-6 col-lg-4', + is_horizontal: false + }) }} + {% if users|length > 1 %} + {{ fields.dropdownArrayField('limitto', params.limitto, { 0: __('All') } + users, null, { + nolabel: true, + field_class: 'col-12 col-sm-6 col-lg-4 align-self-end', + input_class: 'col-xxl-7 mt-3', + is_horizontal: false + }) }} + {% endif %} +
    + + +
    + {{ inputs.submit('checkavailability', _x('button', 'Search'), 1) }} +
    + + {% for user_id, user_display_name in displayed_users %} + {% set interv = {} %} + {% for itemtype in config('planning_types') %} + {% set interv = interv|merge(call([itemtype, 'populatePlanning'], [{ + who: user_id, + whogroup: 0, + begin: real_begin, + end: real_end, + }])) %} + {% set interv = interv|merge(call([itemtype, 'populateNotPlanned'], [{ + who: user_id, + whogroup: 0, + begin: real_begin, + end: real_end, + }])|default({})) %} + {% endfor %} +
    + + + + + + {% for hour in range(begin_hour, end_hour - 1) %} + + {% endfor %} + + + + {% set day_begin = date(real_begin).getTimestamp() %} + {% set day_end = date(real_end).getTimestamp() %} + {% set day_step = min(constant('DAY_TIMESTAMP'), day_end - day_begin) %} + {# used days are tracked as a workaround to Twig not letting the range step be over the (max - range) #} + {% set used_days = [] %} + {% for time in range(day_begin, day_end, day_step) %} + {% set begin_quarter = begin_hour * 4 %} + {% set end_quarter = end_hour * 4 %} + {% set current_day = time|date('Y-m-d') %} + {% if current_day not in used_days %} + {% set used_days = used_days + [current_day] %} + + + {% for i in begin_quarter..(end_quarter-1) %} + {% set begin_time = date(current_day.getTimestamp() + i * constant("HOUR_TIMESTAMP") / 4)|date('Y-m-d H:i:s') %} + {% set end_time = date(current_day.getTimestamp() + (i + 1) * constant("HOUR_TIMESTAMP") / 4)|date('Y-m-d H:i:s') %} + {% set interv_toremove = [] %} + {% set begin_act = end_time %} + {% set end_act = begin_time %} + + {% for k, data in interv %} + {% if data['begin'] >= begin_time and data['end'] <= end_time %} + {# IN #} + {% set begin_act = min(begin_act, data['begin']) %} + {% set end_act = min(end_act, data['end']) %} + {% set interv_toremove = interv_toremove + k %} + {% elseif data['begin'] < begin_time and data['end'] > end_time %} + {# THROUGH #} + {% set begin_act = begin_time %} + {% set end_act = end_time %} + {% elseif data['begin'] >= begin_time and data['begin'] < end_time %} + {# BEGIN #} + {% set end_act = end_time %} + {% elseif data['end'] > begin_time and data['end'] <= end_time %} + {# END #} + {% set begin_act = begin_time %} + {% set end_act = min(end_act, data['end']) %} + {% set interv_toremove = interv_toremove + k %} + {% endif %} + {% endfor %} + {# Remove keys from interv if needed #} + {% set interv = interv|filter((v, k) => k not in interv_toremove) %} + + {% if begin_act < end_act %} + {% if begin_act <= begin_time and end_act >= end_time %} + {# Activity in quarter #} + + {% else %} + {# Activity in part of the quarter #} + {% if begin_act <= begin_time %} + + {% else %} + + {% endif %} + {% endif %} + {% else %} + {# No Activity #} + + {% endif %} + {% endfor %} + + {% endif %} + {% endfor %} + +
    {{ user_display_name }}
    {{ '%02d'|format(hour) ~ ':00' }}
    {{ current_day|formatted_datetime }}
    +
    + {% endfor %} +
    +
    + {{ __('Caption') }} + {{ __('Available') }} + {{ __('Unavailable') }} +
    +
    +
    diff --git a/templates/pages/assistance/planning/external_event.html.twig b/templates/pages/assistance/planning/external_event.html.twig new file mode 100644 index 000000000000..2fd75a0ba244 --- /dev/null +++ b/templates/pages/assistance/planning/external_event.html.twig @@ -0,0 +1,207 @@ +{# + # --------------------------------------------------------------------- + # + # GLPI - Gestionnaire Libre de Parc Informatique + # + # http://glpi-project.org + # + # @copyright 2015-2024 Teclib' and contributors. + # @copyright 2003-2014 by the INDEPNET Development Team. + # @licence https://www.gnu.org/licenses/gpl-3.0.html + # + # --------------------------------------------------------------------- + # + # LICENSE + # + # This file is part of GLPI. + # + # This program is free software: you can redistribute it and/or modify + # it under the terms of the GNU 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 General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with this program. If not, see . + # + # --------------------------------------------------------------------- + #} + +{% extends 'generic_show_form.html.twig' %} +{% import 'components/form/fields_macros.html.twig' as fields %} + +{% block form_fields %} + {% set field_options = { + label_class: 'col-4', + input_class: 'col-8', + } %} + {% if params.canedit %} + {{ fields.dropdownField( + 'PlanningExternalEventTemplate', + 'planningexternaleventtemplates_id', + item.fields['planningexternaleventtemplates_id'], + 'PlanningExternalEventTemplate'|itemtype_name(1), + { + entity: item.getEntityID(), + rand: rand, + onchange: 'template_update' ~ rand ~ '(this.value);' + }|merge(field_options) + ) }} + {{ fields.nullField() }} + + {% endif %} + + {% if params.start is defined %} + + {% endif %} + + {% if params.canedit %} + {{ fields.textField('name', item.fields['name'], __('Title'), { + id: 'textfield_name' ~ rand, + }|merge(field_options)) }} + {% else %} + {{ fields.htmlField('', item.fields['name']|e('html'), __('Title'), field_options) }} + {% endif %} + {{ fields.nullField() }} + + {% if params.from_planning_edit_ajax is defined and params.from_planning_edit_ajax %} + + {% endif %} + + {{ fields.dropdownField('User', 'users_id', item.fields['users_id'], 'User'|itemtype_name(1), { + right: 'all' + }|merge(field_options)) }} + {{ fields.nullField() }} + + {{ fields.dropdownField('User', 'users_id_guests[]', item.fields['users_id_guests'], __('Guests'), { + right: 'all', + multiple: true, + helper: __("Each guest will have a read-only copy of this event")|e + }|merge(field_options)) }} + {{ fields.nullField() }} + + {% set status_field %} + {% if params.canedit %} + {% do call('Planning::dropdownState', ['state', item.fields['state'], true, { + rand: rand, + }]) %} + {% else %} + {{ call('Planning::getState', [item.fields['state']])|e('html') }} + {% endif %} + {% endset %} + {{ fields.htmlField('', status_field, __('Status'), field_options) }} + {{ fields.nullField() }} + + {% set category_field %} + {% if params.canedit %} + {{ fields.dropdownField('PlanningEventCategory', 'planningeventcategories_id', item.fields['planningeventcategories_id'], null, { + no_label: true, + rand: rand + }) }} + {% else %} + {{ get_item_name('PlanningEventCategory', item.fields['planningeventcategories_id']) }} + {% endif %} + {% endset %} + {{ fields.htmlField('', category_field, _n('Category', 'Categories', 1), field_options) }} + {{ fields.nullField() }} + + {% set bg_event_field %} + {% if params.canedit %} + {{ fields.dropdownYesNo('background', item.fields['background'], null, { + no_label: true, + rand: rand + }) }} + {% else %} + {{ item.fields['background'] ? __('Yes')|e('html') : __('No')|e('html') }} + {% endif %} + {% endset %} + {{ fields.htmlField('', bg_event_field, __('Background event'), field_options) }} + {{ fields.nullField() }} + + {% set calendar_field %} + {% do call('Planning::showAddEventClassicForm', [{ + items_id: item.getID(), + itemtype: get_class(item), + begin: item.fields['begin'], + end: item.fields['end'], + rand_user: item.fields['users_id'], + rand: rand_plan, + }]) %} + {% endset %} + {{ fields.htmlField('', calendar_field, _n('Calendar', 'Calendars', 1), { + label_class: 'col-4' + }|merge(field_options)) }} + {{ fields.nullField() }} + + {{ fields.htmlField('', call('PlanningExternalEvent::showRepetitionForm', [item.fields['rrule']|default(''), { + rand: rand_rrule, + }]), __('Repeat'), { + full_width: true, + label_class: 'col-2' + }) }} + {{ fields.nullField() }} + + {% if params.canedit %} + {{ fields.textareaField('text', item.fields['text']|safe_html, __('Description'), { + full_width: true, + label_class: 'col-2', + input_class: 'col-10', + editor_id: 'text' ~ rand, + enable_richtext: true, + enable_fileupload: true, + rand: rand + }) }} + {% else %} + {{ fields.htmlField('', item.fields['text']|enhanced_html, __('Description'), { + full_width: true, + label_class: 'col-2', + input_class: 'col-10', + }) }} + {% endif %} +{% endblock %} diff --git a/templates/pages/assistance/planning/filters.html.twig b/templates/pages/assistance/planning/filters.html.twig new file mode 100644 index 000000000000..4d32f7907bf8 --- /dev/null +++ b/templates/pages/assistance/planning/filters.html.twig @@ -0,0 +1,63 @@ +{# + # --------------------------------------------------------------------- + # + # GLPI - Gestionnaire Libre de Parc Informatique + # + # http://glpi-project.org + # + # @copyright 2015-2024 Teclib' and contributors. + # @copyright 2003-2014 by the INDEPNET Development Team. + # @licence https://www.gnu.org/licenses/gpl-3.0.html + # + # --------------------------------------------------------------------- + # + # LICENSE + # + # This file is part of GLPI. + # + # This program is free software: you can redistribute it and/or modify + # it under the terms of the GNU 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 General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with this program. If not, see . + # + # --------------------------------------------------------------------- + #} + +
    +
    + +
    +
    + {% set headings = { + 'filters': __('Filters'), + 'plannings': __('Plannings') + } %} + {% for filter_heading, filters in session('glpi_plannings') %} + {% if filter_heading in headings|keys %} +
    +

    + {{ headings[filter_heading] }} + {% if filter_heading == 'plannings' %} + + + + {% endif %} +

    +
      + {% for filter_key, filter_data in filters %} + {% do call('Planning::showSingleLinePlanningFilter', [filter_key, filter_data, {filter_color_index: 0}]) %} + {% endfor %} +
    +
    + {% endif %} + {% endfor %} +
    +
    diff --git a/templates/pages/assistance/planning/single_filter.html.twig b/templates/pages/assistance/planning/single_filter.html.twig new file mode 100644 index 000000000000..41c5b135fbb1 --- /dev/null +++ b/templates/pages/assistance/planning/single_filter.html.twig @@ -0,0 +1,108 @@ +{# + # --------------------------------------------------------------------- + # + # GLPI - Gestionnaire Libre de Parc Informatique + # + # http://glpi-project.org + # + # @copyright 2015-2024 Teclib' and contributors. + # @copyright 2003-2014 by the INDEPNET Development Team. + # @licence https://www.gnu.org/licenses/gpl-3.0.html + # + # --------------------------------------------------------------------- + # + # LICENSE + # + # This file is part of GLPI. + # + # This program is free software: you can redistribute it and/or modify + # it under the terms of the GNU 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 General Public License for more details. + # + # You should have received a copy of the GNU General Public License + # along with this program. If not, see . + # + # --------------------------------------------------------------------- + #} + +{% import 'components/form/basic_inputs_macros.html.twig' as inputs %} +
  • + + {% if filter_data.type != 'event_filter' %} + + {% endif %} + {% set label_title %} + {{ title }} + {% if filter_data.type == 'external' and (not filter_data.url|default('') is url_safe) %} + + {% endif %} + {% endset %} + {{ inputs.label(label_title, filter_key) }} + + + {# Colors not for groups #} + {% if filter_data.type != 'group_users' and filter_key != 'OnlyBgEvents' %} + + + + {% endif %} + {% if filter_data.type == 'group_users' %} + + {% endif %} + {% if filter_data.type != 'event_filter' %} +
    + + +
    + {% endif %} +
    + {% if caldav_item_url is not empty and filter_data.type == 'group_users' %} +
      + {% for user_key, user_data in filter_data.users %} + {% do call('Planning::showSingleLinePlanningFilter', [user_key, user_data, { + show_delete: false, + filter_color_index: params.filter_color_index, + }]) %} + {% endfor %} +
    + {% endif %} +