Skip to content

Commit

Permalink
[Feature]: Manage select options via interface
Browse files Browse the repository at this point in the history
Add Select Options management interface via Settings > Data Objects submenu.
Add selectoptions permission to allow access to management interface.
Add options source selection to select fields definition to switch between
direct configuration, select options or class / service configuration.
Generate configuration file and enum to store select options.
Introduce OptionsProviderInterface to detect field definitions using provider.
Introduce OptionsProviderTrait to share properties between select types.
Update classes rebuild command to also rebuild select options.
Add PHP and JavaScript reserved words helper classes.
Add and update documentation.
Relates to pimcore#3409
  • Loading branch information
kjkooistra-youwe committed Sep 28, 2023
1 parent e7c5448 commit 887b2d5
Show file tree
Hide file tree
Showing 45 changed files with 1,665 additions and 90 deletions.
3 changes: 3 additions & 0 deletions .github/ci/files/config/config.yaml
Expand Up @@ -41,3 +41,6 @@ pimcore:
type: 'settings-store'
read_target:
type: 'settings-store'
select_options:
write_target:
type: 'settings-store'
6 changes: 6 additions & 0 deletions bundles/CoreBundle/config/class_builder.yaml
Expand Up @@ -47,3 +47,9 @@ services:
class: Pimcore\DataObject\ClassBuilder\PHPObjectBrickContainerClassDumper
public: true

Pimcore\DataObject\ClassBuilder\PHPSelectOptionsEnumDumperInterface:
class: 'Pimcore\DataObject\ClassBuilder\PHPSelectOptionsEnumDumper'
public: true

Pimcore\DataObject\ClassBuilder\SelectOptionsEnumBuilderInterface:
class: 'Pimcore\DataObject\ClassBuilder\SelectOptionsEnumBuilder'
Expand Up @@ -21,6 +21,7 @@
use Pimcore\DataObject\ClassBuilder\PHPFieldCollectionClassDumperInterface;
use Pimcore\DataObject\ClassBuilder\PHPObjectBrickClassDumperInterface;
use Pimcore\DataObject\ClassBuilder\PHPObjectBrickContainerClassDumperInterface;
use Pimcore\DataObject\ClassBuilder\PHPSelectOptionsEnumDumperInterface;
use Pimcore\Model\DataObject;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputInterface;
Expand All @@ -41,6 +42,7 @@ public function __construct(
protected PHPFieldCollectionClassDumperInterface $collectionClassDumper,
protected PHPObjectBrickClassDumperInterface $brickClassDumper,
protected PHPObjectBrickContainerClassDumperInterface $brickContainerClassDumper,
protected PHPSelectOptionsEnumDumperInterface $selectOptionsEnumDumper,
) {
parent::__construct();
}
Expand Down Expand Up @@ -76,6 +78,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$this->collectionClassDumper->dumpPHPClass($fcDefinition);
}

$selectOptionConfigurations = new DataObject\SelectOptions\Config\Listing();
foreach ($selectOptionConfigurations as $selectOptionConfiguration) {
$this->selectOptionsEnumDumper->dumpPHPEnum($selectOptionConfiguration);
}

if ($cacheStatus) {
\Pimcore\Cache::enable();
}
Expand Down
13 changes: 13 additions & 0 deletions bundles/CoreBundle/src/Command/ClassesRebuildCommand.php
Expand Up @@ -146,6 +146,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$fc->save(false);
}

if ($output->isVerbose()) {
$output->writeln('---------------------');
$output->writeln('Saving all select options');
}
$selectOptionConfigurations = new DataObject\SelectOptions\Config\Listing();
foreach ($selectOptionConfigurations as $selectOptionConfiguration) {
if ($output->isVerbose()) {
$output->writeln(sprintf('%s saved', $selectOptionConfiguration->getId()));
}

$selectOptionConfiguration->generateEnumFiles();
}

return 0;
}
}
26 changes: 26 additions & 0 deletions bundles/CoreBundle/src/DependencyInjection/Configuration.php
Expand Up @@ -139,6 +139,7 @@ public function getConfigTreeBuilder(): TreeBuilder
'custom_views' => PIMCORE_CONFIGURATION_DIRECTORY . '/custom_views',
'object_custom_layouts' => PIMCORE_CONFIGURATION_DIRECTORY . '/object_custom_layouts',
'system_settings' => PIMCORE_CONFIGURATION_DIRECTORY . '/system_settings',
'select_options' => PIMCORE_CONFIGURATION_DIRECTORY . '/select_options',
]);

