Skip to content
Merged
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
1 change: 1 addition & 0 deletions css/includes/_includes.scss
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ $is-dark: false !default;
@import "components/form/form-renderer";
@import "components/form/form-destination";
@import "components/form/form-translations";
@import "components/form/helpdesk-home-config-for-empty-entity";
@import "components/fuzzy";
@import "components/global-menu";
@import "components/illustration-picker";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*!
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2025 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 <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/

.helpdesk-home-config-for-empty-entity-wrapper {
// Hide the tiles and actions
[data-glpi-helpdesk-config-tiles], [data-glpi-helpdesk-config-actions] {
display: none !important;
}
}
2 changes: 1 addition & 1 deletion js/glpi_dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ const glpi_toast = (title, message, css_class, options = {}) => {
location = 'bottom-right';
}
const html = `<div class='toast-container ${location} p-3 messages_after_redirect'>
<div id='toast_js_${toast_id}' class='toast ${animation_classes}' role='alert' aria-live='assertive' aria-atomic='true'>
<div id='toast_js_${toast_id}' class='toast ${animation_classes}' role='alert' aria-live='assertive' aria-atomic='true' aria-label="${message}">
<div class='toast-header ${css_class}'>
<strong class='me-auto'>${title}</strong>
<button type='button' class='btn-close' data-bs-dismiss='toast' aria-label='${__('Close')}'></button>
Expand Down
94 changes: 94 additions & 0 deletions js/modules/Helpdesk/HelpdeskConfigForEmptyEntityController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2025 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/>.
*
* ---------------------------------------------------------------------
*/

export class GlpiHelpdeskConfigForEmptyEntityController
{
/** @type {HTMLElement} */
#container;

constructor(container)
{
this.#container = container;
this.#initEventsHandlers();
this.#enableActions();
}

#initEventsHandlers()
{
// Watch for click on the "define tiles" button.
this.#getDefineTilesButton().addEventListener('click', () => {
this.#getSpecificConfigDiv().classList.add('d-none');
this.#getOriginalHelpdeskConfigDiv()
.classList
.remove('helpdesk-home-config-for-empty-entity-wrapper')
;
});
}

#enableActions()
{
this.#getDefineTilesButton().classList.remove('pointer-events-none');
this.#getCopyTilesButton().classList.remove('pointer-events-none');
}

/** @return {HTMLElement} */
#getDefineTilesButton()
{
return this.#container.querySelector(
'[data-glpi-helpdesk-config-tiles-empty-entity-define-tiles]'
);
}

/** @return {HTMLElement} */
#getCopyTilesButton()
{
return this.#container.querySelector(
'[data-glpi-helpdesk-config-tiles-empty-entity-copy-tiles]'
);
}

/** @return {HTMLElement} */
#getOriginalHelpdeskConfigDiv()
{
return this.#container.querySelector(
'[data-glpi-helpdesk-config-tiles-empty-entity-original-content]'
);
}

/** @return {HTMLElement} */
#getSpecificConfigDiv()
{
return this.#container.querySelector(
'[data-glpi-helpdesk-config-tiles-empty-entity-specific]'
);
}
}
8 changes: 8 additions & 0 deletions phpunit/functional/Glpi/Helpdesk/DefaultDataManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
use CommonITILActor;
use Computer;
use DbTestCase;
use Entity;
use Glpi\Form\AccessControl\FormAccessControlManager;
use Glpi\Form\AccessControl\FormAccessParameters;
use Glpi\Helpdesk\DefaultDataManager;
Expand Down Expand Up @@ -351,6 +352,13 @@ public function testRequestFormShouldBeAccessibleBySelfServiceUsers(): void
public function testsTilesAreAddedAfterInstallation(): void
{
$this->assertEquals(5, countElementsInTable(Item_Tile::getTable()));

// Default tiles must be attached to the root entity
$profile_tiles = (new Item_Tile())->find([]);
foreach ($profile_tiles as $row) {
$this->assertEquals(Entity::class, $row['itemtype_item']);
$this->assertEquals(0, $row['items_id_item']);
}
}

public function testNoTilesAreCreatedWhenDatabaseIsNotEmpty(): void
Expand Down
97 changes: 97 additions & 0 deletions phpunit/functional/Glpi/Helpdesk/Tile/TilesManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@

namespace tests\units\Glpi\Form\Helpdesk\TilesManagerTest;

use CommonDBTM;
use DbTestCase;
use Entity;
use Glpi\Helpdesk\Tile\ExternalPageTile;
use Glpi\Helpdesk\Tile\FormTile;
use Glpi\Helpdesk\Tile\GlpiPageTile;
use Glpi\Helpdesk\Tile\Item_Tile;
use Glpi\Helpdesk\Tile\TileInterface;
use Glpi\Helpdesk\Tile\TilesManager;
use Glpi\Session\SessionInfo;
use Glpi\Tests\FormBuilder;
Expand Down Expand Up @@ -150,6 +153,7 @@ public function testOnlyActiveFormTileAreFound(): void
$session = new SessionInfo(
profile_id: $profile->getID(),
active_entities_ids: [$test_entity_id],
current_entity_id: $test_entity_id,
);
$tiles = $manager->getVisibleTilesForSession($session);

Expand Down Expand Up @@ -191,6 +195,7 @@ public function testOnlyFormWithValidAccessPoliciesAreFound(): void
$session = new SessionInfo(
profile_id: $profile->getID(),
active_entities_ids: [$test_entity_id],
current_entity_id: $test_entity_id,
);
$tiles = $manager->getVisibleTilesForSession($session);

Expand Down Expand Up @@ -238,6 +243,7 @@ public function testOnlyFormVisibleFromActiveEntityAreFound(): void
$session = new SessionInfo(
profile_id: $profile->getID(),
active_entities_ids: [$test_entity_id],
current_entity_id: $test_entity_id,
);
$tiles = $manager->getVisibleTilesForSession($session);

Expand Down Expand Up @@ -412,4 +418,95 @@ public function testDeleteTile(): void
$this->assertFalse(Item_Tile::getById($profile_tile_id_2));
$this->assertFalse(GlpiPageTile::getById($tile_id));
}

public function testTilesFromRootEntityAreFoundWhenCurrentProfileHasNoConfig(): void
{
$test_entity_id = $this->getTestRootEntity(only_id: true);

// Arrange: create a self service profile without tiles
$manager = $this->getManager();
$profile = $this->createItem(Profile::class, [
'name' => 'Helpdesk profile',
'interface' => 'helpdesk',
]);

// Act: get tiles
$session = new SessionInfo(
profile_id: $profile->getID(),
active_entities_ids: [$test_entity_id],
current_entity_id: $test_entity_id,
);
$tiles = $manager->getVisibleTilesForSession($session);

// Assert: the default tiles from the root entity should be found
$this->assertCount(3, $tiles);
}

public function testTilesFromSubEntityAreFoundWhenCurrentProfileHasNoConfig(): void
{
$test_entity = $this->getTestRootEntity();
$test_entity_id = $test_entity->getID();

// Arrange: create a self service profile without tiles
$manager = $this->getManager();
$profile = $this->createItem(Profile::class, [
'name' => 'Helpdesk profile',
'interface' => 'helpdesk',
]);

// Create a tile for the current entity
$manager->addTile($test_entity, ExternalPageTile::class, [
'title' => "GLPI project",
'description' => "Link to GLPI project website",
'illustration' => "request-service",
'url' => "https://glpi-project.org",
]);

// Act: get tiles
$session = new SessionInfo(
profile_id: $profile->getID(),
active_entities_ids: [$test_entity_id],
current_entity_id: $test_entity_id,
);
$tiles = $manager->getVisibleTilesForSession($session);

// Assert: the unique tile from the current entity should be found
$this->assertCount(1, $tiles);
}

public function testCanCopyTilesFromParentEntity(): void
{
$test_entity = $this->getTestRootEntity();

// Need an active session to create entities
$this->login();

// Arrange: create an entity
$my_entity = $this->createItem(Entity::class, [
'name' => "My test entity",
'entities_id' => $test_entity->getID(),
]);

// Act: copy parent entity tiles into the new entity
$manager = $this->getManager();
$before_copy = $manager->getTilesForItem($my_entity);
$manager->copyTilesFromParentEntity($my_entity);
$after_copy = $manager->getTilesForItem($my_entity);

// Asset: tiles should be empty before copy and identical to root entity after copy.
$this->assertEmpty($before_copy);
$this->assertNotEmpty($after_copy);
$root_tiles = $manager->getTilesForItem(Entity::getById(0));

// Normalize values for comparison by remove ids
$normalize = function (CommonDBTM&TileInterface $tile): array {
$fields = $tile->fields;
unset($fields['id']);
return $fields;
};
$root_tiles_fields = array_map($normalize, $root_tiles);
$after_copy_fields = array_map($normalize, $after_copy);

$this->assertEquals($root_tiles_fields, $after_copy_fields);
}
}
54 changes: 53 additions & 1 deletion src/Entity.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@
use Glpi\DBAL\QueryExpression;
use Glpi\DBAL\QueryFunction;
use Glpi\Event;
use Glpi\Helpdesk\Tile\LinkableToTilesInterface;
use Glpi\Helpdesk\Tile\TilesManager;
use Glpi\Plugin\Hooks;

/**
* Entity class
*/
class Entity extends CommonTreeDropdown
class Entity extends CommonTreeDropdown implements LinkableToTilesInterface
{
use Glpi\Features\Clonable;
use MapGeolocation;
Expand Down Expand Up @@ -528,6 +530,7 @@ public function getTabNameForItem(CommonGLPI $item, $withtemplate = 0)
$ong[7] = self::createTabEntry(__('UI customization'), 0, $item::class, 'ti ti-palette');
}
$ong[8] = self::createTabEntry(__('Security'), 0, $item::class, 'ti ti-shield-lock');
$ong[9] = self::createTabEntry(__('Helpdesk home'), 0, $item::class, 'ti ti-home');

return $ong;
}
Expand Down Expand Up @@ -572,6 +575,9 @@ public static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $
case 8:
self::showSecurityOptions($item);
break;
case 9:
$item->showHelpdeskHomeConfig();
break;
}
}
return true;
Expand Down Expand Up @@ -3120,4 +3126,50 @@ public static function getEntitySelectorTree(): array

return $entitiestree;
}

public function showHelpdeskHomeConfig(): bool
{
$tiles_manager = new TilesManager();

if (
// Is not root entity
$this->getId() !== 0
// Editable
&& static::canUpdate()
&& $this->canUpdateItem()
// Has no tiles
&& count($tiles_manager->getTilesForItem($this)) === 0
) {
// Render a custom template when there are no tiles as we want the
// user to preview the tiles from the parent entities and to be able
// to copy them into the current entity if needed.
$twig = TemplateRenderer::getInstance();
$twig->display(
'pages/admin/helpdesk_home_config_for_empty_entity.html.twig',
[
'tiles_manager' => $tiles_manager,
'itemtype_item' => static::class,
'items_id_item' => $this->getID(),
'info_text' => $this->getConfigInformationText(),
'parent_tiles' => $tiles_manager->getTilesForEntityRecursive($this)
]
);
} else {
$tiles_manager->showConfigFormForItem($this);
}

return true;
}

#[Override]
public function acceptTiles(): bool
{
return true;
}

#[Override]
public function getConfigInformationText(): ?string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please note that this method name is not really helping in th Entity context. It should be changed, but please do it in another PR so we can merge this one and integrate it in the 11.0.0-beta release.

{
return __("Tiles may be overriden by profile.");
}
}
Loading