Skip to content

Commit

Permalink
Add proper endpoint for entity selection
Browse files Browse the repository at this point in the history
  • Loading branch information
AdrienClairembault committed Oct 18, 2024
1 parent b9da5d9 commit 82926f5
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 74 deletions.
12 changes: 0 additions & 12 deletions front/central.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,6 @@
return;
}

// Manage entity change
if (isset($_GET["active_entity"])) {
if (!isset($_GET["is_recursive"])) {
$_GET["is_recursive"] = 0;
}
if (Session::changeActiveEntities($_GET["active_entity"], $_GET["is_recursive"])) {
if ($_GET["active_entity"] == $_SESSION["glpiactive_entity"]) {
Html::redirect(preg_replace("/(\?|&|" . urlencode('?') . "|" . urlencode('&') . ")?(entities_id|active_entity).*/", "", Html::getBackUrl()));
}
}
}

Session::checkCentralAccess();

Html::header(Central::getTypeName(1), $_SERVER['PHP_SELF'], 'central', 'central');
Expand Down
12 changes: 0 additions & 12 deletions front/helpdesk.public.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,6 @@
/** @var array $CFG_GLPI */
global $CFG_GLPI;

// Manage entity change
if (isset($_GET["active_entity"])) {
if (!isset($_GET["is_recursive"])) {
$_GET["is_recursive"] = 0;
}
if (Session::changeActiveEntities($_GET["active_entity"], $_GET["is_recursive"])) {
if ($_GET["active_entity"] == $_SESSION["glpiactive_entity"]) {
Html::redirect(preg_replace("/(\?|&|" . urlencode('?') . "|" . urlencode('&') . ")?(entities_id|active_entity).*/", "", Html::getBackUrl()));
}
}
}