ConfigurationHelper::addConfigLocationTargetNode(
Expand Down Expand Up @@ -693,6 +694,31 @@ private function addObjectsNode(ArrayNodeDefinition $rootNode): void
->end()
->end()
->end()
->arrayNode('select_options')
->addDefaultsIfNotSet()
->children()
->arrayNode('definitions')
->normalizeKeys(false)
->prototype('array')
->children()
->scalarNode('id')->end()
->scalarNode('group')->end()
->scalarNode('useTraits')->end()
->scalarNode('implementsInterfaces')->end()
->arrayNode('selectOptions')
->prototype('array')
->children()
->scalarNode('value')->end()
->scalarNode('label')->end()
->scalarNode('name')->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end();
$classDefinitionsNode = $objectsNode
->children()
Expand Down
41 changes: 41 additions & 0 deletions bundles/CoreBundle/src/Migrations/Version20230221073317.php
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\CoreBundle\Migrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20230221073317 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add selectoptions user permission';
}

public function up(Schema $schema): void
{
$this->addSql("INSERT IGNORE INTO users_permission_definitions (`key`) VALUES('selectoptions');");
}

public function down(Schema $schema): void
{
$this->addSql("DELETE FROM users_permission_definitions WHERE `key` = 'selectoptions'");
}
}
@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\CoreBundle\OptionsProvider;

use Pimcore\Model\DataObject\ClassDefinition\Data;
use Pimcore\Model\DataObject\ClassDefinition\DynamicOptionsProvider\SelectOptionsProviderInterface;
use Pimcore\Model\DataObject\SelectOptions\Config;
use Pimcore\Model\DataObject\SelectOptions\Data\SelectOption;
use Pimcore\Model\DataObject\Service;

