*/ namespace oat\taoQtiTest\models; use oat\oatbox\service\ConfigurableService; use oat\taoTests\models\runner\plugins\TestPluginService; use RuntimeException; class TestCategoryPresetProvider extends ConfigurableService { public const SERVICE_ID = 'taoQtiTest/CategoryPresetProvider'; public const GROUP_NAVIGATION = 'navigation'; public const GROUP_WARNING = 'warning'; public const GROUP_TOOLS = 'tools'; private $allPresets; private $isGroomed = false; /** * TestCategoryPresetProvider constructor. * * @param array $options * @param array $allPresets - allow override of preset list */ public function __construct(array $options = [], array $allPresets = []) { $this->allPresets = $allPresets; parent::__construct($options); } /** * @param string $presetGroup * @param TestCategoryPreset[]|TestCategoryPreset $presets */ public function register(string $presetGroup, $presets): void { if (!array_key_exists($presetGroup, $this->allPresets)) { return; } if (!is_array($presets)) { $presets = [$presets]; } foreach ($presets as $preset) { /** @noinspection TypeUnsafeArraySearchInspection */ if (!in_array($preset, $this->allPresets[$presetGroup]['presets'])) { $this->allPresets[$presetGroup]['presets'][] = $preset; } } } /** * Get all active presets * * @param bool $keepGroupKeys if `true` returns groups mapped to their group IDs * * @return array - the sorted preset list */ public function getPresets(bool $keepGroupKeys = false): array { if (empty($this->allPresets)) { $this->loadPresetFromProviders(); } $this->groomPresets(); return $keepGroupKeys ? $this->allPresets : array_values($this->allPresets); } public function findPresetGroupOrFail(string $groupId): array { $presets = $this->getPresets(true); if (!isset($presets[$groupId])) { throw new RuntimeException("Failed to fetch #$groupId preset group."); } return $presets[$groupId]; } /** * Get all active presets matching the given config. * * If a preset is linked to a feature flag, * we add it only if the config value matching the flag is true. * * For example, if a $aPreset->featureFlag = 'foo'; * The preset will be included only if $config['foo'] = true. * * If the config doesn't have a flag, we keep the preset. * * @param array $config a config flag list as { key : string => value : boolean } * * @return array the sorted preset list */ public function getAvailablePresets(array $config = []): array { //work on a clone $presets = array_merge([], $this->getPresets()); foreach ($presets as $groupId => &$presetGroup) { if (isset($presetGroup['presets'])) { //filter presets based on the config value //if the config has the flag, we check it's value //if the config doesn't have the flag, we keep the preset $presetGroup['presets'] = array_filter( $presetGroup['presets'], function ($preset) use ($config) { return $this->isPresetAvailable($preset, $config); } ); //remove empty groups if (count($presetGroup['presets']) === 0) { unset($presets[$groupId]); } } } return $presets; } protected function getPresetGroups(): array { return [ self::GROUP_NAVIGATION => [ 'groupId' => self::GROUP_NAVIGATION, 'groupLabel' => __('Test Navigation'), 'groupOrder' => 100, 'presets' => [], ], self::GROUP_WARNING => [ 'groupId' => self::GROUP_WARNING, 'groupLabel' => __('Navigation Warnings'), 'groupOrder' => 200, 'presets' => [], ], self::GROUP_TOOLS => [ 'groupId' => self::GROUP_TOOLS, 'groupLabel' => __('Test-Taker Tools'), 'groupOrder' => 300, 'presets' => [], ], ]; } /** * Is a preset available according to a configuration (ie. based on it's featureFlag) * * @param TestCategoryPreset $preset the preset to test * @param array $config the configuration * * @return boolean true if available */ private function isPresetAvailable(TestCategoryPreset $preset, array $config = []): bool { $flag = $preset->getFeatureFlag(); return !$flag || !isset($config[$flag]) || $config[$flag]; } private function loadPresetFromProviders(): void { $this->allPresets = $this->getPresetGroups(); $providersRegistry = TestCategoryPresetRegistry::getRegistry(); $allProviders = $providersRegistry->getMap(); if (!empty($allProviders)) { foreach ($allProviders as $providerClass) { if (class_exists($providerClass)) { $providerInstance = new $providerClass(); $providerInstance->registerPresets($this); } } } } private function filterInactivePresets(): void { $serviceLocator = $this->getServiceLocator(); $pluginService = $serviceLocator->get(TestPluginService::SERVICE_ID); $allEmptyGroups = []; if (!empty($this->allPresets)) { foreach ($this->allPresets as $groupId => &$presetGroup) { if (!empty($presetGroup['presets'])) { $presetGroup['presets'] = array_filter( $presetGroup['presets'], static function (TestCategoryPreset $preset) use ($pluginService): bool { $presetPluginId = $preset->getPluginId(); if (!empty($presetPluginId)) { $presetPlugin = $pluginService->getPlugin($presetPluginId); return ($presetPlugin !== null) ? $presetPlugin->isActive() : false; } return true; } ); } if (empty($presetGroup['presets'])) { $allEmptyGroups[] = $groupId; } } unset($presetGroup); } // finally, remove empty groups, if any if (!empty($allEmptyGroups)) { foreach ($allEmptyGroups as $emptyGroupId) { unset($this->allPresets[$emptyGroupId]); } } } private function sortPresets(): void { // sort presets groups uasort( $this->allPresets, static function (array $a, array $b): int { return $a['groupOrder'] <=> $b['groupOrder']; } ); // sort presets foreach ($this->allPresets as &$presetGroup) { if (!empty($presetGroup)) { usort( $presetGroup['presets'], static function (TestCategoryPreset $a, TestCategoryPreset $b): int { return $a->getOrder() <=> $b->getOrder(); } ); } } } private function groomPresets(): void { if ($this->isGroomed) { return; } $this->filterInactivePresets(); $this->sortPresets(); $this->isGroomed = true; } }