diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c9c8ee4..7ffa3c06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [UNRELEASED] +### Added + +- Permit to Inject GlpiAsset + ### Fixed - Fix : confirm dialog js escaping diff --git a/inc/commoninjectionlib.class.php b/inc/commoninjectionlib.class.php index a628cdb9..5ac07073 100644 --- a/inc/commoninjectionlib.class.php +++ b/inc/commoninjectionlib.class.php @@ -27,11 +27,12 @@ * @link https://github.com/pluginsGLPI/datainjection * ------------------------------------------------------------------------- */ - +use Glpi\Asset\Asset; use Glpi\DBAL\QueryExpression; use Glpi\DBAL\QuerySubQuery; use Glpi\Exception\Http\HttpException; use Glpi\Features\AssignableItem; +use GlpiPlugin\Datainjection\Glpi\Asset\AssetInjection; use function Safe\preg_match; use function Safe\preg_replace; @@ -337,6 +338,9 @@ public static function getItemtypeByInjection($injectionClassName) public static function getItemtypeByInjectionClass($injectionClass) { + if ($injectionClass instanceof AssetInjection) { + return Toolbox::ucfirst(getItemTypeForTable($injectionClass->getVirtualTable())); + } return Toolbox::ucfirst(getItemTypeForTable($injectionClass->getTable())); } @@ -346,7 +350,10 @@ public static function getItemtypeByInjectionClass($injectionClass) * * @param string $itemtype the itemtype * - * @return PluginDatainjectionInjectionInterface the injection class instance + * Every injection class both implements the interface and extends CommonDBTM, + * so the returned instance exposes the CommonDBTM API (getTable(), ...) too. + * + * @return PluginDatainjectionInjectionInterface&CommonDBTM the injection class instance */ public static function getInjectionClassInstance($itemtype) { @@ -358,7 +365,15 @@ public static function getInjectionClassInstance($itemtype) $injectionClass = ucfirst($itemtype) . 'Injection'; } - if (!is_a($injectionClass, PluginDatainjectionInjectionInterface::class, true)) { + if (!class_exists($injectionClass)) { + plugin_datainjection_creationInjectableAssets(); + } + + + if ( + !is_a($injectionClass, PluginDatainjectionInjectionInterface::class, true) + || !is_a($injectionClass, CommonDBTM::class, true) + ) { throw new HttpException(500, 'Class ' . $injectionClass . ' is not a valid class'); } return new $injectionClass(); @@ -1999,6 +2014,10 @@ private function dataAlreadyInDB($injectionClass, $itemtype) } } + if ($injectionClass instanceof Asset && method_exists($injectionClass, 'getAssetDefinitionID')) { + $where['assets_assetdefinitions_id'] = $injectionClass->getAssetDefinitionID(); + } + //Add additional parameters specific to this itemtype (or function checkPresent exists) if (method_exists($injectionClass, 'checkPresent')) { $extra = $injectionClass->checkPresent($this->values, $options); diff --git a/inc/injectiontype.class.php b/inc/injectiontype.class.php index 9cfb291d..33f9be85 100644 --- a/inc/injectiontype.class.php +++ b/inc/injectiontype.class.php @@ -28,6 +28,7 @@ * ------------------------------------------------------------------------- */ use Glpi\Exception\Http\HttpException; +use GlpiPlugin\Datainjection\Glpi\Asset\AssetInjection; use function Safe\json_decode; use function Safe\json_encode; @@ -78,6 +79,10 @@ public static function getItemtypes($only_primary = false) $name = $plugin->getName() . ': '; } $name .= call_user_func([$type, 'getTypeName']); + + if ($instance instanceof AssetInjection) { + $typename = $instance->getVirtualType(); + } $values[$typename] = $name; } } @@ -388,7 +393,7 @@ public static function getUsedMappingsOrInfos($options = []) foreach ($options as $option) { if ( isset($option['table']) - && ($option['table'] == getItemTypeForTable($data['itemtype'])) + && ($option['table'] == $injectionClass->getTable()) && ($option['linkfield'] == $data['value']) && ($option['displaytype'] != 'multiline_text') && ($mapping_or_info['value'] != $data['value']) diff --git a/phpstan.neon b/phpstan.neon index db69ea19..275425e8 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,6 +9,7 @@ parameters: - ajax - front - inc + - src - hook.php - setup.php scanDirectories: diff --git a/setup.php b/setup.php index 12788cb7..af23464b 100644 --- a/setup.php +++ b/setup.php @@ -28,6 +28,10 @@ * ------------------------------------------------------------------------- */ +use Glpi\Asset\AssetDefinition; +use Glpi\Asset\AssetDefinitionManager; +use GlpiPlugin\Datainjection\Glpi\Asset\Capacity\IsInjectableCapacity; + use function Safe\define; use function Safe\mkdir; @@ -49,6 +53,12 @@ function plugin_init_datainjection() /** @var array $INJECTABLE_TYPES */ global $PLUGIN_HOOKS, $CFG_GLPI, $INJECTABLE_TYPES; + if (!isset($CFG_GLPI['injectable_types'])) { + $CFG_GLPI['injectable_types'] = []; + } + $asset_definition_manager = AssetDefinitionManager::getInstance(); + $asset_definition_manager->registerCapacity(new IsInjectableCapacity()); + $PLUGIN_HOOKS['csrf_compliant']['datainjection'] = true; $PLUGIN_HOOKS['migratetypes']['datainjection'] = 'plugin_datainjection_migratetypes_datainjection'; @@ -84,6 +94,8 @@ function plugin_init_datainjection() $PLUGIN_HOOKS['add_javascript']['datainjection'] = 'js/datainjection.js'; $INJECTABLE_TYPES = []; + + // plugin_datainjection_creationInjectableAssets(); } } @@ -115,8 +127,8 @@ function plugin_version_datainjection() function getTypesToInject(): void { /** @var array $INJECTABLE_TYPES */ - /** @var array $PLUGIN_HOOKS */ - global $INJECTABLE_TYPES,$PLUGIN_HOOKS; + /** @var array $CFG_GLPI */ + global $INJECTABLE_TYPES,$CFG_GLPI; if (count($INJECTABLE_TYPES)) { // already populated @@ -220,6 +232,101 @@ function getTypesToInject(): void ]; //Add plugins Plugin::doHook('plugin_datainjection_populate'); + + // Register injectable assets dynamically + plugin_datainjection_registerInjectableAssets(); + + plugin_datainjection_creationInjectableAssets(); +} + + +/** + * Register injection classes for each injectable asset definition + */ +function plugin_datainjection_registerInjectableAssets(): void +{ + /** @var array $INJECTABLE_TYPES */ + /** @var array $CFG_GLPI */ + global $INJECTABLE_TYPES, $CFG_GLPI; + + if (!isset($CFG_GLPI['injectable_types']) || empty($CFG_GLPI['injectable_types'])) { + return; + } + + // For each injectable asset definition, create and register a distinct class + foreach ($CFG_GLPI['injectable_types'] as $definition_id => $itemtype) { + // Get the asset definition to extract the system name + + $definition = AssetDefinition::getById($definition_id); + if ($definition->getAssetClassName() === $itemtype) { + // Use the system name to create a nice class name + $system_name = ucfirst($definition->fields['system_name']);//strtolower() + $injection_class = 'PluginDatainjection' . $system_name . 'AssetInjection'; + + // Only create if not already registered + if (!isset($INJECTABLE_TYPES[$injection_class])) { + $INJECTABLE_TYPES[$injection_class] = 'datainjection'; + } + } + } +} + +/** + * Register injection classes for each injectable asset definition + */ +function plugin_datainjection_creationInjectableAssets(): void +{ + /** @var array $CFG_GLPI */ + global $CFG_GLPI; + + if (!isset($CFG_GLPI['injectable_types']) || empty($CFG_GLPI['injectable_types'])) { + return; + } + + + // For each injectable asset definition, create and register a distinct class + foreach ($CFG_GLPI['injectable_types'] as $definition_id => $itemtype) { + $definition = AssetDefinition::getById($definition_id); + // Get the asset definition to extract the system name + if ($definition->getAssetClassName() === $itemtype) { + // Use the system name to create a nice class name + $system_name = ucfirst($definition->fields['system_name']);//strtolower() + $injection_class = 'PluginDatainjection' . $system_name . 'AssetInjection'; + + plugin_datainjection_createAssetInjectionClass($injection_class, $definition_id); + } + } +} + +/** + * Dynamically create an asset injection class + */ +function plugin_datainjection_createAssetInjectionClass(string $class_name, int $definition_id): void +{ + + // Check if class already exists + if (class_exists($class_name)) { + return; + } + + // Create the class dynamically (not final, so it can be extended) + $code = <<. + * ------------------------------------------------------------------------- + * @copyright Copyright (C) 2007-2023 by DataInjection plugin team. + * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + * @link https://github.com/pluginsGLPI/datainjection + * ------------------------------------------------------------------------- + */ + +namespace GlpiPlugin\Datainjection\Glpi\Asset; + +use Glpi\Asset\Asset; +use Glpi\Asset\AssetDefinition; +use Glpi\Asset\AssetDefinitionManager; +use PluginDatainjectionCommonInjectionLib; +use PluginDatainjectionInjectionInterface; +use Search; + +use function Safe\preg_replace; + +abstract class AssetInjection extends Asset implements PluginDatainjectionInjectionInterface +{ + protected static string $definition_system_name = ''; + protected static int $fixed_asset_definition_id = 0; + + public static function getAssetDefinitionID(): int + { + + // If a fixed ID is set (for dynamically created classes), use it + if (static::$fixed_asset_definition_id > 0) { + return static::$fixed_asset_definition_id; + } + + return 0; + } + + /** + * Get the first active asset definition to use as system name + */ + protected static function getAssetDefinitionSystemName(): string + { + // Get first active asset definition from the database + // This ensures we have a valid definition to use + $manager = AssetDefinitionManager::getInstance(); + foreach ($manager->getDefinitions() as $definition) { + if ($definition->fields['id'] == static::getAssetDefinitionID()) { + return $definition->fields['system_name']; + } + } + + // Fallback to a default if none found + return ''; + } + + public static function getTypeName($nb = 0) + { + $manager = AssetDefinitionManager::getInstance(); + foreach ($manager->getDefinitions() as $definition) { + if ($definition->fields['id'] == static::getAssetDefinitionID()) { + return $definition->fields['label']; + } + } + // Fallback to a default if none found + return ''; + } + + public static function getDefinition(): AssetDefinition + { + if (empty(static::$definition_system_name)) { + static::$definition_system_name = static::getAssetDefinitionSystemName(); + } + + return parent::getDefinition(); + } + + + public static function getVirtualType($classname = null) + { + return 'Glpi\\CustomAsset\\' . static::getAssetDefinitionSystemName() . 'Asset'; + } + + public static function getVirtualTable($classname = null) + { + return getTableForItemType(self::getVirtualType()); + } + + public static function getTable($classname = null) + { + return getTableForItemType(Asset::class); + } + + public static function canCreate(): bool + { + return true; + } + + public function isPrimaryType() + { + return true; + } + + public function connectedTo() + { + return []; + } + + public function isNullable($field) + { + return true; + } + + + public static function replaceTableName(array $data): array + { + foreach ($data as $key => $value) { + if (is_array($value)) { + $data[$key] = self::replaceTableName($value); // récursif + } else { + if ($value === self::getTable()) { + $data[$key] = self::getVirtualTable(); + } + } + } + return $data; + } + /** + * @see plugins/datainjection/inc/PluginDatainjectionInjectionInterface::getOptions() + */ + public function getOptions($primary_type = '') + { + + // Build search options manually to avoid registration checks + // $tab = Search::getOptions(static::class); + $tab = Search::getOptions(self::getVirtualType()); + + $tab = self::replaceTableName($tab); + unset($tab[4]); + unset($tab[40]); + + $tab2 = [ + // [ + // 'id' => '4', + // 'table' => 'glpi_assets_assettypes', + // 'field' => 'name', + // 'linkfield' => 'assets_assettypes_id', + // 'name' => __('Type'), + // 'datatype' => 'dropdown', + // 'injectable' => true, + // ], + // [ + // 'id' => '40', + // 'table' => 'glpi_assets_assetmodels', + // 'field' => 'name', + // 'linkfield' => 'assets_assetmodels_id', + // 'name' => __('Model'), + // 'datatype' => 'dropdown', + // 'injectable' => true, + // ], + [ + 'id' => '200', + 'table' => $this->getTable(), + 'field' => 'assets_assetdefinitions_id', + 'linkfield' => 'assets_assetdefinitions_id', + 'name' => AssetDefinition::getTypeName(), + 'datatype' => 'number', + 'massiveaction' => false, + 'injectable' => true, + ], + ]; + + $tab = array_merge($tab, $tab2); + //Remove some options because some fields cannot be imported + $blacklist = PluginDatainjectionCommonInjectionLib::getBlacklistedOptions(static::class); + $blacklist = []; + $notimportable = ['300', '301']; + $options['ignore_fields'] = array_merge($blacklist, $notimportable); + + return PluginDatainjectionCommonInjectionLib::addToSearchOptions($tab, $options, $this); + } + + + /** + * @param array $fields_toinject array + **/ + public function getValueForAdditionalMandatoryFields($fields_toinject = []) + { + $fields_toinject[self::getVirtualType()]['assets_assetdefinitions_id'] = static::getAssetDefinitionID(); + + return $fields_toinject; + } + + /** + * @see plugins/datainjection/inc/PluginDatainjectionInjectionInterface::addOrUpdateObject() + */ + public function addOrUpdateObject($values = [], $options = []) + { + + $lib = new PluginDatainjectionCommonInjectionLib($this, $values, $options); + $lib->processAddOrUpdate(); + return $lib->getInjectionResults(); + } +} diff --git a/src/Glpi/Asset/Capacity/IsInjectableCapacity.php b/src/Glpi/Asset/Capacity/IsInjectableCapacity.php new file mode 100644 index 00000000..37a9f0a3 --- /dev/null +++ b/src/Glpi/Asset/Capacity/IsInjectableCapacity.php @@ -0,0 +1,127 @@ +. + * ------------------------------------------------------------------------- + * @copyright Copyright (C) 2007-2023 by DataInjection plugin team. + * @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html + * @link https://github.com/pluginsGLPI/datainjection + * ------------------------------------------------------------------------- + */ + +namespace GlpiPlugin\Datainjection\Glpi\Asset\Capacity; + +use Glpi\Asset\AssetDefinitionManager; +use Glpi\Asset\Capacity\AbstractCapacity; +use Glpi\Asset\CapacityConfig; +use Override; + +final class IsInjectableCapacity extends AbstractCapacity +{ + #[Override] + public function getLabel(): string + { + return __('Injectable', 'datainjection'); + } + + #[Override] + public function getIcon(): string + { + return 'ti ti-download'; + } + + #[Override] + public function getDescription(): string + { + return __("Inject objects list"); + } + + // public function getCloneRelations(): array + // { + // return [ + // Infocom::class, + // ]; + // } + // + // public function isUsed(string $classname): bool + // { + // return parent::isUsed($classname) + // && $this->countAssetsLinkedToPeerItem($classname, Infocom::class) > 0; + // } + + #[Override] + public function getCapacityUsageDescription(string $classname): string + { + // return sprintf( + // __('Used by %1$s of %2$s assets'), + // $this->countAssetsLinkedToPeerItem($classname, Infocom::class), + // $this->countAssets($classname) + // ); + return ''; + } + + public function onClassBootstrap(string $classname, CapacityConfig $config): void + { + global $CFG_GLPI; + + // if (!isset($CFG_GLPI['injectable_types'])) { + // $CFG_GLPI['injectable_types'] = []; + // } + $manager = AssetDefinitionManager::getInstance(); + foreach ($manager->getDefinitions() as $definition) { + $itemtype = $definition->getAssetClassName(); + if ($itemtype == $classname) { + if (!in_array($classname, $CFG_GLPI['injectable_types'])) { + $CFG_GLPI['injectable_types'][$definition->fields['id']] = $classname; + } + } + } + + $this->registerToTypeConfig('injectable_types', $classname); + + // CommonGLPI::registerStandardTab($classname, Infocom::class, 50); + } + + public function onCapacityDisabled(string $classname, CapacityConfig $config): void + { + // Unregister from infocom types + // $this->unregisterFromTypeConfig('injectable_types', $classname); + // + // // Delete related infocom data + // $infocom = new Infocom(); + // $infocom->deleteByCriteria(['itemtype' => $classname], force: true, history: false); + // + // $infocom_search_options = Infocom::rawSearchOptionsToAdd($classname); + // + // // Clean history related to infocoms + // $this->deleteFieldsLogs($classname, $infocom_search_options); + // + // // Clean display preferences + // $this->deleteDisplayPreferences($classname, $infocom_search_options); + } + + // #[Override] + // public function onObjectInstanciation(Asset $object, CapacityConfig $config): void + // { + // $object->fields['_added_by_hasinjectablecapacity'] = 'abc'; + // } +}