class SelectOptionsOptionsProvider implements SelectOptionsProviderInterface
{
public function getOptions(array $context, Data $fieldDefinition): array
{
if (!$fieldDefinition instanceof Data\OptionsProviderInterface) {
return [];
}

$configurationId = $fieldDefinition->getOptionsProviderData();
$selectOptionsConfiguration = Config::getById($configurationId);
if ($selectOptionsConfiguration === null) {
throw new \Exception('Missing select options configuration ' . $configurationId, 1677137682677);
}

return array_map(
fn (SelectOption $selectOption) => [
'value' => $selectOption->getValue(),
'key' => $selectOption->getLabel(),
],
$selectOptionsConfiguration->getSelectOptions(),
);
}

public function hasStaticOptions(array $context, Data $fieldDefinition): bool
{
return true;
}

public function getDefaultValue(array $context, Data $fieldDefinition): ?string
{
if ($fieldDefinition instanceof Data\Select) {
return $fieldDefinition->getDefaultValue();
}
return null;
}
}
1 change: 1 addition & 0 deletions bundles/InstallBundle/src/Installer.php
Expand Up @@ -808,6 +808,7 @@ protected function insertDatabaseContents(): void
$userPermissions = [
'assets',
'classes',
'selectoptions',
'clear_cache',
'clear_fullpage_cache',
'clear_temp_files',
Expand Down
2 changes: 2 additions & 0 deletions config/dao-classmap.php
Expand Up @@ -60,6 +60,8 @@
'Pimcore\\Model\\DataObject\\Objectbrick\\Definition' => 'Pimcore\\Model\\DataObject\\Objectbrick\\Definition\\Dao',
'Pimcore\\Model\\DataObject\\QuantityValue\\Unit' => 'Pimcore\\Model\\DataObject\\QuantityValue\\Unit\\Dao',
'Pimcore\\Model\\DataObject\\QuantityValue\\Unit\\Listing' => 'Pimcore\\Model\\DataObject\\QuantityValue\\Unit\\Listing\\Dao',
'Pimcore\\Model\\DataObject\\SelectOptions\\Config' => 'Pimcore\\Model\\DataObject\\SelectOptions\\Config\\Dao',
'Pimcore\\Model\\DataObject\\SelectOptions\\Config\\Listing' => 'Pimcore\\Model\\DataObject\\SelectOptions\\Config\\Listing\\Dao',
'Pimcore\\Model\\DataObject\\Service' => 'Pimcore\\Model\\Element\\Dao',
'Pimcore\\Model\\Dependency' => 'Pimcore\\Model\\Dependency\\Dao',
'Pimcore\\Model\\Document' => 'Pimcore\\Model\\Document\\Dao',
Expand Down
Expand Up @@ -10,14 +10,19 @@ Note that there are two ways to define an options provider.

Either simply specify the class name ...

![Select Field](../../../img/dynselect1.png)
![Select Field](../../../img/dynamic_select_class.png)

... or the name of a Symfony service (notice the prefix).
![Select Field](../../../img/dynselect1b.png)
![Select Field](../../../img/dynamic_select_service.png)

The services.yaml would then look like this one ...
The services.yaml would then look like this:

![Select Field](../../../img/dynselect1a.png)
```yaml
services:
website.optionsprovider:
class: Website\OptionsProvider
public: true
```

Depending on your datatype you have to implement the appropriate interface.

Expand Down
@@ -0,0 +1,70 @@
# Select Options

Select options are predefined sets of options which may be used for (multi)select fields.

![Fieldcollection Configuration](../../../img/classes-datatypes-selectoptions-editor.png)

The 'Name' column is optional, unless the value can't be converted to a valid PHP enum case.
This applies to values starting with a number or certain symbols.
A name may contain alphanumeric characters and underscores.

## Field configuration

Set the options source to 'Select Options' and select one of the select options sets.

![Fieldcollection Configuration](../../../img/classes-datatypes-selectoptions-selector.png)

## Working with PHP API

The configuration in the first screenshot generates the [backed enum](https://www.php.net/manual/en/language.enumerations.backed.php) below.

```php
<?php

namespace Pimcore\Model\DataObject\SelectOptions;

enum TestOptions: string implements \App\SelectOptions\DataInterface, \App\SelectOptions\NameInterface
{
use \Pimcore\Model\DataObject\SelectOptions\Traits\EnumGetValuesTrait;
use \Pimcore\Model\DataObject\SelectOptions\Traits\EnumTryFromNullableTrait;
use \App\SelectOptions\DataTrait;
use \App\SelectOptions\NameTrait;

case Ten = 'ten';
case Twenty = '20';
case Check = '√';
case C = '©';
case Multiple_Word_Value = 'Multiple Word Value';

public function getLabel(): string
{
return match ($this) {
self::Ten => '10',
self::Twenty => 'Twenty',
self::Check => '√',
self::C => '©',
self::Multiple_Word_Value => 'Multiple Word Value',
};
}
}
```

### Retrieve available option values

Provided by the `EnumGetValuesTrait`.

```php
TestOptions::getValues();
```

### Map select value to enum

Provided by the `EnumTryFromNullableTrait`.

```php
$value = $product->getSelectField();
$testOption = TestOptions::tryFromNullable($value);
if ($testOption !== null) {
$label = $testOption->getLabel();
}
```
Expand Up @@ -9,7 +9,11 @@ multiselect, values are stored as a comma separated list.

For select and multiselect the options can be defined with a value and display value in the class definition:

![Select Field](../../../img/classes-datatypes-select2.png)
![Select Field](../../../img/classes-datatypes-select-configure.png)

It's also possible to retrieve options from difference sources.
* [Select Options](./77_Select_Options.md)
* [Class / Service](./30_Dynamic_Select_Types.md)

Country and language have fixed option values. For the language field the options can be limited to available system
languages. The country and language select field are also available as multi select fields.
Expand Down
6 changes: 6 additions & 0 deletions doc/05_Objects/01_Object_Classes/01_Data_Types/README.md
Expand Up @@ -42,6 +42,12 @@ The entire list of data types is indicated below:
| countries | combo box with multiple select and predefined country list |
| languages | combo box with multiple select and combo box with multiple select and predefined language |

### [Select Options](./77_Select_Options.md)

| Name | Description |
|----------------|------------------------------------------------|
| select options | Manage select options for (multi)select fields |

### [Dynamic Select Datatypes](./30_Dynamic_Select_Types.md)

| Name | Description |
Expand Down
5 changes: 5 additions & 0 deletions doc/21_Deployment/03_Configuration_Environments.md
Expand Up @@ -91,6 +91,11 @@ pimcore:
type: 'symfony-config'
options:
directory: '/var/www/html/var/config/object_custom_layouts'
select_options:
write_target:
type: 'symfony-config'
options:
directory: '/var/www/html/var/config/select_options'
```

#### Production environment with `symfony-config`
Expand Down
1 change: 1 addition & 0 deletions doc/22_Administration_of_Pimcore/07_Users_and_Roles.md
Expand Up @@ -54,6 +54,7 @@ The following list outlines what the different system permissions (available for
* **Redirects**: User can create and modify redirects
* **Reports**: User has access to reports module
* **Seemode**: Seemode available/not available for user
* **Select Options**: Object [select options editor](../05_Objects/01_Object_Classes/01_Data_Types/77_Select_Options.md)
* **SEO Document Editor**: User has access to SEO document editor
* **System Settings**: User has access to system settings
* **Tag & Snippet Management**: User can create and modify entries in tag & snippet management
Expand Down
Binary file added doc/img/classes-datatypes-select-configure.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed doc/img/classes-datatypes-select2.png
Binary file not shown.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/img/dynamic_select_class.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/img/dynamic_select_service.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed doc/img/dynselect1.png
Binary file not shown.
Binary file removed doc/img/dynselect1a.png
Binary file not shown.
Binary file removed doc/img/dynselect1b.png
Binary file not shown.

0 comments on commit 887b2d5

Please sign in to comment.