diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 71c61da..ac8dad1 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -20,7 +20,7 @@ jobs: name: "Generate CI matrix" uses: "glpi-project/plugin-ci-workflows/.github/workflows/generate-ci-matrix.yml@v1" with: - glpi-version: "11.0.x" + glpi-version: "12.0.x" ci: name: "GLPI ${{ matrix.glpi-version }} - php:${{ matrix.php-version }} - ${{ matrix.db-image }}" needs: "generate-ci-matrix" diff --git a/CHANGELOG.md b/CHANGELOG.md index 0693377..f2897d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] +- GLPI 12 Compatibility + ### Fixed - Fixed the issue where a ticket could be solved without a solution diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..da234c6 --- /dev/null +++ b/rector.php @@ -0,0 +1,50 @@ +withPaths([ + __DIR__ . '/front', + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + // uncomment to reach your current PHP version + // ->withPhpSets() + ->withTypeCoverageLevel(0) + ->withDeadCodeLevel(0) + ->withCodeQualityLevel(0); diff --git a/setup.php b/setup.php index 2ad112c..54153da 100644 --- a/setup.php +++ b/setup.php @@ -40,11 +40,11 @@ // Minimal GLPI version, inclusive /** @phpstan-ignore theCodingMachineSafe.function (safe to assume this isn't already defined) */ -define("PLUGIN_MOREOPTIONS_MIN_GLPI_VERSION", "11.0.0"); +define("PLUGIN_MOREOPTIONS_MIN_GLPI_VERSION", "12.0.0"); // Maximum GLPI version, exclusive /** @phpstan-ignore theCodingMachineSafe.function (safe to assume this isn't already defined) */ -define("PLUGIN_MOREOPTIONS_MAX_GLPI_VERSION", "11.0.99"); +define("PLUGIN_MOREOPTIONS_MAX_GLPI_VERSION", "12.0.99"); /** * Init hooks of the plugin. diff --git a/src/Config.php b/src/Config.php index 00c53e1..a3f52a9 100644 --- a/src/Config.php +++ b/src/Config.php @@ -46,8 +46,8 @@ class Config extends CommonDBTM { - public $dohistory = true; - public static $rightname = 'config'; + public bool $dohistory = true; + public static string $rightname = 'config'; public const CONFIG_PARENT = \Entity::CONFIG_PARENT; public static function getMenuName(): string { diff --git a/src/Controller.php b/src/Controller.php index 2f5100b..f567a83 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -68,8 +68,8 @@ class Controller extends CommonDBTM { - public $dohistory = true; - public static $rightname = 'config'; + public bool $dohistory = true; + public static string $rightname = 'config'; private static bool $solution_check_done = false; public static function getTypeName($nb = 0): string { @@ -554,12 +554,11 @@ public static function checkTaskRequirements(CommonDBTM $item): CommonDBTM /** * Hooked on {@link \Glpi\Plugin\Hooks::POST_ITEM_FORM}. Renders, into the task creation/edit * form, a script that marks the fields configured as mandatory in moreoptions (category, - * duration, technician, technician group) with the usual red "required" marker and blocks - * client-side submission of the form until they are filled. + * duration, technician, technician group) with the usual red "required" marker. * - * The server-side block already performed by {@see self::checkTaskRequirements()} on actual - * submission (PRE_ITEM_ADD) is kept as-is; this only prevents the user from submitting an - * incomplete task in the first place. + * This is purely a visual hint: the actual block on submission is performed server-side by + * {@see self::checkTaskRequirements()} (PRE_ITEM_ADD), whose message is what gets shown to + * the user. * * @param array $params */ @@ -596,9 +595,8 @@ public static function markMandatoryTaskFields(array $params): void TemplateRenderer::getInstance()->display('@moreoptions/timeline_task_mandatory_fields.html.twig', [ // Unique per-call anchor: lets the injected script find its own
reliably. - 'marker_id' => 'moreoptions-task-mandatory-' . bin2hex(random_bytes(6)), - 'field_labels' => $labels, - 'error_message' => __('To create this task, you must fill in the following fields:', 'moreoptions'), + 'marker_id' => 'moreoptions-task-mandatory-' . bin2hex(random_bytes(6)), + 'field_labels' => $labels, ]); } diff --git a/templates/timeline_solution_warning.html.twig b/templates/timeline_solution_warning.html.twig index 2c20063..4872542 100644 --- a/templates/timeline_solution_warning.html.twig +++ b/templates/timeline_solution_warning.html.twig @@ -101,14 +101,20 @@ // A tooltip (not a popover) so it keeps GLPI's dark bubble style: core deliberately // styles popovers with a light background (see `.popover .popover-body` in // _base.scss), while tooltips stay dark. Tooltips support HTML content too. - solutionToggle.setAttribute('data-bs-toggle', 'tooltip'); - solutionToggle.setAttribute('data-bs-placement', 'right'); - solutionToggle.setAttribute('data-bs-trigger', 'hover focus'); - solutionToggle.setAttribute('data-bs-html', 'true'); - solutionToggle.setAttribute('title', {{ tooltip_content|json_encode|raw }}); - - if (typeof initTooltips === 'function') { - initTooltips(footer); + // + // The toggle already carries `data-bs-toggle="collapse"` (it's still a working + // "Add a solution" action): it can't also be set to "tooltip", as Bootstrap only + // honors one `data-bs-toggle` value per element and would silently drop the + // collapse behavior. The tooltip is instantiated directly through the JS API + // instead of going through `initTooltips()`'s `[data-bs-toggle="tooltip"]` selector. + if (typeof bootstrap !== 'undefined' && bootstrap.Tooltip) { + new bootstrap.Tooltip(solutionToggle, { + delay: {show: 50, hide: 50}, + html: true, + placement: 'right', + trigger: 'hover focus', + title: {{ tooltip_content|json_encode|raw }}, + }); } })(); diff --git a/templates/timeline_task_mandatory_fields.html.twig b/templates/timeline_task_mandatory_fields.html.twig index fe1b43f..81cdedb 100644 --- a/templates/timeline_task_mandatory_fields.html.twig +++ b/templates/timeline_task_mandatory_fields.html.twig @@ -32,24 +32,25 @@ {# # Rendered through the core `Hooks::POST_ITEM_FORM` hook (see Controller::markMandatoryTaskFields), # right before the task form's closing tag. Marks the configured fields as required (red asterisk - # on their label, matching GLPI's own convention) and blocks client-side submission until they - # are filled. + # on their label, matching GLPI's own convention). + # + # This is purely a visual hint: submission itself is left to go through normally so the existing + # server-side block ({@see Controller::checkTaskRequirements()}, on PRE_ITEM_ADD) can render its + # usual accessible, styled message. An earlier version of this script also intercepted the + # form's `submit` event to reject it client-side through a `window.alert()`: that stopped the + # AJAX round-trip entirely, so the styled server-side message never got a chance to show, and a + # native `alert()` popup isn't reachable through accessible/role-based locators anyway. #} diff --git a/tests/e2e/fixtures/moreoptions_fixture.ts b/tests/e2e/fixtures/moreoptions_fixture.ts new file mode 100644 index 0000000..c9f5402 --- /dev/null +++ b/tests/e2e/fixtures/moreoptions_fixture.ts @@ -0,0 +1,34 @@ +/** + * ------------------------------------------------------------------------- + * MoreOptions plugin for GLPI + * ------------------------------------------------------------------------- + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * ------------------------------------------------------------------------- + * @copyright Copyright (C) 2025 by the MoreOptions plugin team. + * @license MIT https://opensource.org/licenses/mit-license.php + * @link https://github.com/pluginsGLPI/moreoptions + * ------------------------------------------------------------------------- + */ + +// Re-export the core GLPI fixture so plugin specs share the same authenticated +// `test`/`expect` and the worker/profile/api helpers. +export * from '../../../../../tests/e2e/fixtures/glpi_fixture'; diff --git a/tests/e2e/pages/MoreOptionsConfigPage.ts b/tests/e2e/pages/MoreOptionsConfigPage.ts new file mode 100644 index 0000000..2011e18 --- /dev/null +++ b/tests/e2e/pages/MoreOptionsConfigPage.ts @@ -0,0 +1,72 @@ +/** + * ------------------------------------------------------------------------- + * MoreOptions plugin for GLPI + * ------------------------------------------------------------------------- + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * ------------------------------------------------------------------------- + * @copyright Copyright (C) 2025 by the MoreOptions plugin team. + * @license MIT https://opensource.org/licenses/mit-license.php + * @link https://github.com/pluginsGLPI/moreoptions + * ------------------------------------------------------------------------- + */ + +import { Locator, Page } from '@playwright/test'; +// eslint-disable-next-line playwright/no-raw-locators +import { GlpiPage } from '../../../../../tests/e2e/pages/GlpiPage'; + +// The plugin registers a single tab (index 1) on the Entity item. +const CONFIG_TAB = 'GlpiPlugin\\Moreoptions\\Config$1'; + +export class MoreOptionsConfigPage extends GlpiPage +{ + public readonly save_button: Locator; + + public constructor(page: Page) + { + super(page); + + this.save_button = this.getButton('Save'); + } + + public async goto(entities_id: number): Promise + { + await this.page.goto( + `/front/entity.form.php?id=${entities_id}&forcetab=${CONFIG_TAB}` + ); + } + + /** + * The config fields are rendered without an associated