// Redirect management
if (isset($_GET["redirect"])) {
Toolbox::manageRedirect($_GET["redirect"]);
Expand Down
6 changes: 3 additions & 3 deletions phpunit/functional/EntityTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1314,10 +1314,10 @@ public function testGetEntitySelectorTree(): void

$fn_find_entities_in_selector = static function ($selector, $entities, $parent_id = 0, &$found = []) use (&$fn_find_entities_in_selector) {
foreach ($selector as $item) {
// extract entity name from the first <a> element inside the 'title' property
// extract entity name from the first <button> element inside the 'title' property
$matches = [];
preg_match('/>(.+)<\/a>/', $item['title'], $matches);
$entity_name = $matches[1];
preg_match('/<button.*?>(.*?)<\/button>/s', $item['title'], $matches);
$entity_name = trim($matches[1]);
foreach ($entities as $child) {
if ($child['name'] === $entity_name && $child['entities_id'] === $parent_id) {
$found[] = $child['id'];
Expand Down
34 changes: 17 additions & 17 deletions src/Entity.php
Original file line number Diff line number Diff line change
Expand Up @@ -3061,13 +3061,8 @@ private static function getEntityTree(int $entities_id_root): array

public static function getEntitySelectorTree(): array
{
/** @var array $CFG_GLPI */
global $CFG_GLPI;

$base_path = $CFG_GLPI['root_doc'] . "/front/central.php";
if (Session::getCurrentInterface() === 'helpdesk') {
$base_path = $CFG_GLPI["root_doc"] . "/front/helpdesk.public.php";
}
$token = Session::getNewCSRFToken();
$twig = TemplateRenderer::getInstance();

$ancestors = getAncestorsOf('glpi_entities', $_SESSION['glpiactive_entity']);

Expand All @@ -3077,28 +3072,33 @@ public static function getEntitySelectorTree(): array
$default_entity_id = $default_entity['id'];
$entitytree = $default_entity['is_recursive'] ? self::getEntityTree($default_entity_id) : [$default_entity['id'] => $default_entity];

$adapt_tree = static function (&$entities) use (&$adapt_tree, $base_path) {
$adapt_tree = static function (&$entities) use (&$adapt_tree, $token, $twig) {
foreach ($entities as $entities_id => &$entity) {
$entity['key'] = $entities_id;

$title = "<a href='$base_path?active_entity={$entities_id}'>" . htmlspecialchars($entity['name']) . "</a>";
$entity['title'] = $title;
unset($entity['name']);

if (isset($entity['tree']) && count($entity['tree']) > 0) {
$entity['folder'] = true;

$entity['title'] .= "<a href='$base_path?active_entity={$entities_id}&is_recursive=1'>
<i class='fas fa-angle-double-down ms-1' data-bs-toggle='tooltip' data-bs-placement='right' title='" . __s('+ sub-entities') . "'></i>
</a>";
$is_recursive = true;

$children = $adapt_tree($entity['tree']);
$entity['children'] = array_values($children);
} else {
$is_recursive = false;
}

unset($entity['tree']);
$entity['title'] = $twig->render('layout/parts/profile_selector_form.html.twig', [
'id' => $entities_id,
'name' => $entity['name'],
'is_recursive' => $is_recursive,
// To avoid generating one token per entity (which may
// make us reach our token limit too fast), we reuse a
// common one.
'csrf_token' => $token,
]);
}

unset($entity);

return $entities;
};
$adapt_tree($entitytree);
Expand Down
72 changes: 72 additions & 0 deletions src/Glpi/Controller/Session/ChangeEntityController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2024 Teclib' and contributors.
* @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 <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/

namespace Glpi\Controller\Session;

use Glpi\Controller\AbstractController;
use Glpi\Exception\Http\AccessDeniedHttpException;
use Glpi\Http\Firewall;
use Glpi\Security\Attribute\SecurityStrategy;
use Html;
use Session;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

final class ChangeEntityController extends AbstractController
{
#[Route(
"/Session/ChangeEntity",
name: "glpi_change_entity",
methods: "POST",
)]
#[SecurityStrategy(Firewall::STRATEGY_AUTHENTICATED)]
public function __invoke(Request $request): Response
{
// Read parameters
$full_structure = $request->request->getBoolean('full_structure');
$entity_id = $full_structure ? 'all' : $request->request->getInt('id');
$is_recursive = $request->request->getBoolean('is_recursive');

// Try to load new entity
if (!Session::changeActiveEntities($entity_id, $is_recursive)) {
throw new AccessDeniedHttpException();
}

// Redirect to previous page
$redirect = Html::getBackUrl();
return new RedirectResponse($redirect);
}
}
37 changes: 23 additions & 14 deletions templates/layout/parts/profile_selector.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,6 @@
</div>
</div>

{% set target = path("front/central.php") %}
{% if get_current_interface() == "helpdesk" %}
{% set target = path("front/helpdesk.public.php") %}
{% endif %}

{% set current_entity = session('glpiactive_entity_name') %}
{% set current_entity_short = session('glpiactive_entity_shortname') %}
{% if current_entity != current_entity_short %}
Expand Down Expand Up @@ -105,24 +100,38 @@
</span>
</div>

<form id="entsearchform{{ rand }}">
<div class="input-group">
{% set switch_to_full_structure_id = 'switch_to_full_structure_' ~ rand %}
<form
id="{{ switch_to_full_structure_id }}"
method="POST"
action="/Session/ChangeEntity"
>
<input type="hidden" name="full_structure" value="true">
<input type="hidden" name="_glpi_csrf_token" value="{{ csrf_token() }}" />
</form>

<div class="input-group">
<input type="text" class="form-control" name="entsearchtext" id="entsearchtext{{ rand }}"
placeholder="{{ __('Search entity') }}" autocomplete="off">
<button type="submit" class="btn btn-icon btn-primary" title="{{ __('Search') }}"
<button id="entsearchsubmit{{ rand }}" type="submit" class="btn btn-icon btn-primary" title="{{ __('Search') }}"
data-bs-toggle="tooltip" data-bs-placement="top">
<i class="ti ti-search"></i>
</button>
<a class="btn btn-icon btn-outline-secondary" href="#" id="entsearchtext{{ rand }}_clear"
title="{{ __("Clear search") }}" data-bs-toggle="tooltip" data-bs-placement="top">
<i class="ti ti-x"></i>
</a>
<a href="{{ target }}?active_entity=all" class="btn btn-secondary" role="button"
title="{{ __('Select all') }}" data-bs-toggle="tooltip" data-bs-placement="top">
<button
class="btn btn-secondary"
title="{{ __('Select all') }}"
data-bs-toggle="tooltip"
data-bs-placement="top"
form="{{ switch_to_full_structure_id }}"
type="submit"
>
<i class="ti ti-eye"></i>
</a>
</div>
</form>
</button>
</div>

<div class="fancytree-grid-container flexbox-item-grow entity_tree">
<table id="tree_entity{{ rand }}" aria-label="{{ __('Entity tree') }}">
Expand Down Expand Up @@ -246,7 +255,7 @@
$.ui.fancytree.getTree("#tree_entity{{ rand }}").filterBranches(search_text);
}
$('#entsearchform{{ rand }}').submit(function(event) {
$('#entsearchsubmit{{ rand }}').click(function(event) {
// cancel submit of entity search form
event.preventDefault();
Expand Down
57 changes: 57 additions & 0 deletions templates/layout/parts/profile_selector_form.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{#
# ---------------------------------------------------------------------
#
# GLPI - Gestionnaire Libre de Parc Informatique
#
# http://glpi-project.org
#
# @copyright 2015-2024 Teclib' and contributors.
# @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 <https://www.gnu.org/licenses/>.
#
# ---------------------------------------------------------------------
#}

<div class="d-flex align-items-center">
<form method="POST" action="/Session/ChangeEntity">
<button class="btn btn-link p-0 bg-transparent {{ is_recursive ? 'fw-bold' : '' }}">
{{ name }}
</button>
<input type="hidden" name="id" value="{{ id }}">
<input type="hidden" name="_glpi_csrf_token" value="{{ csrf_token }}">
</form>

{% if is_recursive %}
<form method="POST" action="/Session/ChangeEntity">
<button class="btn btn-link p-0 bg-transparent">
<i
class="ti ti-chevrons-down"
data-bs-toggle="tooltip"
data-bs-placement="right"
title="{{ __('+ sub-entities') }}"
></i>
</button>
<input type="hidden" name="id" value="{{ id }}">
<input type="hidden" name="is_recursive" value="true">
<input type="hidden" name="_glpi_csrf_token" value="{{ csrf_token }}">
</form>
{% endif %}
</div>
21 changes: 12 additions & 9 deletions tests/cypress/e2e/entities_selector.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ describe('Entities selector', () => {

// Go to any page; force the entity to be E2ETestEntity
cy.blockGLPIDashboards();
cy.visit('/front/central.php?active_entity=1');
cy.changeEntity(1);
cy.visit('/front/preference.php');
cy.get('header').findByTitle('Root entity > E2ETestEntity').should('exist');
});

after(() => {
cy.blockGLPIDashboards();
cy.visit('/front/central.php?active_entity=1&is_recursive=1');
cy.changeEntity(1, true);
});

it('Can switch to full structure', () => {
Expand All @@ -68,12 +69,16 @@ describe('Entities selector', () => {

// Go to entity 2
cy.get('header').findByTitle('Root entity > E2ETestEntity > E2ETestSubEntity2').should('not.exist');
cy.findByRole('gridcell', {'name': "E2ETestSubEntity2"})
.findByRole('link', {'name': "E2ETestSubEntity2"})
.as("entity_link")
cy.findByRole('button', {'name': "E2ETestSubEntity2"})
.as('entity_button')
.click()
;
cy.get('@entity_link').click(); // Not sure why but the link only work in cypress if you click twice...

// Not sure why but this button only work in cypress if you click it 3 times...
// This is not a timing issue, waiting before a click doesn't change anything.
cy.get('@entity_button').click();
cy.get('@entity_button').click();

cy.get('header').findByTitle('Root entity > E2ETestEntity > E2ETestSubEntity2').should('exist');
});

Expand All @@ -98,9 +103,7 @@ describe('Entities selector', () => {

// Enable sub entities
cy.get('header').findByTitle('Root entity > E2ETestEntity (tree structure)').should('not.exist');
cy.findByRole('gridcell', {'name': "Root entity > E2ETestEntity+ sub-entities"})
.findByLabelText('+ sub-entities')
.click();
cy.findAllByRole('gridcell').findByTitle('+ sub-entities').click();
cy.get('header').findByTitle('Root entity > E2ETestEntity (tree structure)').should('exist');
});
});
2 changes: 2 additions & 0 deletions tests/cypress/support/commands.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,7 @@ declare namespace Cypress {
startToDrag(): Chainable<any>
dropDraggedItemAfter(): Chainable<any>
checkAndCloseAlert(text: string): Chainable<any>
getCsrfToken(): Chainable<any>
changeEntity(entity: string|number, is_recursive: boolean): Chainable<any>
}
}
Loading

0 comments on commit 82926f5

Please sign in to comment.