diff --git a/Services/MetaData/classes/Editor/Full/Services/Inputs/Conditions/BaseConditionFactory.php b/Services/MetaData/classes/Editor/Full/Services/Inputs/Conditions/BaseConditionFactory.php index fd5b25537554..f30c6f335cfe 100644 --- a/Services/MetaData/classes/Editor/Full/Services/Inputs/Conditions/BaseConditionFactory.php +++ b/Services/MetaData/classes/Editor/Full/Services/Inputs/Conditions/BaseConditionFactory.php @@ -26,9 +26,13 @@ use ILIAS\UI\Component\Input\Field\Factory as UIFactory; use ILIAS\MetaData\Repository\Validation\Dictionary\DictionaryInterface as ConstraintDictionary; use ILIAS\MetaData\Editor\Presenter\PresenterInterface; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; +use ILIAS\MetaData\Editor\Full\Services\Inputs\WithoutConditions\InputHelper; abstract class BaseConditionFactory { + use InputHelper; + protected UIFactory $ui_factory; protected PresenterInterface $presenter; protected ConstraintDictionary $constraint_dictionary; @@ -46,49 +50,22 @@ public function __construct( $this->types = $types; } - abstract protected function conditionInput( + abstract public function getConditionInput( ElementInterface $element, ElementInterface $context_element, - ElementInterface ...$conditional_elements + ElementInterface $conditional_element ): FormInput; - final public function getConditionInput( - ElementInterface $element, - ElementInterface $context_element, - ElementInterface ...$conditional_elements - ): FormInput { - $input = $this->conditionInput( - $element, - $context_element, - ...$conditional_elements - ); - - return $this->finishInput($element, $context_element, $input); - } - - final protected function getInputInCondition( + protected function getInputInCondition( ElementInterface $element, ElementInterface $context_element, - string $condition_value + SlotIdentifier $conditional_slot ): FormInput { $input_factory = $this->types->factory($element->getDefinition()->dataType()); return $input_factory->getInputInCondition( $element, $context_element, - $condition_value - ); - } - - final protected function finishInput( - ElementInterface $element, - ElementInterface $context_element, - FormInput $input - ): FormInput { - $input_factory = $this->types->factory($element->getDefinition()->dataType()); - return $input_factory->finishInput( - $element, - $context_element, - $input + $conditional_slot ); } } diff --git a/Services/MetaData/classes/Editor/Full/Services/Inputs/Conditions/FactoryWithConditionTypesService.php b/Services/MetaData/classes/Editor/Full/Services/Inputs/Conditions/FactoryWithConditionTypesService.php index bfd3ec5db45d..845eba857202 100644 --- a/Services/MetaData/classes/Editor/Full/Services/Inputs/Conditions/FactoryWithConditionTypesService.php +++ b/Services/MetaData/classes/Editor/Full/Services/Inputs/Conditions/FactoryWithConditionTypesService.php @@ -20,7 +20,6 @@ namespace ILIAS\MetaData\Editor\Full\Services\Inputs\Conditions; -use ILIAS\MetaData\Vocabularies\VocabulariesInterface; use ILIAS\UI\Component\Input\Field\Factory as UIFactory; use ILIAS\MetaData\Editor\Presenter\PresenterInterface; use ILIAS\MetaData\Repository\Validation\Dictionary\DictionaryInterface as ConstraintDictionary; @@ -30,6 +29,8 @@ use ILIAS\MetaData\Editor\Full\Services\Inputs\WithoutConditions\FactoryWithoutConditionTypesService; use ILIAS\MetaData\Editor\Full\Services\Inputs\WithoutConditions\BaseFactory; use ILIAS\MetaData\DataHelper\DataHelperInterface; +use ILIAS\MetaData\Vocabularies\ElementHelper\ElementHelperInterface; +use ILIAS\MetaData\Vocabularies\Slots\HandlerInterface as VocabSlotHandler; class FactoryWithConditionTypesService { @@ -40,26 +41,29 @@ public function __construct( UIFactory $ui_factory, PresenterInterface $presenter, ConstraintDictionary $constraint_dictionary, - VocabulariesInterface $vocabularies, Refinery $refinery, PathFactory $path_factory, - DataHelperInterface $data_helper + DataHelperInterface $data_helper, + ElementHelperInterface $element_vocab_helper, + VocabSlotHandler $vocab_slot_handler ) { $this->types_without_conditions = new FactoryWithoutConditionTypesService( $ui_factory, $presenter, $constraint_dictionary, - $vocabularies, $refinery, - $data_helper + $data_helper, + $element_vocab_helper, + $path_factory ); $this->vocab_value = new VocabValueConditionFactory( $ui_factory, $presenter, $constraint_dictionary, $this->types_without_conditions, - $vocabularies, - $path_factory + $path_factory, + $element_vocab_helper, + $vocab_slot_handler ); } diff --git a/Services/MetaData/classes/Editor/Full/Services/Inputs/Conditions/VocabValueConditionFactory.php b/Services/MetaData/classes/Editor/Full/Services/Inputs/Conditions/VocabValueConditionFactory.php index f72510e77c9f..4ac806963f8d 100644 --- a/Services/MetaData/classes/Editor/Full/Services/Inputs/Conditions/VocabValueConditionFactory.php +++ b/Services/MetaData/classes/Editor/Full/Services/Inputs/Conditions/VocabValueConditionFactory.php @@ -22,68 +22,110 @@ use ILIAS\UI\Component\Input\Container\Form\FormInput; use ILIAS\MetaData\Elements\ElementInterface; -use ILIAS\MetaData\Vocabularies\VocabulariesInterface; use ILIAS\UI\Component\Input\Field\Factory as UIFactory; use ILIAS\MetaData\Repository\Validation\Dictionary\DictionaryInterface as ConstraintDictionary; use ILIAS\MetaData\Editor\Presenter\PresenterInterface; use ILIAS\MetaData\Paths\FactoryInterface as PathFactory; use ILIAS\MetaData\Editor\Full\Services\Inputs\WithoutConditions\FactoryWithoutConditionTypesService; +use ILIAS\MetaData\Vocabularies\ElementHelper\ElementHelperInterface; +use ILIAS\MetaData\Vocabularies\Slots\HandlerInterface as VocabSlotHandler; +use ILIAS\MetaData\Elements\Data\Type; +use ILIAS\MetaData\Vocabularies\VocabularyInterface; class VocabValueConditionFactory extends BaseConditionFactory { - protected VocabulariesInterface $vocabularies; protected PathFactory $path_factory; + protected ElementHelperInterface $element_vocab_helper; + protected VocabSlotHandler $vocab_slot_handler; public function __construct( UIFactory $ui_factory, PresenterInterface $presenter, ConstraintDictionary $constraint_dictionary, FactoryWithoutConditionTypesService $types, - VocabulariesInterface $vocabularies, - PathFactory $path_factory + PathFactory $path_factory, + ElementHelperInterface $element_vocab_helper, + VocabSlotHandler $vocab_slot_handler ) { parent::__construct($ui_factory, $presenter, $constraint_dictionary, $types); - $this->vocabularies = $vocabularies; $this->path_factory = $path_factory; + $this->element_vocab_helper = $element_vocab_helper; + $this->vocab_slot_handler = $vocab_slot_handler; } - protected function conditionInput( + public function getConditionInput( ElementInterface $element, ElementInterface $context_element, - ElementInterface ...$conditional_elements + ElementInterface $conditional_element ): FormInput { + $slot = $this->element_vocab_helper->slotForElement($element); + $unique_path_to_conditional_element = $this->path_factory->toElement($conditional_element, true); + $path_for_conditional_slot = $this->path_factory->toElement($conditional_element); + $path_for_condition = $this->path_factory->betweenElements($conditional_element, $element); + + $data = $this->getDataFromElementOrConstraint($element); + $conditional_data = $this->getDataFromElementOrConstraint($conditional_element); + $groups = []; - foreach ($this->vocabularies->vocabulariesForElement($element) as $vocab) { + foreach ($this->element_vocab_helper->vocabulariesForSlot($slot) as $vocab) { + $labels_by_value = $this->getLabelsByValueForVocabulary($vocab); foreach ($vocab->values() as $value) { - $inputs = []; - foreach ($conditional_elements as $conditional_element) { - $input = $this->getInputInCondition( - $conditional_element, - $context_element, - $value - ); - $path_string = $this->path_factory->toElement($conditional_element, true) - ->toString(); - $inputs[$path_string] = $this->ui_factory->group( - [$input] - ); - } - if (!isset($default_value)) { - $default_value = $value; + $conditional_slot = $this->vocab_slot_handler->identiferFromPathAndCondition( + $path_for_conditional_slot, + $path_for_condition, + $value, + ); + + $input = $this->getInputInCondition( + $conditional_element, + $context_element, + $conditional_slot + ); + + if ($data === $value && isset($conditional_data)) { + $input = $input->withValue($conditional_data); } + $groups[$value] = $this->ui_factory->group( - $inputs, - $this->presenter->data()->vocabularyValue($value) + [$unique_path_to_conditional_element->toString() => $input], + $labels_by_value[$value] ?? '' ); } } - $input = $this->ui_factory->switchableGroup( + + $radios = $this->ui_factory->switchableGroup( $groups, - 'placeholder' + $this->getInputLabelFromElement($this->presenter, $element, $context_element) ); - if (isset($default_value)) { - return $input->withValue($default_value); + if (isset($data)) { + $radios = $radios->withValue($data); + } + return $this->addConstraintsFromElement( + $this->constraint_dictionary, + $element, + $radios + ); + } + + protected function getLabelsByValueForVocabulary(VocabularyInterface $vocabulary): array + { + $presentable_labels = $this->presenter->data()->vocabularyValues( + $vocabulary->slot(), + ...$vocabulary->values() + ); + $labels_by_value = []; + foreach ($presentable_labels as $labelled_value) { + $labels_by_value[$labelled_value->value()] = $labelled_value->label(); + } + return $labels_by_value; + } + + protected function getDataFromElementOrConstraint(ElementInterface $element): ?string + { + $data = null; + if ($element->getData()->type() !== Type::NULL) { + $data = $element->getData()->value(); } - return $input; + return $this->getPresetValueFromConstraints($this->constraint_dictionary, $element) ?? $data; } } diff --git a/Services/MetaData/classes/Editor/Full/Services/Inputs/InputFactory.php b/Services/MetaData/classes/Editor/Full/Services/Inputs/InputFactory.php index c5f48ee39c6c..8ff91e79f1c9 100644 --- a/Services/MetaData/classes/Editor/Full/Services/Inputs/InputFactory.php +++ b/Services/MetaData/classes/Editor/Full/Services/Inputs/InputFactory.php @@ -29,10 +29,10 @@ use ILIAS\MetaData\Elements\ElementInterface; use ILIAS\MetaData\Editor\Full\Services\DataFinder; use ILIAS\MetaData\Paths\FactoryInterface as PathFactory; -use ILIAS\MetaData\Vocabularies\VocabulariesInterface; use ILIAS\MetaData\Paths\Navigator\NavigatorFactoryInterface; use ILIAS\MetaData\Editor\Full\Services\Inputs\Conditions\FactoryWithConditionTypesService; use ILIAS\MetaData\Elements\Data\Type; +use ILIAS\MetaData\Vocabularies\ElementHelper\ElementHelperInterface; class InputFactory { @@ -41,8 +41,8 @@ class InputFactory protected PresenterInterface $presenter; protected PathFactory $path_factory; protected NavigatorFactoryInterface $navigator_factory; + protected ElementHelperInterface $element_vocab_helper; protected DataFinder $data_finder; - protected VocabulariesInterface $vocabularies; protected FactoryWithConditionTypesService $types; /** @@ -59,21 +59,19 @@ public function __construct( Refinery $refinery, PresenterInterface $presenter, PathFactory $path_factory, - NavigatorFactoryInterface $navigator_factory, DataFinder $data_finder, - VocabulariesInterface $vocabularies, DatabaseDictionary $db_dictionary, - FactoryWithConditionTypesService $types + FactoryWithConditionTypesService $types, + ElementHelperInterface $element_vocab_helper ) { $this->ui_factory = $ui_factory; $this->refinery = $refinery; $this->presenter = $presenter; $this->path_factory = $path_factory; - $this->navigator_factory = $navigator_factory; $this->data_finder = $data_finder; - $this->vocabularies = $vocabularies; $this->db_dictionary = $db_dictionary; $this->types = $types; + $this->element_vocab_helper = $element_vocab_helper; } public function getInputFields( @@ -81,26 +79,36 @@ public function getInputFields( ElementInterface $context_element, bool $with_title ): Section|Group { + $data_carriers = iterator_to_array($this->data_finder->getDataCarryingElements($element)); $conditional_elements = []; $input_elements = []; - foreach ($this->data_finder->getDataCarryingElements($element) as $data_carrier) { + $vocab_sources = []; + foreach ($data_carriers as $data_carrier) { $conditional_element = null; /** - * Currently, hidden inputs don't play nice with switchable group inputs, - * so for the time being they get pulled out here. + * Vocab Sources don't have their own inputs, but are set implicitely with + * the corresponding Vocab Values (see VocabValueFactory). */ - if ( - $data_carrier->getDefinition()->dataType() !== Type::VOCAB_SOURCE && - $el = $this->getConditionElement($data_carrier) - ) { - $conditional_element = $data_carrier; - $data_carrier = $el; + if ($data_carrier->getDefinition()->dataType() === Type::VOCAB_SOURCE) { + $vocab_sources[] = $data_carrier; + continue; + } + foreach ($this->element_vocab_helper->slotsForElementWithoutCondition($data_carrier) as $slot) { + /** + * The conditions of multiple slots for the same element should point at the + * same element (or at least not more than one per context element). + */ + if ($el = $this->element_vocab_helper->findElementOfCondition($slot, $data_carrier, ...$data_carriers)) { + $conditional_element = $data_carrier; + $data_carrier = $el; + break; + } } $path_string = $this->path_factory->toElement($data_carrier, true) ->toString(); $input_elements[$path_string] = $data_carrier; if (isset($conditional_element)) { - $conditional_elements[$path_string][] = $conditional_element; + $conditional_elements[$path_string] = $conditional_element; } } @@ -112,7 +120,7 @@ public function getInputFields( $input = $this->types->conditionFactory($data_type)->getConditionInput( $input_element, $context_element, - ...$conditional_elements[$path_string] + $conditional_elements[$path_string] ); } else { $input = $this->types->factory($data_type)->getInput( @@ -140,36 +148,26 @@ public function getInputFields( $fields = $this->ui_factory->group($inputs); } + // needs flattening twice because of vocab sources in conditional inputs return $this->addNotEmptyConstraintIfNeeded( $context_element, - $this->flattenOutput($fields), + $this->flattenOutput($this->flattenOutput($fields)), ...$exclude_required ); } - protected function getConditionElement( - ElementInterface $element - ): ?ElementInterface { - foreach ($this->vocabularies->vocabulariesForElement($element) as $vocab) { - if ($path = $vocab->condition()?->path()) { - return $this->navigator_factory->navigator($path, $element) - ->lastElementAtFinalStep(); - } - } - return null; - } - protected function flattenOutput( Section|Group $fields ): Section|Group { return $fields->withAdditionalTransformation( $this->refinery->custom()->transformation(function ($vs) { foreach ($vs as $key => $value) { - if (is_array($value)) { - $vs[$key] = $value[0]; - foreach ($value[1] as $k => $v) { - $vs[$k] = $v[0]; - } + if (!is_array($value)) { + continue; + } + $vs[$key] = $value[0]; + foreach ($value[1] as $k => $v) { + $vs[$k] = $v; } } return $vs; diff --git a/Services/MetaData/classes/Editor/Full/Services/Inputs/InputHelper.php b/Services/MetaData/classes/Editor/Full/Services/Inputs/InputHelper.php new file mode 100644 index 000000000000..73e835d191c7 --- /dev/null +++ b/Services/MetaData/classes/Editor/Full/Services/Inputs/InputHelper.php @@ -0,0 +1,101 @@ +elements()->nameWithParents( + $element, + $context_element, + false + ); + } + + protected function addValueFromElement( + ElementInterface $element, + FormInput $input + ): FormInput { + if ($element->getData()->type() === Type::NULL) { + return $input; + } + return $input->withValue($element->getData()->value()); + } + + protected function addConstraintsFromElement( + ConstraintDictionary $constraint_dictionary, + ElementInterface $element, + FormInput $input, + bool $skip_value = false + ): FormInput { + foreach ($constraint_dictionary->tagsForElement($element) as $tag) { + $input = $this->addConstraintFromTag($input, $tag, $skip_value); + } + return $input; + } + + private function addConstraintFromTag( + FormInput $input, + ConstraintTag $tag, + bool $skip_value + ): FormInput { + switch ($tag->restriction()) { + case Restriction::PRESET_VALUE: + if ($skip_value) { + return $input; + } + return $input->withValue($tag->value()); + + case Restriction::NOT_DELETABLE: + return $input->withRequired(true); + + case Restriction::NOT_EDITABLE: + return $input->withDisabled(true); + } + return $input; + } + + protected function getPresetValueFromConstraints( + ConstraintDictionary $constraint_dictionary, + ElementInterface $element + ): ?string { + $preset_value = null; + foreach ($constraint_dictionary->tagsForElement($element) as $tag) { + if ($tag->restriction() !== Restriction::PRESET_VALUE) { + continue; + } + $preset_value = $tag->value(); + } + return $preset_value; + } +} diff --git a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/BaseFactory.php b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/BaseFactory.php index b1fa2c318907..4d5b13bbc2f7 100644 --- a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/BaseFactory.php +++ b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/BaseFactory.php @@ -25,13 +25,12 @@ use ILIAS\MetaData\Editor\Presenter\PresenterInterface; use ILIAS\MetaData\Repository\Validation\Dictionary\DictionaryInterface as ConstraintDictionary; use ILIAS\MetaData\Elements\ElementInterface; -use ILIAS\MetaData\Elements\Data\DataInterface; -use ILIAS\MetaData\Elements\Data\Type; -use ILIAS\MetaData\Repository\Validation\Dictionary\Restriction; -use ILIAS\MetaData\Repository\Validation\Dictionary\TagInterface as ConstraintTag; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; abstract class BaseFactory { + use InputHelper; + protected UIFactory $ui_factory; protected PresenterInterface $presenter; protected ConstraintDictionary $constraint_dictionary; @@ -46,94 +45,14 @@ public function __construct( $this->constraint_dictionary = $constraint_dictionary; } - abstract protected function rawInput( + abstract public function getInput( ElementInterface $element, - ElementInterface $context_element, - string $condition_value = '' + ElementInterface $context_element ): FormInput; - /** - * @return string|string[] - */ - protected function dataValueForInput( - DataInterface $data - ): string|array { - return $data->value(); - } - - final public function getInput( - ElementInterface $element, - ElementInterface $context_element, - ): FormInput { - $input = $this->rawInput( - $element, - $context_element - ); - - return $this->finishInput($element, $context_element, $input); - } - - final public function getInputInCondition( - ElementInterface $element, - ElementInterface $context_element, - string $condition_value - ): FormInput { - $input = $this->rawInput( - $element, - $context_element, - $condition_value - ); - - return $this->finishInputIgnoreValue($element, $context_element, $input); - } - - public function finishInput( + abstract public function getInputInCondition( ElementInterface $element, ElementInterface $context_element, - FormInput $input - ): FormInput { - if (($data = $element->getData())->type() !== Type::NULL) { - $input = $input->withValue( - $this->dataValueForInput($data) - ); - } - - return $this->finishInputIgnoreValue($element, $context_element, $input); - } - - protected function finishInputIgnoreValue( - ElementInterface $element, - ElementInterface $context_element, - FormInput $input - ): FormInput { - $label = $this->presenter->elements()->nameWithParents( - $element, - $context_element, - false - ); - $input = $input->withLabel($label); - - foreach ($this->constraint_dictionary->tagsForElement($element) as $tag) { - $input = $this->addConstraintFromTag($input, $tag); - } - - return $input; - } - - protected function addConstraintFromTag( - FormInput $input, - ConstraintTag $tag - ): FormInput { - switch ($tag->restriction()) { - case Restriction::PRESET_VALUE: - return $input->withValue($tag->value()); - - case Restriction::NOT_DELETABLE: - return $input->withRequired(true); - - case Restriction::NOT_EDITABLE: - return $input->withDisabled(true); - } - return $input; - } + SlotIdentifier $conditional_slot + ): FormInput; } diff --git a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/DatetimeFactory.php b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/DatetimeFactory.php index ebf373cc6e56..ade79db5f9a8 100644 --- a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/DatetimeFactory.php +++ b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/DatetimeFactory.php @@ -28,6 +28,8 @@ use ILIAS\MetaData\Editor\Presenter\PresenterInterface; use ILIAS\MetaData\Elements\Data\DataInterface; use ILIAS\MetaData\DataHelper\DataHelperInterface; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; +use ILIAS\MetaData\Elements\Data\Type; class DatetimeFactory extends BaseFactory { @@ -46,14 +48,29 @@ public function __construct( $this->data_helper = $data_helper; } + public function getInput( + ElementInterface $element, + ElementInterface $context_element + ): FormInput { + return $this->rawInput($element, $context_element); + } + + public function getInputInCondition( + ElementInterface $element, + ElementInterface $context_element, + SlotIdentifier $conditional_slot + ): FormInput { + return $this->rawInput($element, $context_element); + } + protected function rawInput( ElementInterface $element, ElementInterface $context_element, - string $condition_value = '' + SlotIdentifier $conditional_slot = SlotIdentifier::NULL ): FormInput { $dh = $this->data_helper; - return $this->ui_factory - ->dateTime('placeholder') + $input = $this->ui_factory + ->dateTime($this->getInputLabelFromElement($this->presenter, $element, $context_element)) ->withFormat($this->presenter->utilities()->getUserDateFormat()) ->withAdditionalTransformation( $this->refinery->custom()->transformation( @@ -62,6 +79,11 @@ function ($v) use ($dh) { } ) ); + + if ($element->getData()->type() !== Type::NULL) { + $input = $input->withValue($this->dataValueForInput($element->getData())); + } + return $this->addConstraintsFromElement($this->constraint_dictionary, $element, $input); } protected function dataValueForInput(DataInterface $data): string diff --git a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/DurationFactory.php b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/DurationFactory.php index c005a4cddfbb..9a7644dc8e4e 100644 --- a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/DurationFactory.php +++ b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/DurationFactory.php @@ -28,6 +28,8 @@ use ILIAS\MetaData\Editor\Presenter\PresenterInterface; use ILIAS\MetaData\Elements\Data\DataInterface; use ILIAS\MetaData\DataHelper\DataHelperInterface; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; +use ILIAS\MetaData\Elements\Data\Type; class DurationFactory extends BaseFactory { @@ -49,7 +51,7 @@ public function __construct( protected function rawInput( ElementInterface $element, ElementInterface $context_element, - string $condition_value = '' + SlotIdentifier $conditional_slot = SlotIdentifier::NULL ): FormInput { $num = $this->ui_factory ->numeric('placeholder') @@ -61,12 +63,20 @@ protected function rawInput( $nums[] = (clone $num)->withLabel($label); } $dh = $this->data_helper; - return $this->ui_factory->group($nums)->withAdditionalTransformation( + $input = $this->ui_factory->group( + $nums, + $this->getInputLabelFromElement($this->presenter, $element, $context_element) + )->withAdditionalTransformation( $this->refinery->custom()->transformation(function ($vs) use ($dh) { - $vs = array_map(fn ($v) => is_null($v) ? $v : (int) $v, $vs); + $vs = array_map(fn($v) => is_null($v) ? $v : (int) $v, $vs); return $dh->durationFromIntegers(...$vs); }) ); + + if ($element->getData()->type() !== Type::NULL) { + $input = $input->withValue($this->dataValueForInput($element->getData())); + } + return $this->addConstraintsFromElement($this->constraint_dictionary, $element, $input); } /** @@ -76,4 +86,19 @@ protected function dataValueForInput(DataInterface $data): array { return iterator_to_array($this->data_helper->durationToIterator($data->value())); } + + public function getInput( + ElementInterface $element, + ElementInterface $context_element + ): FormInput { + return $this->rawInput($element, $context_element); + } + + public function getInputInCondition( + ElementInterface $element, + ElementInterface $context_element, + SlotIdentifier $conditional_slot + ): FormInput { + return $this->rawInput($element, $context_element); + } } diff --git a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/FactoryWithoutConditionTypesService.php b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/FactoryWithoutConditionTypesService.php index 96bd258aa9d8..76714942171f 100644 --- a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/FactoryWithoutConditionTypesService.php +++ b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/FactoryWithoutConditionTypesService.php @@ -20,7 +20,6 @@ namespace ILIAS\MetaData\Editor\Full\Services\Inputs\WithoutConditions; -use ILIAS\MetaData\Vocabularies\VocabulariesInterface; use ILIAS\UI\Component\Input\Field\Factory as UIFactory; use ILIAS\MetaData\Editor\Presenter\PresenterInterface; use ILIAS\MetaData\Repository\Validation\Dictionary\DictionaryInterface as ConstraintDictionary; @@ -28,6 +27,7 @@ use ILIAS\MetaData\Elements\Data\Type; use ILIAS\MetaData\Paths\FactoryInterface as PathFactory; use ILIAS\MetaData\DataHelper\DataHelperInterface; +use ILIAS\MetaData\Vocabularies\ElementHelper\ElementHelperInterface; class FactoryWithoutConditionTypesService { @@ -36,16 +36,16 @@ class FactoryWithoutConditionTypesService protected LangFactory $lang; protected NonNegIntFactory $non_neg_int; protected StringFactory $string; - protected VocabSourceFactory $vocab_source; protected VocabValueFactory $vocab_value; public function __construct( UIFactory $ui_factory, PresenterInterface $presenter, ConstraintDictionary $constraint_dictionary, - VocabulariesInterface $vocabularies, Refinery $refinery, - DataHelperInterface $data_helper + DataHelperInterface $data_helper, + ElementHelperInterface $element_vocab_helper, + PathFactory $path_factory ) { $this->datetime = new DatetimeFactory( $ui_factory, @@ -76,18 +76,17 @@ public function __construct( $this->string = new StringFactory( $ui_factory, $presenter, - $constraint_dictionary - ); - $this->vocab_source = new VocabSourceFactory( - $ui_factory, - $presenter, - $constraint_dictionary + $constraint_dictionary, + $element_vocab_helper, + $refinery ); $this->vocab_value = new VocabValueFactory( $ui_factory, $presenter, $constraint_dictionary, - $vocabularies + $element_vocab_helper, + $refinery, + $path_factory ); } @@ -100,9 +99,6 @@ public function factory(Type $type): BaseFactory case Type::LANG: return $this->lang; - case Type::VOCAB_SOURCE: - return $this->vocab_source; - case Type::VOCAB_VALUE: return $this->vocab_value; diff --git a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/LangFactory.php b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/LangFactory.php index a5f617d2125f..6bf810443c3e 100644 --- a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/LangFactory.php +++ b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/LangFactory.php @@ -26,6 +26,8 @@ use ILIAS\UI\Component\Input\Field\Factory as UIFactory; use ILIAS\MetaData\Editor\Presenter\PresenterInterface; use ILIAS\MetaData\Repository\Validation\Dictionary\DictionaryInterface as ConstraintDictionary; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; +use ILIAS\MetaData\Elements\Data\Type; class LangFactory extends BaseFactory { @@ -44,12 +46,36 @@ public function __construct( protected function rawInput( ElementInterface $element, ElementInterface $context_element, - string $condition_value = '' + SlotIdentifier $conditional_slot = SlotIdentifier::NULL ): FormInput { $langs = []; foreach ($this->data_helper->getAllLanguages() as $key) { $langs[$key] = $this->presenter->data()->language($key); } - return $this->ui_factory->select('placeholder', $langs); + $input = $this->ui_factory->select( + $this->getInputLabelFromElement($this->presenter, $element, $context_element), + $langs + ); + + return $this->addConstraintsFromElement( + $this->constraint_dictionary, + $element, + $this->addValueFromElement($element, $input) + ); + } + + public function getInput( + ElementInterface $element, + ElementInterface $context_element + ): FormInput { + return $this->rawInput($element, $context_element); + } + + public function getInputInCondition( + ElementInterface $element, + ElementInterface $context_element, + SlotIdentifier $conditional_slot + ): FormInput { + return $this->rawInput($element, $context_element); } } diff --git a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/NonNegIntFactory.php b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/NonNegIntFactory.php index 8f57806dc01e..2c64f0f46cfe 100644 --- a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/NonNegIntFactory.php +++ b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/NonNegIntFactory.php @@ -26,6 +26,8 @@ use ILIAS\UI\Component\Input\Field\Factory as UIFactory; use ILIAS\MetaData\Repository\Validation\Dictionary\DictionaryInterface as ConstraintDictionary; use ILIAS\MetaData\Editor\Presenter\PresenterInterface; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; +use ILIAS\MetaData\Elements\Data\Type; class NonNegIntFactory extends BaseFactory { @@ -44,10 +46,10 @@ public function __construct( protected function rawInput( ElementInterface $element, ElementInterface $context_element, - string $condition_value = '' + SlotIdentifier $conditional_slot = SlotIdentifier::NULL ): FormInput { - return $this->ui_factory - ->numeric('placeholder') + $input = $this->ui_factory + ->numeric($this->getInputLabelFromElement($this->presenter, $element, $context_element)) ->withAdditionalTransformation( $this->refinery->int()->isGreaterThanOrEqual(0) ) @@ -57,5 +59,25 @@ protected function rawInput( $this->refinery->kindlyTo()->null() ]) ); + return $this->addConstraintsFromElement( + $this->constraint_dictionary, + $element, + $this->addValueFromElement($element, $input) + ); + } + + public function getInput( + ElementInterface $element, + ElementInterface $context_element + ): FormInput { + return $this->rawInput($element, $context_element); + } + + public function getInputInCondition( + ElementInterface $element, + ElementInterface $context_element, + SlotIdentifier $conditional_slot + ): FormInput { + return $this->rawInput($element, $context_element); } } diff --git a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/StringFactory.php b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/StringFactory.php index ecf48d00ef64..49ffc1a14707 100644 --- a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/StringFactory.php +++ b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/StringFactory.php @@ -22,20 +22,172 @@ use ILIAS\UI\Component\Input\Container\Form\FormInput; use ILIAS\MetaData\Elements\ElementInterface; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; +use ILIAS\MetaData\Vocabularies\ElementHelper\ElementHelperInterface; +use ILIAS\UI\Component\Input\Field\Factory as UIFactory; +use ILIAS\MetaData\Editor\Presenter\PresenterInterface; +use ILIAS\MetaData\Repository\Validation\Dictionary\DictionaryInterface as ConstraintDictionary; +use ILIAS\MetaData\Vocabularies\Slots\Identifier; +use ILIAS\MetaData\Elements\Data\Type; +use ILIAS\Refinery\Factory as Refinery; class StringFactory extends BaseFactory { + protected ElementHelperInterface $element_vocab_helper; + protected Refinery $refinery; + + public function __construct( + UIFactory $ui_factory, + PresenterInterface $presenter, + ConstraintDictionary $constraint_dictionary, + ElementHelperInterface $element_vocab_helper, + Refinery $refinery, + ) { + parent::__construct($ui_factory, $presenter, $constraint_dictionary); + $this->element_vocab_helper = $element_vocab_helper; + $this->refinery = $refinery; + } + protected function rawInput( ElementInterface $element, ElementInterface $context_element, - string $condition_value = '' + SlotIdentifier $conditional_slot = SlotIdentifier::NULL + ): FormInput { + $slot = $this->element_vocab_helper->slotForElement($element); + + $data = null; + if ($element->getData()->type() !== Type::NULL) { + $data = $element->getData()->value(); + } + $data = $this->getPresetValueFromConstraints($this->constraint_dictionary, $element) ?? $data; + + $raw_values = []; + $allows_custom_input = true; + foreach ($this->element_vocab_helper->vocabulariesForSlot($slot) as $vocab) { + $values_from_vocab = iterator_to_array($vocab->values()); + $raw_values = array_merge($raw_values, $values_from_vocab); + + if (!$vocab->allowsCustomInputs()) { + $allows_custom_input = false; + } + } + + // return finished text input if there are no vocabs + if (empty($raw_values)) { + return $this->buildTextInput( + $this->getInputLabelFromElement($this->presenter, $element, $context_element), + $element, + true + ); + } + + // return finished select input if no custom input is allowed + if (!$allows_custom_input) { + if (isset($data) && !in_array($data, $raw_values)) { + array_unshift($raw_values, $data); + } + return $this->buildSelectInput( + $this->getInputLabelFromElement($this->presenter, $element, $context_element), + $slot, + $element, + true, + ...$raw_values + ); + } + + // else, switchable group to choose between the two + $value_label = $this->presenter->utilities()->txt('md_editor_value'); + $select_input = $this->buildSelectInput($value_label, $slot, $element, false, ...$raw_values); + $text_input = $this->buildTextInput($value_label, $element, false); + + if (isset($data)) { + if (!in_array($data, $raw_values)) { + $text_input = $text_input->withValue($data); + $radio_value = 'custom'; + } else { + $select_input = $select_input->withValue($data); + $radio_value = 'from_vocab'; + } + } + + $input = $this->ui_factory->switchableGroup( + [ + 'from_vocab' => $this->ui_factory->group( + ['value' => $select_input], + $this->presenter->utilities()->txt('md_editor_from_vocab_input') + ), + 'custom' => $this->ui_factory->group( + ['value' => $text_input], + $this->presenter->utilities()->txt('md_editor_custom_input') + ) + ], + $this->getInputLabelFromElement($this->presenter, $element, $context_element) + ); + if (isset($radio_value)) { + $input = $input->withValue($radio_value); + } + return $this->addConstraintsFromElement($this->constraint_dictionary, $element, $input, true) + ->withAdditionalTransformation( + $this->refinery->custom()->transformation(function ($vs) { + return $vs[1]['value'] ?? null; + }) + ); + } + + protected function buildTextInput( + string $label, + ElementInterface $element, + bool $with_value ): FormInput { $super_name = $element->getSuperElement() ->getDefinition() ->name(); if ($super_name === 'description') { - return $this->ui_factory->textarea('placeholder'); + $input = $this->ui_factory->textarea($label); + } else { + $input = $this->ui_factory->text($label); + } + + if ($with_value) { + $input = $this->addValueFromElement($element, $input); + } + return $this->addConstraintsFromElement($this->constraint_dictionary, $element, $input, !$with_value); + } + + /** + * @return string[] + */ + protected function buildSelectInput( + string $label, + SlotIdentifier $slot, + ElementInterface $element, + bool $with_value, + string ...$raw_values + ): FormInput { + $values = []; + foreach ($this->presenter->data()->vocabularyValues($slot, ...$raw_values) as $labelled_value) { + $values[$labelled_value->value()] = $labelled_value->label(); + } + $input = $this->ui_factory->select($label, $values); + + if ($with_value) { + $input = $this->addValueFromElement($element, $input); } - return $this->ui_factory->text('placeholder'); + return $this->addConstraintsFromElement($this->constraint_dictionary, $element, $input, !$with_value); + } + + public function getInput( + ElementInterface $element, + ElementInterface $context_element + ): FormInput { + return $this->rawInput($element, $context_element); + } + + public function getInputInCondition( + ElementInterface $element, + ElementInterface $context_element, + SlotIdentifier $conditional_slot + ): FormInput { + return $this->rawInput($element, $context_element); } } diff --git a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/VocabSourceFactory.php b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/VocabSourceFactory.php deleted file mode 100644 index 7aa23a40f32d..000000000000 --- a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/VocabSourceFactory.php +++ /dev/null @@ -1,38 +0,0 @@ -ui_factory->hidden()->withValue(FactoryInterface::STANDARD_SOURCE); - } -} diff --git a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/VocabValueFactory.php b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/VocabValueFactory.php index 73cce081fdf3..1ec724de542b 100644 --- a/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/VocabValueFactory.php +++ b/Services/MetaData/classes/Editor/Full/Services/Inputs/WithoutConditions/VocabValueFactory.php @@ -22,74 +22,122 @@ use ILIAS\UI\Component\Input\Container\Form\FormInput; use ILIAS\MetaData\Elements\ElementInterface; -use ILIAS\MetaData\Elements\Data\DataInterface; -use ILIAS\MetaData\Vocabularies\VocabulariesInterface; use ILIAS\UI\Component\Input\Field\Factory as UIFactory; use ILIAS\MetaData\Repository\Validation\Dictionary\DictionaryInterface as ConstraintDictionary; use ILIAS\MetaData\Editor\Presenter\PresenterInterface; use ILIAS\MetaData\Elements\Data\Type; use ILIAS\MetaData\Paths\FactoryInterface as PathFactory; +use ILIAS\MetaData\Vocabularies\ElementHelper\ElementHelperInterface; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; +use ILIAS\MetaData\Vocabularies\Slots\Identifier; +use ILIAS\Refinery\Factory as Refinery; +use ILIAS\MetaData\Paths\PathInterface; class VocabValueFactory extends BaseFactory { - protected VocabulariesInterface $vocabularies; + protected ElementHelperInterface $element_vocab_helper; + protected Refinery $refinery; + protected PathFactory $path_factory; public function __construct( UIFactory $ui_factory, PresenterInterface $presenter, ConstraintDictionary $constraint_dictionary, - VocabulariesInterface $vocabularies + ElementHelperInterface $element_vocab_helper, + Refinery $refinery, + PathFactory $path_factory ) { parent::__construct($ui_factory, $presenter, $constraint_dictionary); - $this->vocabularies = $vocabularies; + $this->element_vocab_helper = $element_vocab_helper; + $this->refinery = $refinery; + $this->path_factory = $path_factory; } protected function rawInput( ElementInterface $element, ElementInterface $context_element, - string $condition_value = '' + SlotIdentifier $slot, + bool $add_value_from_data ): FormInput { $data = null; - if ($element->getDefinition()->dataType() !== Type::NULL) { - $data = $this->dataValueForInput($element->getData()); + if ($element->getData()->type() !== Type::NULL) { + $data = $element->getData()->value(); } + + $raw_values = []; + $sources_by_value = []; + foreach ($this->element_vocab_helper->vocabulariesForSlot($slot) as $vocab) { + $values_from_vocab = iterator_to_array($vocab->values()); + + $raw_values = array_merge($raw_values, $values_from_vocab); + $sources_by_value = array_merge( + $sources_by_value, + array_fill_keys($values_from_vocab, $vocab->source()) + ); + } + if ($add_value_from_data && isset($data) && !in_array($data, $raw_values)) { + array_unshift($raw_values, $data); + } + $values = []; - $use_data_as_value = false; - foreach ($this->vocabularies->vocabulariesForElement($element) as $vocab) { - if ($condition_value !== '' && $vocab->condition()?->value() !== $condition_value) { - continue; - } - foreach ($vocab->values() as $value) { - if ($data === $value) { - $use_data_as_value = true; - } - $values[$value] = $this->presenter->data()->vocabularyValue($value); - } + foreach ($this->presenter->data()->vocabularyValues($slot, ...$raw_values) as $labelled_value) { + $values[$labelled_value->value()] = $labelled_value->label(); } - $input = $this->ui_factory->select('placeholder', $values); - if ($use_data_as_value && isset($data)) { + + $input = $this->ui_factory->select( + $this->getInputLabelFromElement($this->presenter, $element, $context_element), + $values + ); + if ($add_value_from_data && isset($data)) { $input = $input->withValue($data); } - return $input; + + $source_path = $this->getPathToSourceElement($element); + return $this->addConstraintsFromElement($this->constraint_dictionary, $element, $input) + ->withAdditionalTransformation( + $this->refinery->custom()->transformation(function ($vs) use ($sources_by_value, $source_path) { + $source = $sources_by_value[$vs] ?? null; + return [ + $vs, + [$source_path->toString() => $source] + ]; + }) + ); } - protected function dataValueForInput(DataInterface $data): string - { - $value = strtolower( - preg_replace('/(?<=[a-z])(?=[A-Z])/', ' ', $data->value()) + public function getInput( + ElementInterface $element, + ElementInterface $context_element + ): FormInput { + return $this->rawInput( + $element, + $context_element, + $slot = $this->element_vocab_helper->slotForElement($element), + true ); - $exceptions = [ - 'is part of' => 'ispartof', 'has part' => 'haspart', - 'is version of' => 'isversionof', 'has version' => 'hasversion', - 'is format of' => 'isformatof', 'has format' => 'hasformat', - 'references' => 'references', - 'is referenced by' => 'isreferencedby', - 'is based on' => 'isbasedon', 'is basis for' => 'isbasisfor', - 'requires' => 'requires', 'is required by' => 'isrequiredby', - ]; - if (array_key_exists($value, $exceptions)) { - $value = $exceptions[$value]; + } + + public function getInputInCondition( + ElementInterface $element, + ElementInterface $context_element, + SlotIdentifier $conditional_slot + ): FormInput { + $slot = $this->element_vocab_helper->slotForElement($element); + return $this->rawInput( + $element, + $context_element, + $conditional_slot, + $slot === $conditional_slot + ); + } + + public function getPathToSourceElement(ElementInterface $element): PathInterface + { + foreach ($element->getSuperElement()->getSubElements() as $el) { + if ($el->getDefinition()->dataType() === Type::VOCAB_SOURCE) { + return $this->path_factory->toElement($el, true); + } } - return $value; + throw new \ilMDEditorException('Vocab values must not be separated from their source.'); } } diff --git a/Services/MetaData/classes/Editor/Full/Services/Services.php b/Services/MetaData/classes/Editor/Full/Services/Services.php index 20bce4b445f7..d2b99318e5df 100644 --- a/Services/MetaData/classes/Editor/Full/Services/Services.php +++ b/Services/MetaData/classes/Editor/Full/Services/Services.php @@ -89,25 +89,25 @@ public function inputFactory(): InputFactory $refinery = $this->dic->refinery(); $presenter = $this->editor_services->presenter(); $path_factory = $this->path_services->pathFactory(); - $vocabularies = $this->vocabularies_services->vocabularies(); + $element_vocab_helper = $this->vocabularies_services->elementHelper(); return $this->input_factory = new InputFactory( $field_factory, $refinery, $presenter, $path_factory, - $this->path_services->navigatorFactory(), $this->dataFinder(), - $vocabularies, $this->repository_services->databaseDictionary(), new FactoryWithConditionTypesService( $field_factory, $presenter, $this->repository_services->constraintDictionary(), - $vocabularies, $refinery, $path_factory, - $this->data_helper_services->dataHelper() - ) + $this->data_helper_services->dataHelper(), + $element_vocab_helper, + $this->vocabularies_services->slotHandler() + ), + $element_vocab_helper ); } diff --git a/Services/MetaData/classes/Editor/Presenter/Data.php b/Services/MetaData/classes/Editor/Presenter/Data.php index afc621084253..88b42d06d9eb 100644 --- a/Services/MetaData/classes/Editor/Presenter/Data.php +++ b/Services/MetaData/classes/Editor/Presenter/Data.php @@ -22,19 +22,26 @@ use ILIAS\MetaData\Elements\Data\DataInterface as ElementsDataInterface; use ILIAS\MetaData\Elements\Data\Type; +use ILIAS\MetaData\Presentation\UtilitiesInterface as BaseUtilities; use ILIAS\MetaData\Presentation\DataInterface as DataPresentation; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; +use ILIAS\MetaData\Vocabularies\Dispatch\Presentation\PresentationInterface as VocabulariesPresentation; +use ILIAS\MetaData\Vocabularies\Dispatch\Presentation\LabelledValueInterface; class Data implements DataInterface { - protected UtilitiesInterface $utilities; + protected BaseUtilities $utilities; protected DataPresentation $data_presentation; + protected VocabulariesPresentation $vocabularies_presentation; public function __construct( - UtilitiesInterface $utilities, - DataPresentation $data_presentation + BaseUtilities $utilities, + DataPresentation $data_presentation, + VocabulariesPresentation $vocabularies_presentation ) { $this->utilities = $utilities; $this->data_presentation = $data_presentation; + $this->vocabularies_presentation = $vocabularies_presentation; } public function dataValue(ElementsDataInterface $data): string @@ -42,9 +49,17 @@ public function dataValue(ElementsDataInterface $data): string return $this->data_presentation->dataValue($data); } - public function vocabularyValue(string $value): string + /** + * @return string[] with values as keys + */ + public function vocabularyValues(SlotIdentifier $slot, string ...$values): \Generator { - return $this->data_presentation->vocabularyValue($value); + yield from $this->vocabularies_presentation->presentableLabels( + $this->utilities, + $slot, + true, + ...$values + ); } public function language(string $language): string diff --git a/Services/MetaData/classes/Editor/Presenter/DataInterface.php b/Services/MetaData/classes/Editor/Presenter/DataInterface.php index 27a5339258eb..9499964d163a 100644 --- a/Services/MetaData/classes/Editor/Presenter/DataInterface.php +++ b/Services/MetaData/classes/Editor/Presenter/DataInterface.php @@ -21,12 +21,17 @@ namespace ILIAS\MetaData\Editor\Presenter; use ILIAS\MetaData\Elements\Data\DataInterface as ElementsDataInterface; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; +use ILIAS\MetaData\Vocabularies\Dispatch\Presentation\LabelledValueInterface; interface DataInterface { public function dataValue(ElementsDataInterface $data): string; - public function vocabularyValue(string $value): string; + /** + * @return LabelledValueInterface[] + */ + public function vocabularyValues(SlotIdentifier $slot, string ...$values): \Generator; public function language(string $language): string; diff --git a/Services/MetaData/classes/Editor/Services/Services.php b/Services/MetaData/classes/Editor/Services/Services.php index 6d528bddb03a..14acc4be63f2 100644 --- a/Services/MetaData/classes/Editor/Services/Services.php +++ b/Services/MetaData/classes/Editor/Services/Services.php @@ -42,6 +42,7 @@ use ILIAS\MetaData\Editor\Observers\ObserverHandlerInterface; use ILIAS\MetaData\Manipulator\Services\Services as ManipulatorServices; use ILIAS\MetaData\Presentation\Services\Services as PresentationServices; +use ILIAS\MetaData\Vocabularies\Services\Services as VocabulariesServices; class Services { @@ -58,6 +59,7 @@ class Services protected RepositoryServices $repository_services; protected ManipulatorServices $manipulator_services; protected PresentationServices $presentation_services; + protected VocabulariesServices $vocabularies_services; public function __construct( GlobalContainer $dic, @@ -65,7 +67,8 @@ public function __construct( StructureServices $structure_services, RepositoryServices $repository_services, ManipulatorServices $manipulator_services, - PresentationServices $presentation_services + PresentationServices $presentation_services, + VocabulariesServices $vocabularies_services ) { $this->dic = $dic; $this->path_services = $path_services; @@ -73,6 +76,7 @@ public function __construct( $this->repository_services = $repository_services; $this->manipulator_services = $manipulator_services; $this->presentation_services = $presentation_services; + $this->vocabularies_services = $vocabularies_services; } public function presenter(): PresenterInterface @@ -85,8 +89,9 @@ public function presenter(): PresenterInterface $this->presentation_services->utilities() ), $data = new Data( - $utilities, - $this->presentation_services->data() + $this->presentation_services->utilities(), + $this->presentation_services->data(), + $this->vocabularies_services->presentation() ), new Elements( $data, diff --git a/Services/MetaData/classes/Elements/Data/Data.php b/Services/MetaData/classes/Elements/Data/Data.php index cda0b9dfa30a..e94e7534953e 100644 --- a/Services/MetaData/classes/Elements/Data/Data.php +++ b/Services/MetaData/classes/Elements/Data/Data.php @@ -20,15 +20,22 @@ namespace ILIAS\MetaData\Elements\Data; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; + class Data implements DataInterface { protected Type $type; protected string $value; + protected SlotIdentifier $vocabulary_slot; - public function __construct(Type $type, string $value) - { + public function __construct( + Type $type, + string $value, + SlotIdentifier $vocabulary_slot + ) { $this->type = $type; $this->value = $value; + $this->vocabulary_slot = $vocabulary_slot; } public function type(): Type @@ -40,4 +47,9 @@ public function value(): string { return $this->value; } + + public function vocabularySlot(): SlotIdentifier + { + return $this->vocabulary_slot; + } } diff --git a/Services/MetaData/classes/Elements/Data/DataFactory.php b/Services/MetaData/classes/Elements/Data/DataFactory.php index 7cf689c0f426..5c8a2610d4e6 100644 --- a/Services/MetaData/classes/Elements/Data/DataFactory.php +++ b/Services/MetaData/classes/Elements/Data/DataFactory.php @@ -20,11 +20,16 @@ namespace ILIAS\MetaData\Elements\Data; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; + class DataFactory implements DataFactoryInterface { - public function data(Type $type, string $value): DataInterface - { - return new Data($type, $value); + public function data( + Type $type, + string $value, + SlotIdentifier $vocabulary_slot = SlotIdentifier::NULL + ): DataInterface { + return new Data($type, $value, $vocabulary_slot); } public function null(): DataInterface diff --git a/Services/MetaData/classes/Elements/Data/DataFactoryInterface.php b/Services/MetaData/classes/Elements/Data/DataFactoryInterface.php index e85149ecb838..ea7f4275f300 100644 --- a/Services/MetaData/classes/Elements/Data/DataFactoryInterface.php +++ b/Services/MetaData/classes/Elements/Data/DataFactoryInterface.php @@ -20,9 +20,15 @@ namespace ILIAS\MetaData\Elements\Data; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; + interface DataFactoryInterface { - public function data(Type $type, string $value): DataInterface; + public function data( + Type $type, + string $value, + SlotIdentifier $vocabulary_slot = SlotIdentifier::NULL + ): DataInterface; public function null(): DataInterface; } diff --git a/Services/MetaData/classes/Elements/Data/DataInterface.php b/Services/MetaData/classes/Elements/Data/DataInterface.php index 05d223178ece..b59ad1d72c7c 100644 --- a/Services/MetaData/classes/Elements/Data/DataInterface.php +++ b/Services/MetaData/classes/Elements/Data/DataInterface.php @@ -20,6 +20,8 @@ namespace ILIAS\MetaData\Elements\Data; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; + interface DataInterface { /** @@ -31,4 +33,10 @@ public function type(): Type; * Value of the data, in a format according to its type. */ public function value(): string; + + /** + * Vocabulary slot the data belongs to (important for + * making vocab values/strings presentable). + */ + public function vocabularySlot(): SlotIdentifier; } diff --git a/Services/MetaData/classes/Elements/Data/NullData.php b/Services/MetaData/classes/Elements/Data/NullData.php index de1998767847..825158c176c0 100644 --- a/Services/MetaData/classes/Elements/Data/NullData.php +++ b/Services/MetaData/classes/Elements/Data/NullData.php @@ -20,6 +20,9 @@ namespace ILIAS\MetaData\Elements\Data; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; +use ILIAS\MetaData\Vocabularies\Slots\Identifier; + class NullData implements DataInterface { public function type(): Type @@ -31,4 +34,9 @@ public function value(): string { return ''; } + + public function vocabularySlot(): SlotIdentifier + { + return SlotIdentifier::NULL; + } } diff --git a/Services/MetaData/classes/Elements/Data/NullDataFactory.php b/Services/MetaData/classes/Elements/Data/NullDataFactory.php index b82822d4edec..e476c55b7186 100644 --- a/Services/MetaData/classes/Elements/Data/NullDataFactory.php +++ b/Services/MetaData/classes/Elements/Data/NullDataFactory.php @@ -20,10 +20,15 @@ namespace ILIAS\MetaData\Elements\Data; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; + class NullDataFactory implements DataFactoryInterface { - public function data(Type $type, string $value): DataInterface - { + public function data( + Type $type, + string $value, + SlotIdentifier $vocabulary_slot = SlotIdentifier::NULL + ): DataInterface { return new NullData(); } diff --git a/Services/MetaData/classes/Presentation/Data.php b/Services/MetaData/classes/Presentation/Data.php index 3f45c3ded8d7..54449e7e31ce 100644 --- a/Services/MetaData/classes/Presentation/Data.php +++ b/Services/MetaData/classes/Presentation/Data.php @@ -23,25 +23,31 @@ use ILIAS\MetaData\Elements\Data\DataInterface as ElementsDataInterface; use ILIAS\MetaData\Elements\Data\Type; use ILIAS\MetaData\DataHelper\DataHelperInterface; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; +use ILIAS\MetaData\Vocabularies\Dispatch\Presentation\PresentationInterface as VocabulariesPresentation; class Data implements DataInterface { protected UtilitiesInterface $utilities; protected DataHelperInterface $data_helper; + protected VocabulariesPresentation $vocab_presentation; public function __construct( UtilitiesInterface $utilities, - DataHelperInterface $data_helper + DataHelperInterface $data_helper, + VocabulariesPresentation $vocab_presentation ) { $this->utilities = $utilities; $this->data_helper = $data_helper; + $this->vocab_presentation = $vocab_presentation; } public function dataValue(ElementsDataInterface $data): string { switch ($data->type()) { case Type::VOCAB_VALUE: - return $this->vocabularyValue($data->value()); + case Type::STRING: + return $this->vocabularyValue($data->value(), $data->vocabularySlot()); case Type::LANG: return $this->language($data->value()); @@ -57,32 +63,16 @@ public function dataValue(ElementsDataInterface $data): string } } - public function vocabularyValue(string $value): string - { - $value = $this->camelCaseToSpaces($value); - $exceptions = [ - 'ispartof' => 'is_part_of', 'haspart' => 'has_part', - 'isversionof' => 'is_version_of', 'hasversion' => 'has_version', - 'isformatof' => 'is_format_of', 'hasformat' => 'has_format', - 'references' => 'references', - 'isreferencedby' => 'is_referenced_by', - 'isbasedon' => 'is_based_on', 'isbasisfor' => 'is_basis_for', - 'requires' => 'requires', 'isrequiredby' => 'is_required_by', - 'graphical designer' => 'graphicaldesigner', - 'technical implementer' => 'technicalimplementer', - 'content provider' => 'contentprovider', - 'technical validator' => 'technicalvalidator', - 'educational validator' => 'educationalvalidator', - 'script writer' => 'scriptwriter', - 'instructional designer' => 'instructionaldesigner', - 'subject matter expert' => 'subjectmatterexpert', - 'diagram' => 'diagramm' - ]; - if (array_key_exists($value, $exceptions)) { - $value = $exceptions[$value]; - } - - return $this->utilities->txt('meta_' . $this->fillSpaces($value)); + public function vocabularyValue( + string $value, + SlotIdentifier $vocabulary_slot + ): string { + return $this->vocab_presentation->presentableLabels( + $this->utilities, + $vocabulary_slot, + false, + $value + )->current()->label(); } public function language(string $language): string @@ -118,16 +108,4 @@ public function duration(string $duration): string } return implode(', ', $res_array); } - - protected function fillSpaces(string $string): string - { - $string = str_replace(' ', '_', $string); - return strtolower($string); - } - - protected function camelCaseToSpaces(string $string): string - { - $string = preg_replace('/(?<=[a-z])(?=[A-Z])/', ' ', $string); - return strtolower($string); - } } diff --git a/Services/MetaData/classes/Presentation/DataInterface.php b/Services/MetaData/classes/Presentation/DataInterface.php index fab8a03014b0..e4a1fea6f4f8 100644 --- a/Services/MetaData/classes/Presentation/DataInterface.php +++ b/Services/MetaData/classes/Presentation/DataInterface.php @@ -21,12 +21,16 @@ namespace ILIAS\MetaData\Presentation; use ILIAS\MetaData\Elements\Data\DataInterface as ElementsDataInterface; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; interface DataInterface { public function dataValue(ElementsDataInterface $data): string; - public function vocabularyValue(string $value): string; + public function vocabularyValue( + string $value, + SlotIdentifier $vocabulary_slot + ): string; public function language(string $language): string; diff --git a/Services/MetaData/classes/Presentation/NullData.php b/Services/MetaData/classes/Presentation/NullData.php index 0d74e8cf94ad..06924af06a68 100644 --- a/Services/MetaData/classes/Presentation/NullData.php +++ b/Services/MetaData/classes/Presentation/NullData.php @@ -21,6 +21,7 @@ namespace ILIAS\MetaData\Presentation; use ILIAS\MetaData\Elements\Data\DataInterface as ElementsDataInterface; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; class NullData implements DataInterface { @@ -29,8 +30,10 @@ public function dataValue(ElementsDataInterface $data): string return ''; } - public function vocabularyValue(string $value): string - { + public function vocabularyValue( + string $value, + SlotIdentifier $vocabulary_slot + ): string { return ''; } diff --git a/Services/MetaData/classes/Presentation/Services/Services.php b/Services/MetaData/classes/Presentation/Services/Services.php index 6a88ac3fe5f8..b194248e6542 100644 --- a/Services/MetaData/classes/Presentation/Services/Services.php +++ b/Services/MetaData/classes/Presentation/Services/Services.php @@ -28,6 +28,7 @@ use ILIAS\MetaData\Presentation\Data; use ILIAS\MetaData\Presentation\Elements; use ILIAS\MetaData\DataHelper\Services\Services as DataHelperServices; +use ILIAS\MetaData\Vocabularies\Services\Services as VocabulariesServices; class Services { @@ -37,13 +38,16 @@ class Services protected GlobalContainer $dic; protected DataHelperServices $data_helper_services; + protected VocabulariesServices $vocabularies_services; public function __construct( GlobalContainer $dic, - DataHelperServices $data_helper_services + DataHelperServices $data_helper_services, + VocabulariesServices $vocabularies_services ) { $this->dic = $dic; $this->data_helper_services = $data_helper_services; + $this->vocabularies_services = $vocabularies_services; } public function utilities(): UtilitiesInterface @@ -64,7 +68,8 @@ public function data(): DataInterface } return $this->data = new Data( $this->utilities(), - $this->data_helper_services->dataHelper() + $this->data_helper_services->dataHelper(), + $this->vocabularies_services->presentation() ); } diff --git a/Services/MetaData/classes/Services/InternalServices.php b/Services/MetaData/classes/Services/InternalServices.php index 6317a2fca40e..1a14ad06b90b 100644 --- a/Services/MetaData/classes/Services/InternalServices.php +++ b/Services/MetaData/classes/Services/InternalServices.php @@ -58,10 +58,6 @@ public function __construct(GlobalContainer $dic) $this->structure_services ); $this->data_helper_services = new DataHelperServices(); - $this->presentation_services = new PresentationServices( - $this->dic, - $this->data_helper_services - ); $this->search_services = new SearchServices(); $this->manipulator_services = new ManipulatorServices( $this->path_services, @@ -78,6 +74,11 @@ public function __construct(GlobalContainer $dic) $this->structure_services, $this->copyright_services ); + $this->presentation_services = new PresentationServices( + $this->dic, + $this->data_helper_services, + $this->vocabularies_services + ); $this->repository_services = new RepositoryServices( $this->dic, $this->path_services, @@ -92,7 +93,8 @@ public function __construct(GlobalContainer $dic) $this->structure_services, $this->repository_services, $this->manipulator_services, - $this->presentation_services + $this->presentation_services, + $this->vocabularies_services ); $this->xml_services = new XMLServices( $this->dic, diff --git a/Services/MetaData/classes/Settings/Vocabularies/Presentation.php b/Services/MetaData/classes/Settings/Vocabularies/Presentation.php index af0047298fb6..fd74504a7077 100644 --- a/Services/MetaData/classes/Settings/Vocabularies/Presentation.php +++ b/Services/MetaData/classes/Settings/Vocabularies/Presentation.php @@ -31,7 +31,6 @@ use ILIAS\MetaData\Paths\Navigator\NavigatorFactoryInterface as NavigatorFactory; use ILIAS\MetaData\Paths\PathInterface; use ILIAS\MetaData\Elements\Structure\StructureElementInterface; -use ILIAS\MetaData\Elements\Data\Type as DataType; use ILIAS\MetaData\Vocabularies\Slots\Conditions\ConditionInterface; use ILIAS\MetaData\Paths\FactoryInterface as PathFactory; @@ -94,7 +93,7 @@ public function makeTypePresentable(VocabType $type): string public function makeSlotPresentable(SlotIdentifier $slot): string { //skip the name of the element if it does not add any information - $skip_data = [DataType::VOCAB_VALUE, DataType::STRING]; + $skip_data = ['string', 'value']; $element = $this->getStructureElementFromPath( $this->slot_handler->pathForSlot($slot), @@ -104,7 +103,7 @@ public function makeSlotPresentable(SlotIdentifier $slot): string $element, null, false, - in_array($element->getDefinition()->dataType(), $skip_data), + in_array($element->getDefinition()->name(), $skip_data), ); if (!$this->slot_handler->isSlotConditional($slot)) { @@ -121,7 +120,7 @@ public function makeSlotPresentable(SlotIdentifier $slot): string $condition_element, $this->findFirstCommonParent($element, $condition_element)->getSuperElement(), false, - in_array($element->getDefinition()->dataType(), $skip_data), + in_array($element->getDefinition()->name(), $skip_data), ); return $this->presentation_utils->txtFill( @@ -191,8 +190,15 @@ protected function findFirstCommonParent( StructureElementInterface $a, StructureElementInterface $b ): StructureElementInterface { - while ($a !== $b) { + $a_supers = []; + while ($a) { + $a_supers[] = $a; $a = $a->getSuperElement(); + } + while ($b) { + if (in_array($b, $a_supers, true)) { + return $b; + } $b = $b->getSuperElement(); } return $a; diff --git a/Services/MetaData/classes/Vocabularies/Dispatch/Presentation/NullPresentation.php b/Services/MetaData/classes/Vocabularies/Dispatch/Presentation/NullPresentation.php new file mode 100644 index 000000000000..5658a68387f1 --- /dev/null +++ b/Services/MetaData/classes/Vocabularies/Dispatch/Presentation/NullPresentation.php @@ -0,0 +1,51 @@ +label() === '') { + $label = new LabelledValue($label->value(), $label->value()); + } $labelled_values[$label->value()] = $label; } @@ -93,16 +96,16 @@ public function presentableLabels( yield $labelled_value; continue; } - $label = $value; + $label = (string) $value; if ($with_unknown_vocab_flag) { $label .= ' ' . $presentation_utilities->txt('md_unknown_vocabulary_flag'); } - yield new LabelledValue($value, $label); + yield new LabelledValue((string) $value, $label); } } /** - * @return LabelledValue[] + * @return LabelledValueInterface[] */ public function labelsForVocabulary( PresentationUtilities $presentation_utilities, diff --git a/Services/MetaData/classes/Vocabularies/Dispatch/Presentation/PresentationInterface.php b/Services/MetaData/classes/Vocabularies/Dispatch/Presentation/PresentationInterface.php index 36b329db6404..681c206e3309 100644 --- a/Services/MetaData/classes/Vocabularies/Dispatch/Presentation/PresentationInterface.php +++ b/Services/MetaData/classes/Vocabularies/Dispatch/Presentation/PresentationInterface.php @@ -30,7 +30,7 @@ interface PresentationInterface /** * For values not from any active vocabulary, returns the * value itself as label, optionally with 'unknown vocabulary' suffix. - * @return LabelledValue[] + * @return LabelledValueInterface[] */ public function presentableLabels( PresentationUtilities $presentation_utilities, @@ -40,7 +40,7 @@ public function presentableLabels( ): \Generator; /** - * @return LabelledValue[] + * @return LabelledValueInterface[] */ public function labelsForVocabulary( PresentationUtilities $presentation_utilities, diff --git a/Services/MetaData/classes/Vocabularies/ElementHelper/ElementHelper.php b/Services/MetaData/classes/Vocabularies/ElementHelper/ElementHelper.php new file mode 100644 index 000000000000..eac352bda7b4 --- /dev/null +++ b/Services/MetaData/classes/Vocabularies/ElementHelper/ElementHelper.php @@ -0,0 +1,97 @@ +slot_handler = $slot_handler; + $this->path_factory = $path_factory; + $this->navigator_factory = $navigator_factory; + $this->condition_checker = $condition_checker; + $this->reader = $reader; + } + + public function slotForElement(ElementInterface $element): SlotIdentifier + { + foreach ($this->slotsForElementWithoutCondition($element) as $slot) { + if ($this->condition_checker->doesElementFitSlot($element, $slot)) { + return $slot; + } + } + return SlotIdentifier::NULL; + } + + /** + * @return SlotIdentifier[] + */ + public function slotsForElementWithoutCondition(ElementInterface $element): \Generator + { + yield from $this->slot_handler->allSlotsForPath($this->path_factory->toElement($element)); + } + + /** + * @return VocabularyInterface[] + */ + public function vocabulariesForSlot( + SlotIdentifier $slot + ): \Generator { + yield from $this->reader->activeVocabulariesForSlots($slot); + } + + public function findElementOfCondition( + SlotIdentifier $slot, + ElementInterface $element, + ElementInterface ...$all_elements + ): ?ElementInterface { + if (!$this->slot_handler->isSlotConditional($slot)) { + return null; + } + + $condition_path = $this->slot_handler->conditionForSlot($slot)->path(); + $potential_result = $this->navigator_factory->navigator($condition_path, $element) + ->lastElementAtFinalStep(); + + return in_array($potential_result, $all_elements, true) ? $potential_result : null; + } +} diff --git a/Services/MetaData/classes/Vocabularies/ElementHelper/ElementHelperInterface.php b/Services/MetaData/classes/Vocabularies/ElementHelper/ElementHelperInterface.php index 11f2cee01d7e..ce8547449dc1 100644 --- a/Services/MetaData/classes/Vocabularies/ElementHelper/ElementHelperInterface.php +++ b/Services/MetaData/classes/Vocabularies/ElementHelper/ElementHelperInterface.php @@ -18,11 +18,33 @@ declare(strict_types=1); -namespace ILIAS\MetaData\Vocabularies\Manager; +namespace ILIAS\MetaData\Vocabularies\ElementHelper; use ILIAS\MetaData\Vocabularies\VocabularyInterface; use ILIAS\MetaData\Elements\ElementInterface; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; interface ElementHelperInterface { + public function slotForElement(ElementInterface $element): SlotIdentifier; + + /** + * Does not check the condition of the slots, so can return multiple slots + * per element. + * @return SlotIdentifier[] + */ + public function slotsForElementWithoutCondition(ElementInterface $element): \Generator; + + /** + * @return VocabularyInterface[] + */ + public function vocabulariesForSlot( + SlotIdentifier $slot + ): \Generator; + + public function findElementOfCondition( + SlotIdentifier $slot, + ElementInterface $element, + ElementInterface ...$all_elements + ): ?ElementInterface; } diff --git a/Services/MetaData/classes/Vocabularies/Services/Services.php b/Services/MetaData/classes/Vocabularies/Services/Services.php index ea58d18f6066..4317f73dec99 100644 --- a/Services/MetaData/classes/Vocabularies/Services/Services.php +++ b/Services/MetaData/classes/Vocabularies/Services/Services.php @@ -44,11 +44,15 @@ use ILIAS\MetaData\Vocabularies\Dispatch\Reader; use ILIAS\MetaData\Vocabularies\Dispatch\Actions; use ILIAS\MetaData\Vocabularies\Dispatch\Info\Infos; +use ILIAS\MetaData\Vocabularies\ElementHelper\ElementHelperInterface; +use ILIAS\MetaData\Vocabularies\ElementHelper\ElementHelper; +use ILIAS\MetaData\Vocabularies\Slots\Conditions\Checker; class Services { protected PresentationInterface $presentation; protected ManagerInterface $manager; + protected ElementHelperInterface $element_helper; protected SlotHandler $slot_handler; protected ReaderInterface $reader; @@ -86,6 +90,24 @@ public function presentation(): PresentationInterface ); } + public function elementHelper(): ElementHelperInterface + { + if (isset($this->element_helper)) { + return $this->element_helper; + } + return $this->element_helper = new ElementHelper( + $this->slotHandler(), + $this->path_services->pathFactory(), + $this->path_services->navigatorFactory(), + new Checker( + $this->slotHandler(), + $this->path_services->pathFactory(), + $this->path_services->navigatorFactory() + ), + $this->reader() + ); + } + public function manager(): ManagerInterface { if (isset($this->manager)) { diff --git a/Services/MetaData/classes/Vocabularies/Slots/Conditions/Checker.php b/Services/MetaData/classes/Vocabularies/Slots/Conditions/Checker.php index 3a0a990a12d7..bd18d5d2584a 100644 --- a/Services/MetaData/classes/Vocabularies/Slots/Conditions/Checker.php +++ b/Services/MetaData/classes/Vocabularies/Slots/Conditions/Checker.php @@ -52,11 +52,12 @@ public function doesElementFitSlot( ): bool { $slot_path = $this->slots_handler->pathForSlot($slot); $path_to_element = $this->path_factory->toElement($element); - if ($slot_path !== $path_to_element) { + + if ($slot_path->toString() !== $path_to_element->toString()) { return false; } - if ($this->slots_handler->isSlotConditional($slot)) { + if (!$this->slots_handler->isSlotConditional($slot)) { return true; } diff --git a/Services/MetaData/test/Elements/Data/DataFactoryTest.php b/Services/MetaData/test/Elements/Data/DataFactoryTest.php index 1e21e7f9e4cd..2f6b54d3af6b 100644 --- a/Services/MetaData/test/Elements/Data/DataFactoryTest.php +++ b/Services/MetaData/test/Elements/Data/DataFactoryTest.php @@ -21,6 +21,7 @@ namespace ILIAS\MetaData\Elements\Data; use PHPUnit\Framework\TestCase; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; class DataFactoryTest extends TestCase { @@ -32,6 +33,18 @@ public function testCreateData(): void $this->assertInstanceOf(DataInterface::class, $data); } + public function testCreateDataSlotNotNull(): void + { + $factory = new DataFactory(); + $data = $factory->data( + Type::VOCAB_VALUE, + 'value', + SlotIdentifier::CLASSIFICATION_PURPOSE + ); + + $this->assertInstanceOf(DataInterface::class, $data); + } + public function testCreateNullData(): void { $factory = new DataFactory(); diff --git a/Services/MetaData/test/Elements/Data/DataTest.php b/Services/MetaData/test/Elements/Data/DataTest.php index aadb7180b6a9..32186fb3f87b 100644 --- a/Services/MetaData/test/Elements/Data/DataTest.php +++ b/Services/MetaData/test/Elements/Data/DataTest.php @@ -21,14 +21,20 @@ namespace ILIAS\MetaData\Elements\Data; use PHPUnit\Framework\TestCase; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; class DataTest extends TestCase { public function testTypeAndValue(): void { - $data = new Data(Type::VOCAB_VALUE, 'value'); + $data = new Data( + Type::VOCAB_VALUE, + 'value', + SlotIdentifier::CLASSIFICATION_TAXON_ENTRY + ); $this->assertSame($data->type(), Type::VOCAB_VALUE); $this->assertSame($data->value(), 'value'); + $this->assertSame($data->vocabularySlot(), SlotIdentifier::CLASSIFICATION_TAXON_ENTRY); } } diff --git a/Services/MetaData/test/Elements/ElementTest.php b/Services/MetaData/test/Elements/ElementTest.php index 726f5857c11d..7d65fd7fd0d7 100644 --- a/Services/MetaData/test/Elements/ElementTest.php +++ b/Services/MetaData/test/Elements/ElementTest.php @@ -111,7 +111,7 @@ public function dataType(): Type } }; - $data = new class () implements DataInterface { + $data = new class () extends NullData { public function type(): Type { return Type::STRING; diff --git a/Services/MetaData/test/Presentation/DataTest.php b/Services/MetaData/test/Presentation/DataTest.php index 11620b01421f..2519c9208146 100644 --- a/Services/MetaData/test/Presentation/DataTest.php +++ b/Services/MetaData/test/Presentation/DataTest.php @@ -26,19 +26,28 @@ use ILIAS\MetaData\Elements\Data\Type; use ILIAS\MetaData\Elements\Data\NullData; use ILIAS\MetaData\DataHelper\NullDataHelper; +use ILIAS\MetaData\Vocabularies\Dispatch\Presentation\NullPresentation as NullVocabulariesPresentation; +use ILIAS\MetaData\Presentation\UtilitiesInterface as PresentationUtilities; +use ILIAS\MetaData\Vocabularies\Slots\Identifier as SlotIdentifier; +use ILIAS\MetaData\Vocabularies\Dispatch\Presentation\NullLabelledValue; +use ILIAS\MetaData\Vocabularies\Slots\Identifier; class DataTest extends TestCase { - protected function getElementData(Type $type, string $value): ElementData - { - return new class ($type, $value) extends NullData { - protected Type $type; - protected string $value; - - public function __construct(Type $type, string $value) - { + protected function getElementData( + Type $type, + string $value, + SlotIdentifier $vocab_slot = SlotIdentifier::NULL + ): ElementData { + return new class ($type, $value, $vocab_slot) extends NullData { + public function __construct( + protected Type $type, + protected string $value, + protected SlotIdentifier $vocab_slot + ) { $this->type = $type; $this->value = $value; + $this->vocab_slot = $vocab_slot; } public function type(): Type @@ -50,6 +59,11 @@ public function value(): string { return $this->value; } + + public function vocabularySlot(): SlotIdentifier + { + return $this->vocab_slot; + } }; } @@ -97,19 +111,46 @@ public function datetimeToObject(string $datetime): \DateTimeImmutable } }; - return new Data($util, $helper); + $vocab_presentation = new class () extends NullVocabulariesPresentation { + public function presentableLabels( + PresentationUtilities $presentation_utilities, + SlotIdentifier $slot, + bool $with_unknown_vocab_flag, + string ...$values + ): \Generator { + foreach ($values as $value) { + yield new class ($value, $slot, $with_unknown_vocab_flag) extends NullLabelledValue { + public function __construct( + protected string $value, + protected SlotIdentifier $slot, + protected bool $with_unknown_vocab_flag + ) { + } + + public function value(): string + { + return $this->value; + } + + public function label(): string + { + return 'vocab ' . $this->slot->value . ' ' . + $this->value . ($this->with_unknown_vocab_flag ? ' flagged' : ''); + } + }; + } + } + }; + + return new Data($util, $helper, $vocab_presentation); } public function testVocabularyValue(): void { $data = $this->getData(); $this->assertSame( - 'translated meta_some_key', - $data->vocabularyValue('SomeKey') - ); - $this->assertSame( - 'translated meta_subjectmatterexpert', - $data->vocabularyValue('subjectMatterExpert') + 'vocab rights_cost SomeKey', + $data->vocabularyValue('SomeKey', SlotIdentifier::RIGHTS_COST), ); } @@ -144,12 +185,20 @@ public function testDataValue(): void { $data = $this->getData(); $this->assertSame( - 'translated meta_some_key', - $data->dataValue($this->getElementData(Type::VOCAB_VALUE, 'SomeKey')) + 'vocab rights_cost SomeKey', + $data->dataValue($this->getElementData( + Type::VOCAB_VALUE, + 'SomeKey', + SlotIdentifier::RIGHTS_COST + )) ); $this->assertSame( - 'translated meta_subjectmatterexpert', - $data->dataValue($this->getElementData(Type::VOCAB_VALUE, 'subjectMatterExpert')) + 'vocab rights_cost SomeKey', + $data->dataValue($this->getElementData( + Type::STRING, + 'SomeKey', + SlotIdentifier::RIGHTS_COST + )) ); $this->assertSame( 'translated meta_l_key', @@ -165,7 +214,7 @@ public function testDataValue(): void ); $this->assertSame( 'This should just go through.', - $data->dataValue($this->getElementData(Type::STRING, 'This should just go through.')) + $data->dataValue($this->getElementData(Type::VOCAB_SOURCE, 'This should just go through.')) ); } } diff --git a/Services/MetaData/test/Vocabularies/Dispatch/Presentation/PresentationTest.php b/Services/MetaData/test/Vocabularies/Dispatch/Presentation/PresentationTest.php index cd40476d7d92..d3179fe4b236 100644 --- a/Services/MetaData/test/Vocabularies/Dispatch/Presentation/PresentationTest.php +++ b/Services/MetaData/test/Vocabularies/Dispatch/Presentation/PresentationTest.php @@ -122,12 +122,14 @@ public function label(): string public function getControlledRepository( SlotIdentifier $slot_with_vocab, bool $only_active, + bool $empty_labels, string ...$values_from_vocab ): ControlledRepository { - return new class ($slot_with_vocab, $only_active, $values_from_vocab) extends NullControlledRepository { + return new class ($slot_with_vocab, $only_active, $empty_labels, $values_from_vocab) extends NullControlledRepository { public function __construct( protected SlotIdentifier $slot_with_vocab, protected bool $only_active, + protected bool $empty_labels, protected array $values_from_vocab ) { } @@ -144,9 +146,11 @@ public function getLabelsForValues( if (!in_array($value, $this->values_from_vocab)) { continue; } - yield new class ($value) extends NullLabelledValue { - public function __construct(protected string $value) - { + yield new class ($value, $this->empty_labels) extends NullLabelledValue { + public function __construct( + protected string $value, + protected bool $empty_label + ) { } public function value(): string @@ -156,6 +160,9 @@ public function value(): string public function label(): string { + if ($this->empty_label) { + return ''; + } return 'controlled label for ' . $this->value; } }; @@ -284,6 +291,7 @@ public function testPresentableLabels( $this->getControlledRepository( SlotIdentifier::EDUCATIONAL_DIFFICULTY, true, + false, ...($is_in_controlled ? [$value] : []) ), $this->getStandardRepository( @@ -324,6 +332,7 @@ public function testPresentableLabelsWithUnknownVocab( $this->getControlledRepository( SlotIdentifier::EDUCATIONAL_DIFFICULTY, true, + false, ...($is_in_controlled ? [$value] : []) ), $this->getStandardRepository( @@ -347,6 +356,35 @@ public function testPresentableLabelsWithUnknownVocab( ); } + public function testPresentableLabelsWithEmptyLabelFromControlledVocabulary(): void + { + $value = 'some value'; + $presentation = new Presentation( + $this->getCopyrightBridge(SlotIdentifier::EDUCATIONAL_DIFFICULTY), + $this->getControlledRepository( + SlotIdentifier::EDUCATIONAL_DIFFICULTY, + true, + true, + ), + $this->getStandardRepository( + SlotIdentifier::EDUCATIONAL_DIFFICULTY, + true + ) + ); + + $labels = $presentation->presentableLabels( + $this->getPresentationUtilities(), + SlotIdentifier::EDUCATIONAL_DIFFICULTY, + false, + $value + ); + + $this->assertLabelledValuesMatchInOrder( + $labels, + [['value' => $value, 'label' => $value]] + ); + } + public function testPresentableLabelsMultipleValues(): void { $presentation = new Presentation( @@ -358,6 +396,7 @@ public function testPresentableLabelsMultipleValues(): void $this->getControlledRepository( SlotIdentifier::EDUCATIONAL_DIFFICULTY, true, + false, 'contr 1', 'contr 2', 'contr 3' @@ -405,6 +444,7 @@ public function testLabelsForVocabularyStandard(): void $this->getControlledRepository( SlotIdentifier::EDUCATIONAL_DIFFICULTY, false, + false, 'v1', 'v2', 'v3' @@ -451,6 +491,7 @@ public function testLabelsForVocabularyControlledString(): void $this->getControlledRepository( SlotIdentifier::EDUCATIONAL_DIFFICULTY, false, + false, 'v1', 'v2', 'v3' @@ -497,6 +538,7 @@ public function testLabelsForVocabularyControlledVocabValue(): void $this->getControlledRepository( SlotIdentifier::EDUCATIONAL_DIFFICULTY, false, + false, 'v1', 'v2', 'v3' @@ -543,6 +585,7 @@ public function testLabelsForVocabularyCopyright(): void $this->getControlledRepository( SlotIdentifier::EDUCATIONAL_DIFFICULTY, false, + false, 'v1', 'v2', 'v3' diff --git a/Services/MetaData/test/XML/Writer/Standard/StandardTest.php b/Services/MetaData/test/XML/Writer/Standard/StandardTest.php index 977747c587e4..6d43d76435e6 100644 --- a/Services/MetaData/test/XML/Writer/Standard/StandardTest.php +++ b/Services/MetaData/test/XML/Writer/Standard/StandardTest.php @@ -36,6 +36,7 @@ use ILIAS\MetaData\XML\Dictionary\TagInterface; use ILIAS\MetaData\XML\Dictionary\NullTag; use ILIAS\MetaData\XML\SpecialCase; +use ILIAS\MetaData\Elements\Data\NullData; class StandardTest extends TestCase { @@ -69,7 +70,7 @@ public function name(): string public function getData(): DataInterface { - return new class ($this->element_as_array) implements DataInterface { + return new class ($this->element_as_array) extends NullData { public function __construct(protected array $element_as_array) { } diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 1d6ab183b504..ab10d9c9f799 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -11807,6 +11807,9 @@ meta#:#md_days#:#Tage: meta#:#md_delete_cp_sure#:#Möchten Sie wirklich die ausgewählten Vorgaben entfernen? meta#:#md_delimiter#:#Trennzeichen meta#:#md_delimiter_info#:#Das Trennzeichen wird in der Schnellbearbeitung der Metadaten verwendet, um mehrere Angaben zu trennen. Standardzeichen ist ','.###Modified as part of gender mainstreaming activities for ILIAS 8 +meta#:#md_editor_custom_input#:#Eigene Angabe +meta#:#md_editor_from_vocab_input#:#Aus Vokabular +meta#:#md_editor_value#:#Begriff meta#:#md_fields#:#Felder meta#:#md_import_file_vocab#:#Importdatei meta#:#md_import_vocab#:#Import diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 59234d08fe18..f4994abf5a80 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -11808,6 +11808,9 @@ meta#:#md_days#:#Days: meta#:#md_delete_cp_sure#:#Are you sure you want to delete the following entries? meta#:#md_delimiter#:#Delimiter meta#:#md_delimiter_info#:#The delimiter is used in the quick editing screen to separate multiple keywords and/or authors. Default is ‘,’. +meta#:#md_editor_custom_input#:#Custom +meta#:#md_editor_from_vocab_input#:#From Vocabulary +meta#:#md_editor_value#:#Value meta#:#md_fields#:#Fields meta#:#md_import_file_vocab#:#Import File meta#:#md_import_vocab#:#Import