diff --git a/ajax/form/form_tags.php b/ajax/form/form_tags.php
new file mode 100644
index 00000000000..b3d556a78bb
--- /dev/null
+++ b/ajax/form/form_tags.php
@@ -0,0 +1,56 @@
+.
+ *
+ * ---------------------------------------------------------------------
+ */
+
+use Glpi\Form\Form;
+
+include('../../inc/includes.php');
+
+// The user must be able to respond to forms.
+Session::checkRight(Form::$rightname, UPDATE);
+
+// Get tags
+$tags = [
+ [
+ 'label' => 'Exemple tag 1',
+ ],
+ [
+ 'label' => 'Exemple tag 2',
+ ],
+ [
+ 'label' => 'Exemple tag 3',
+ ],
+];
+header('Content-Type: application/json');
+echo json_encode($tags);
diff --git a/css/includes/components/_richtext.scss b/css/includes/components/_richtext.scss
index 6e571d96b68..1897ccbe779 100644
--- a/css/includes/components/_richtext.scss
+++ b/css/includes/components/_richtext.scss
@@ -39,6 +39,7 @@
body.mce-content-body {
margin: 15px 10px;
+ [data-form-tag="true"],
[data-user-mention="true"] {
cursor: default !important; // Overrides "not-allowed" cursor on noneditable content
}
@@ -100,3 +101,9 @@ body.mce-content-body {
border-radius: 3px;
padding: 5px;
}
+
+[data-form-tag="true"] {
+ background: rgba(0, 0, 0, 20%);
+ border-radius: 3px;
+ padding: 5px;
+}
diff --git a/js/RichText/FormTags.js b/js/RichText/FormTags.js
new file mode 100644
index 00000000000..f4cd219d5b6
--- /dev/null
+++ b/js/RichText/FormTags.js
@@ -0,0 +1,106 @@
+/**
+ * ---------------------------------------------------------------------
+ *
+ * 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 .
+ *
+ * ---------------------------------------------------------------------
+ */
+
+/* global tinymce */
+
+var GLPI = GLPI || {};
+GLPI.RichText = GLPI.RichText || {};
+
+/**
+ * Form tags rich text autocompleter.
+ *
+ * @since 11.0.0
+ */
+GLPI.RichText.FormTags = class
+{
+ /**
+ * Target tinymce editor.
+ * @type {TinyMCE.Editor}
+ */
+ #editor;
+
+ /**
+ * @param {Editor} editor
+ */
+ constructor(editor) {
+ this.#editor = editor;
+ }
+
+ /**
+ * Register as autocompleter to editor.
+ *
+ * @returns {void}
+ */
+ register() {
+ // Register autocompleter
+ this.#editor.ui.registry.addAutocompleter(
+ 'form_tags',
+ {
+ trigger: '#',
+ minChars: 0,
+ fetch: () => this.#fetchItems(),
+ onAction: (autocompleteApi, range, value) => {
+ this.#insertTag(autocompleteApi, range, value);
+ }
+ }
+ );
+ }
+
+ async #fetchItems() {
+ const url = CFG_GLPI.root_doc + '/ajax/form/form_tags.php';
+ const data = await $.get(url);
+
+ return data.map((tag) => ({
+ type: 'autocompleteitem',
+ value: JSON.stringify(tag),
+ text: tag.label,
+ }));
+ }
+
+ #insertTag(autocompleteApi, range, value) {
+ this.#editor.selection.setRng(range);
+ this.#editor.insertContent(this.#generateTagHtml(value));
+
+ autocompleteApi.hide();
+ }
+
+ #generateTagHtml(value) {
+ const tag = JSON.parse(value);
+ return `
+ ${tag.label}
+ `;
+ }
+};
diff --git a/src/Form/Destination/CommonITILField/ContentField.php b/src/Form/Destination/CommonITILField/ContentField.php
index c3834f3fbb0..8aeccf57e6b 100644
--- a/src/Form/Destination/CommonITILField/ContentField.php
+++ b/src/Form/Destination/CommonITILField/ContentField.php
@@ -69,6 +69,7 @@ public function renderConfigForm(
options|merge({
'enable_richtext': true,
'enable_images': false,
+ 'enable_form_tags': true,
})
) }}
TWIG;
diff --git a/src/Html.php b/src/Html.php
index 79c877d9f6e..81b8cbb30d0 100644
--- a/src/Html.php
+++ b/src/Html.php
@@ -5876,6 +5876,7 @@ public static function requireJs($name)
break;
case 'tinymce':
$_SESSION['glpi_js_toload'][$name][] = 'public/lib/tinymce.js';
+ $_SESSION['glpi_js_toload'][$name][] = 'js/RichText/FormTags.js';
$_SESSION['glpi_js_toload'][$name][] = 'js/RichText/UserMention.js';
$_SESSION['glpi_js_toload'][$name][] = 'js/RichText/ContentTemplatesParameters.js';
break;
diff --git a/src/RichText/RichText.php b/src/RichText/RichText.php
index a7c62b38fc5..19e152979f8 100644
--- a/src/RichText/RichText.php
+++ b/src/RichText/RichText.php
@@ -591,6 +591,7 @@ private static function getHtmlSanitizer(): HtmlSanitizer
// required for user mentions
'data-user-mention',
'data-user-id',
+ 'data-form-tag',
];
foreach ($rich_text_completion_attributes as $attribute) {
$config = $config->allowAttribute($attribute, 'span');
diff --git a/templates/components/form/basic_inputs_macros.html.twig b/templates/components/form/basic_inputs_macros.html.twig
index 7933c0b9e98..9e4cb47c178 100644
--- a/templates/components/form/basic_inputs_macros.html.twig
+++ b/templates/components/form/basic_inputs_macros.html.twig
@@ -308,6 +308,7 @@
'toolbar_location': 'top',
'init': true,
'placeholder': "",
+ 'enable_form_tags': false,
}|merge(options) %}
{% if options.fields_template.isMandatoryField(name) %}
@@ -340,6 +341,17 @@
]) %}
{% endif %}
+ {% if options.enable_form_tags %}
+
+ {% endif %}
+
{% if options.enable_mentions and config('use_notifications') %}