From 1652f651ed491a97bb8853ee9e89ee6044750f33 Mon Sep 17 00:00:00 2001 From: Stanislas Kita Date: Thu, 29 Jan 2026 09:26:41 +0100 Subject: [PATCH] Feat(Core): Implement HL API --- hook.php | 130 +++++++++++++++++++++++++++++++++++++ setup.php | 188 ++++++++++++++++++++++++++++-------------------------- 2 files changed, 228 insertions(+), 90 deletions(-) diff --git a/hook.php b/hook.php index 3ccd34ea..f52897b9 100644 --- a/hook.php +++ b/hook.php @@ -28,6 +28,8 @@ * ------------------------------------------------------------------------- */ +use Glpi\Api\HL\Doc\Schema; + /** * Plugin install process * @@ -434,3 +436,131 @@ function plugin_fields_addWhere($link, $nott, $itemtype, $ID, $val, $searchtype) return null; } + +function plugin_fields_redefine_api_schemas(array $data): array +{ + global $DB; + + $fn_fieldTypeToAPIType = static function (string $type): array { + $type = explode('-', $type)[0]; + return match ($type) { + 'number' => [Schema::TYPE_NUMBER, Schema::FORMAT_NUMBER_FLOAT], + 'yesno' => [Schema::TYPE_BOOLEAN, Schema::FORMAT_BOOLEAN_BOOLEAN], + 'date' => [Schema::TYPE_STRING, Schema::FORMAT_STRING_DATE], + 'datetime' => [Schema::TYPE_STRING, Schema::FORMAT_STRING_DATE_TIME], + default => [Schema::TYPE_STRING, Schema::FORMAT_STRING_STRING], + }; + }; + + $new_schemas = []; + + foreach ($data['schemas'] as &$schema) { + if (!isset($schema['x-itemtype'])) { + continue; + } + + $itemtype = $schema['x-itemtype']; + $schema_name = $itemtype . '_CustomFields'; + //Note PluginFieldsContainer::findContainer already checks permissions + $container_id = PluginFieldsContainer::findContainer($schema['x-itemtype'], 'dom'); + if ($container_id !== null) { + $it = $DB->request([ + 'SELECT' => [ + 'glpi_plugin_fields_fields.*', + 'glpi_plugin_fields_containers.name AS container_name', + ], + 'FROM' => 'glpi_plugin_fields_fields', + 'LEFT JOIN' => [ + 'glpi_plugin_fields_containers' => [ + 'ON' => [ + 'glpi_plugin_fields_fields' => 'plugin_fields_containers_id', + 'glpi_plugin_fields_containers' => 'id', + ], + ], + ], + 'WHERE' => [ + 'plugin_fields_containers_id' => $container_id, + 'glpi_plugin_fields_fields.is_active' => 1, + ], + ]); + if (count($it) > 0) { + foreach ($it as $field) { + if (!isset($new_schemas[$schema_name])) { + $new_schemas[$schema_name] = [ + 'type' => Schema::TYPE_OBJECT, + 'properties' => [], + ]; + } + + $type_format = $fn_fieldTypeToAPIType($field['type']); + $table = strtolower(sprintf('glpi_plugin_fields_%s%ss', $schema['x-itemtype'], $field['container_name'])); + $sql_field = $field['name']; + if (str_starts_with((string) $field['type'], 'dropdown')) { + if (str_starts_with((string) $field['type'], 'dropdown-')) { + $dropdown_type = explode('-', (string) $field['type'], 2)[1]; + } else { + $dropdown_type = 'PluginFields' . ucfirst((string) $field['name']) . 'Dropdown'; + } + + $is_tree = is_subclass_of($dropdown_type, CommonTreeDropdown::class); + $dropdown_fk = $field['type'] === 'dropdown' ? $dropdown_type::getForeignKeyField() : $field['name']; + $new_schemas[$schema_name]['properties'][$field['name']] = [ + 'type' => Schema::TYPE_OBJECT, + 'x-join' => [ + 'table' => $dropdown_type::getTable(), // This is the table with the desired values + 'field' => 'id', + 'fkey' => $dropdown_fk, + 'ref_join' => [ + 'table' => $table, + 'fkey' => 'id', + 'field' => 'items_id', + 'condition' => [ + 'itemtype' => $schema['x-itemtype'], + ], + ], + ], + 'properties' => [ + 'id' => [ + 'type' => Schema::TYPE_INTEGER, + 'format' => Schema::FORMAT_INTEGER_INT64, + 'x-readonly' => true, + ], + 'value' => [ + 'type' => Schema::TYPE_STRING, + 'x-field' => $is_tree ? 'completename' : 'name', + ], + ], + ]; + } else { + $new_schemas[$schema_name]['properties'][$field['name']] = [ + 'type' => $type_format[0], + 'format' => $type_format[1], + 'x-join' => [ + // This is the table with the desired values + 'table' => $table, + 'fkey' => 'id', + 'field' => 'items_id', + 'condition' => [ + 'itemtype' => $schema['x-itemtype'], + ], + ], + 'x-field' => $sql_field, + 'x-readonly' => true, + ]; + } + } + + if (isset($new_schemas[$schema_name])) { + $schema['properties']['custom_fields'] = [ + 'type' => Schema::TYPE_OBJECT, + 'x-full-schema' => $schema_name, + 'properties' => $new_schemas[$schema_name]['properties'], + ]; + } + } + } + } + + $data['schemas'] = array_merge($data['schemas'], $new_schemas); + return $data; +} diff --git a/setup.php b/setup.php index bd70ff9a..061873bc 100644 --- a/setup.php +++ b/setup.php @@ -76,6 +76,7 @@ use Glpi\Form\Destination\FormDestinationTicket; use Glpi\Form\Migration\TypesConversionMapper; use Glpi\Form\QuestionType\QuestionTypesManager; +use Glpi\Plugin\Hooks; use Symfony\Component\Yaml\Yaml; /** @@ -99,116 +100,123 @@ function plugin_init_fields() $pluginfields_autoloader = new PluginFieldsAutoloader([PLUGINFIELDS_CLASS_PATH]); $pluginfields_autoloader->register(); - if ((Session::getLoginUserID() || isCommandLine()) && Plugin::isPluginActive('fields')) { - // Init hook about itemtype(s) for plugin fields - if (!isset($PLUGIN_HOOKS['plugin_fields'])) { - $PLUGIN_HOOKS['plugin_fields'] = []; - } + if (Plugin::isPluginActive('fields')) { + // This API integration cannot be done inside a login check since the plugin is initialized before the Router handles authentication + $PLUGIN_HOOKS[Hooks::REDEFINE_API_SCHEMAS]['fields'] = 'plugin_fields_redefine_api_schemas'; + + if ((Session::getLoginUserID() || isCommandLine())) { - // When a Category is changed during ticket creation - if ( - $_POST !== [] - && isset($_POST['_plugin_fields_type']) - && ($_SERVER['REQUEST_URI'] == Ticket::getFormURL()) - ) { - foreach ($_POST as $key => $value) { - if (!is_array($value)) { - $_SESSION['plugin']['fields']['values_sent'][$key] = $value; + // Init hook about itemtype(s) for plugin fields + if (!isset($PLUGIN_HOOKS['plugin_fields'])) { + $PLUGIN_HOOKS['plugin_fields'] = []; + } + + // When a Category is changed during ticket creation + if ( + $_POST !== [] + && isset($_POST['_plugin_fields_type']) + && ($_SERVER['REQUEST_URI'] == Ticket::getFormURL()) + ) { + foreach ($_POST as $key => $value) { + if (!is_array($value)) { + $_SESSION['plugin']['fields']['values_sent'][$key] = $value; + } } } - } - if (Plugin::isPluginActive('fusioninventory')) { - $PLUGIN_HOOKS['fusioninventory_inventory']['fields'] - = ['PluginFieldsInventory', 'updateInventory']; - } + if (Plugin::isPluginActive('fusioninventory')) { + $PLUGIN_HOOKS['fusioninventory_inventory']['fields'] + = ['PluginFieldsInventory', 'updateInventory']; + } - // complete rule engine - $PLUGIN_HOOKS['use_rules']['fields'] = ['PluginFusioninventoryTaskpostactionRule']; - $PLUGIN_HOOKS['rule_matched']['fields'] = 'plugin_fields_rule_matched'; + // complete rule engine + $PLUGIN_HOOKS['use_rules']['fields'] = ['PluginFusioninventoryTaskpostactionRule']; + $PLUGIN_HOOKS['rule_matched']['fields'] = 'plugin_fields_rule_matched'; - if (isset($_SESSION['glpiactiveentities'])) { - // add link in plugin page - $PLUGIN_HOOKS['config_page']['fields'] = 'front/container.php'; + if (isset($_SESSION['glpiactiveentities'])) { + // add link in plugin page + $PLUGIN_HOOKS['config_page']['fields'] = 'front/container.php'; - // add entry to configuration menu (only if user has read access to config) - if (Session::haveRight('config', READ)) { - $PLUGIN_HOOKS['menu_toadd']['fields'] = ['config' => PluginFieldsMenu::class]; - } + // add entry to configuration menu (only if user has read access to config) + if (Session::haveRight('config', READ)) { + $PLUGIN_HOOKS['menu_toadd']['fields'] = ['config' => PluginFieldsMenu::class]; + } - // add tabs to itemtypes - $itemtypes = array_unique(PluginFieldsContainer::getEntries()); - if ($itemtypes !== []) { - Plugin::registerClass( - 'PluginFieldsContainer', - ['addtabon' => $itemtypes], - ); - } + // add tabs to itemtypes + $itemtypes = array_unique(PluginFieldsContainer::getEntries()); + if ($itemtypes !== []) { + Plugin::registerClass( + 'PluginFieldsContainer', + ['addtabon' => $itemtypes], + ); + } - //include js and css - $debug = (isset($_SESSION['glpi_use_mode']) - && $_SESSION['glpi_use_mode'] == Session::DEBUG_MODE); - if (!$debug && file_exists(__DIR__ . '/public/css/fields.min.css')) { - $PLUGIN_HOOKS['add_css']['fields'][] = 'css/fields.min.css'; - } else { - $PLUGIN_HOOKS['add_css']['fields'][] = 'css/fields.scss'; - } + //include js and css + $debug = (isset($_SESSION['glpi_use_mode']) + && $_SESSION['glpi_use_mode'] == Session::DEBUG_MODE); + if (!$debug && file_exists(__DIR__ . '/public/css/fields.min.css')) { + $PLUGIN_HOOKS['add_css']['fields'][] = 'css/fields.min.css'; + } else { + $PLUGIN_HOOKS['add_css']['fields'][] = 'css/fields.scss'; + } - // Add/delete profiles to automaticaly to container - $PLUGIN_HOOKS['item_add']['fields']['Profile'] = ['PluginFieldsProfile', 'addNewProfile']; - $PLUGIN_HOOKS['pre_item_purge']['fields']['Profile'] = ['PluginFieldsProfile', 'deleteProfile']; + // Add/delete profiles to automaticaly to container + $PLUGIN_HOOKS['item_add']['fields']['Profile'] = ['PluginFieldsProfile', 'addNewProfile']; + $PLUGIN_HOOKS['pre_item_purge']['fields']['Profile'] = ['PluginFieldsProfile', 'deleteProfile']; - //load drag and drop javascript library on Package Interface + //load drag and drop javascript library on Package Interface - if ( - plugin_fields_script_endswith('container.form.php') - ) { - $PLUGIN_HOOKS['add_javascript']['fields'][] = 'lib/redips-drag-min.js'; - if (!$debug && file_exists(__DIR__ . '/public/js/drag-field-row.min.js')) { - $PLUGIN_HOOKS['add_javascript']['fields'][] = 'js/drag-field-row.min.js'; - } else { - $PLUGIN_HOOKS['add_javascript']['fields'][] = 'js/drag-field-row.js'; + if ( + plugin_fields_script_endswith('container.form.php') + ) { + $PLUGIN_HOOKS['add_javascript']['fields'][] = 'lib/redips-drag-min.js'; + if (!$debug && file_exists(__DIR__ . '/public/js/drag-field-row.min.js')) { + $PLUGIN_HOOKS['add_javascript']['fields'][] = 'js/drag-field-row.min.js'; + } else { + $PLUGIN_HOOKS['add_javascript']['fields'][] = 'js/drag-field-row.js'; + } } } - } - // Add Fields to Datainjection - if (Plugin::isPluginActive('datainjection')) { - $PLUGIN_HOOKS['plugin_datainjection_populate']['fields'] = 'plugin_datainjection_populate_fields'; - } + // Add Fields to Datainjection + if (Plugin::isPluginActive('datainjection')) { + $PLUGIN_HOOKS['plugin_datainjection_populate']['fields'] = 'plugin_datainjection_populate_fields'; + } - //Retrieve dom container - $itemtypes = PluginFieldsContainer::getUsedItemtypes(); - if ($itemtypes !== false) { - foreach ($itemtypes as $itemtype) { - $PLUGIN_HOOKS['pre_item_update']['fields'][$itemtype] = [ - 'PluginFieldsContainer', - 'preItemUpdate', - ]; - $PLUGIN_HOOKS['pre_item_add']['fields'][$itemtype] = [ - 'PluginFieldsContainer', - 'preItem', - ]; - $PLUGIN_HOOKS['item_add']['fields'][$itemtype] = [ - 'PluginFieldsContainer', - 'postItemAdd', - ]; - $PLUGIN_HOOKS['pre_item_purge'] ['fields'][$itemtype] = [ - 'PluginFieldsContainer', - 'preItemPurge', - ]; + //Retrieve dom container + $itemtypes = PluginFieldsContainer::getUsedItemtypes(); + if ($itemtypes !== false) { + foreach ($itemtypes as $itemtype) { + $PLUGIN_HOOKS['pre_item_update']['fields'][$itemtype] = [ + 'PluginFieldsContainer', + 'preItemUpdate', + ]; + $PLUGIN_HOOKS['pre_item_add']['fields'][$itemtype] = [ + 'PluginFieldsContainer', + 'preItem', + ]; + $PLUGIN_HOOKS['item_add']['fields'][$itemtype] = [ + 'PluginFieldsContainer', + 'postItemAdd', + ]; + $PLUGIN_HOOKS['pre_item_purge'] ['fields'][$itemtype] = [ + 'PluginFieldsContainer', + 'preItemPurge', + ]; + } } - } - // Display fields in any existing tab - $PLUGIN_HOOKS['post_item_form']['fields'] = [ - 'PluginFieldsField', - 'showForTab', - ]; + // Display fields in any existing tab + $PLUGIN_HOOKS['post_item_form']['fields'] = [ + 'PluginFieldsField', + 'showForTab', + ]; - // Register fields question type - plugin_fields_register_plugin_types(); + // Register fields question type + plugin_fields_register_plugin_types(); + } } + }