Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add proper endpoint for entity selection #18098

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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