From 38778786a291e3f05faf6c47d75afc8009a729ca Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 12 Mar 2024 13:42:21 +0100 Subject: [PATCH 01/26] add executeCallback in DcaUtil to replace v2's getConfigByArrayOrCallbackOrFunction --- src/Util/DcaUtil.php | 51 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/Util/DcaUtil.php b/src/Util/DcaUtil.php index 8c6e900e..5ff71dfa 100644 --- a/src/Util/DcaUtil.php +++ b/src/Util/DcaUtil.php @@ -12,6 +12,8 @@ use Contao\CoreBundle\DataContainer\PaletteNotFoundException; use Contao\CoreBundle\Framework\ContaoFramework; use Contao\StringUtil; +use Error; +use Exception; use HeimrichHannot\UtilsBundle\Util\DcaUtil\GetDcaFieldsOptions; class DcaUtil @@ -186,4 +188,53 @@ public function getDcaFields(string $table, GetDcaFieldsOptions $options = new G return $fields; } + + /** + * Execute a callback with given arguments. + * + * @param array|callable|null $callback The callback can be a callable or an array with the first element being + * the class name and the second element being the method name. + * + * @return mixed|null The retrieved value or null if the callback is not callable or an error occurred. + */ + public function executeCallback(array|callable|null $callback, mixed ...$arguments): mixed + { + if (!$callback) { + return null; + } + + if (is_array($callback)) + { + if (!isset($callback[0]) || !isset($callback[1])) { + return null; + } + + try { + /** @var Controller $controller */ + $controller = $this->contaoFramework->getAdapter(Controller::class); + $instance = $controller->importStatic($callback[0]); + } catch (Exception) { + return null; + } + + if (!method_exists($instance, $callback[1])) { + return null; + } + + $callback = [$instance, $callback[1]]; + } + elseif (!is_callable($callback)) + { + return null; + } + + try + { + return call_user_func_array($callback, $arguments); + } + catch (Error) + { + return null; + } + } } From 37d5ffac24ffb6d30761b75b4f26284ce8caa761 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 12 Mar 2024 14:26:38 +0100 Subject: [PATCH 02/26] add FormatterUtil with formatDcaFieldValue to replace v2's prepareSpecialValueForOutput --- config/services.yml | 1 + src/Util/FormatterUtil.php | 321 +++++++++++++++++++++++++++++++++++++ src/Util/Utils.php | 6 + 3 files changed, 328 insertions(+) create mode 100644 src/Util/FormatterUtil.php diff --git a/config/services.yml b/config/services.yml index 8191054d..1d17908b 100644 --- a/config/services.yml +++ b/config/services.yml @@ -17,6 +17,7 @@ services: bind: $projectDir: '%kernel.project_dir%' $csrfTokenName: '%contao.csrf_token_name%' + $kernelBundles: '%kernel.bundles%' HeimrichHannot\UtilsBundle\Util\ContainerUtil: autowire: true diff --git a/src/Util/FormatterUtil.php b/src/Util/FormatterUtil.php new file mode 100644 index 00000000..c6460b4d --- /dev/null +++ b/src/Util/FormatterUtil.php @@ -0,0 +1,321 @@ +table; + + [$system, $controller] = $this->prepareServices(); + + if (~$settings & self::OPTION_SKIP_DCA_LOADING) { + $controller->loadDataContainer($table); + $system->loadLanguageFile($table); + } + + // dca can be overridden from outside + $data = is_array($dcaOverride) + ? $dcaOverride + : ($GLOBALS['TL_DCA'][$table]['fields'][$field] ?? null); + + if (!is_array($data)) { + return $value; + } + + $inputType = $data['inputType'] ?? null; + + if ($inputType === 'inputUnit') + { + return $this->formatInputUnitField($value, $arrayJoiner); + } + + if ($cachedOptions === null || $settings & self::OPTION_SKIP_OPTION_CACHING) + { + $cachedOptions = $data['options'] ?? $this->utils->dca() + ->executeCallback($data['options_callback'] ?? null, $dc); + } + + if (!is_array($cachedOptions)) { + $cachedOptions = []; + } + + if ($inputType === 'multiColumnEditor' && $this->isMultiColumnsActive() && is_array($value)) + { + return $this->formatMultiColumnField( + $value, + $data, + function (string $f, array|string|null $v) use ( + $dc, + $settings, + $arrayJoiner, + $dcaOverride, + $cachedOptions + ): string { + return $this->formatDcaFieldValue( + $dc, + $f, + $v, + $settings, + $arrayJoiner, + $dcaOverride, + $cachedOptions + ); + } + ); + } + + if (is_array($value)) + { + return $this->formatArray( + $value, + $settings, + $arrayJoiner, + function (array|string|null $v) use ( + $dc, + $field, + $settings, + $arrayJoiner, + $dcaOverride, + $cachedOptions + ): string { + return $this->formatDcaFieldValue( + $dc, + $field, + $v, + $settings | self::OPTION_CALL_IS_RECURSIVE, + $arrayJoiner, + $dcaOverride, + $cachedOptions + ); + } + ); + } + + if ($inputType === 'explanation' && isset($data['eval']['text'])) + { + return $data['eval']['text']; + } + + $rgxp = $data['eval']['rgxp'] ?? null; + + if (!empty($data['foreignKey'])) + { + [$foreignTable, $foreignField] = explode('.', $data['foreignKey']); + + $instance = $this->utils->model()->findModelInstanceByPk($foreignTable, $value); + if (null !== $instance) { + $value = $instance->{$foreignField}; + } + } + + if ($inputType === 'cfgTags' && $tagModel = $this->getTagModel()) + { + $collection = $tagModel->findBy(['source=?', 'id = ?'], [$data['eval']['tagsManager'], $value]); + $value = null; + + if (null !== $collection) { + $result = $collection->fetchEach('name'); + $value = implode($arrayJoiner, $result); + } + } + elseif ($rgxp === 'date') + { + $value = Date::parse(Config::get('dateFormat'), $value); + } + elseif ($rgxp === 'time') + { + $value = Date::parse(Config::get('timeFormat'), $value); + } + elseif ($rgxp === 'datim') + { + $value = Date::parse(Config::get('datimFormat'), $value); + } + elseif (Validator::isBinaryUuid($value)) + { + $strPath = $this->utils->file()->getPathFromUuid($value); + $value = $strPath ? Environment::get('url') . '/' . $strPath : Str::binToUuid($value); + } + elseif ($data['eval']['isBoolean'] ?? + $inputType === 'checkbox' && !($data['eval']['multiple'] ?? false)) + { + $value = ('' != $value) ? $GLOBALS['TL_LANG']['MSC']['yes'] : $GLOBALS['TL_LANG']['MSC']['no']; + } + elseif (is_array($cachedOptions) && !array_is_list($cachedOptions)) + { + $value = $cachedOptions[$value] ?? $value; + } + + if (~$settings & self::OPTION_SKIP_LOCALIZATION + && ($reference = $data['reference'][$value] ?? null)) + { + $value = is_array($reference) + ? $reference[0] ?? $reference[array_key_first($reference)] ?? $value + : $reference; + } + + if ($data['eval']['encrypt'] ?? false) + { + [$encrypted, $iv] = explode('.', $value); + $key = System::getContainer()->getParameter('secret'); + $value = openssl_decrypt($encrypted, 'aes-256-ctr', $key, 0, base64_decode($iv, true)); + } + + if (~$settings & self::OPTION_SKIP_REPLACE_INSERT_TAGS) + { + $value = $this->insertTagParser->replace($value); + } + + return Str::specialchars($value); + } + + /** @return array */ + private function prepareServices(): array + { + /** @var System $system */ + $system = $this->framework->getAdapter(System::class); + + /** @var Controller $controller */ + $controller = $this->framework->getAdapter(Controller::class); + + $system->loadLanguageFile('default'); + + return [$system, $controller]; + } + + private function getTagModel(): ?TagModel + { + if (class_exists(TagModel::class)) { + return $this->framework->getAdapter(TagModel::class); + } + + return null; + } + + private function isMultiColumnsActive(): bool + { + return in_array( + 'HeimrichHannot\MultiColumnEditorBundle\HeimrichHannotContaoMultiColumnEditorBundle', + $this->kernelBundles + ); + } + + private function formatArray(array $values, int $settings, string $arraySeparator, callable $callback): string + { + foreach ($values as $k => $v) + { + $result = $callback($v); + + if ($settings & self::OPTION_PRESERVE_EMPTY_ARRAY_VALUES) + { + $values[$k] = $result; + continue; + } + + if (empty($result)) + { + unset($values[$k]); + } + else + { + $values[$k] = $result; + } + } + + return implode($arraySeparator, $values); + } + + private function formatInputUnitField(array|string|null $values, string $arraySeparator): string + { + $data = Str::deserialize($values, true); + return ($data['value'] ?? '') . $arraySeparator . ($data['unit'] ?? ''); + } + + private function formatMultiColumnField(array $values, array $data, callable $callback = null): string + { + $formatted = ''; + + foreach ($values as $row) { + $formatted .= "\t\n"; + + foreach ($row as $fieldName => $fieldValue) { + $dca = $data['eval']['multiColumnEditor']['fields'][$fieldName]; + + $label = ''; + + if (!$data['eval']['skipMceFieldLabels']) { + $label = ($dca['label'][0] ?: $fieldName).': '; + + if ($data['eval']['skipMceFieldLabelFormatting']) { + $label = $fieldName.': '; + } + } + + $formatted .= "\t" . $label . ($callback ? $callback($fieldName, $fieldValue) : ''); + } + } + + $formatted .= "\t\n"; + + return $formatted; + } +} \ No newline at end of file diff --git a/src/Util/Utils.php b/src/Util/Utils.php index d6fd00f4..aadeb5c7 100644 --- a/src/Util/Utils.php +++ b/src/Util/Utils.php @@ -66,6 +66,11 @@ public function file(): FileUtil return $this->locator->get(FileUtil::class); } + public function formatter(): FormatterUtil + { + return $this->locator->get(FormatterUtil::class); + } + public function html(): HtmlUtil { return $this->locator->get(HtmlUtil::class); @@ -117,6 +122,7 @@ public static function getSubscribedServices(): array DatabaseUtil::class, DcaUtil::class, FileUtil::class, + FormatterUtil::class, HtmlUtil::class, LocaleUtil::class, ModelUtil::class, From 0b9e9895a1f6f465cecb7bd46ddca51592dcd505 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 12 Mar 2024 14:31:53 +0100 Subject: [PATCH 03/26] fix getTagModel return type --- src/Util/FormatterUtil.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Util/FormatterUtil.php b/src/Util/FormatterUtil.php index c6460b4d..27f896cd 100644 --- a/src/Util/FormatterUtil.php +++ b/src/Util/FormatterUtil.php @@ -10,6 +10,7 @@ use Contao\DataContainer; use Contao\Date; use Contao\Environment; +use Contao\Model; use Contao\StringUtil as Str; use Contao\System; use Contao\Validator; @@ -168,7 +169,7 @@ function (array|string|null $v) use ( } } - if ($inputType === 'cfgTags' && $tagModel = $this->getTagModel()) + if ($inputType === 'cfgTags' && ($tagModel = $this->getTagModel())) { $collection = $tagModel->findBy(['source=?', 'id = ?'], [$data['eval']['tagsManager'], $value]); $value = null; @@ -242,7 +243,7 @@ private function prepareServices(): array return [$system, $controller]; } - private function getTagModel(): ?TagModel + private function getTagModel(): ?Model { if (class_exists(TagModel::class)) { return $this->framework->getAdapter(TagModel::class); From 405eee0cec0ca14421e76196eb385efc89b7b313 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 12 Mar 2024 14:35:02 +0100 Subject: [PATCH 04/26] fix PhpStan type hint --- src/Util/FormatterUtil.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Util/FormatterUtil.php b/src/Util/FormatterUtil.php index 27f896cd..4bec241d 100644 --- a/src/Util/FormatterUtil.php +++ b/src/Util/FormatterUtil.php @@ -229,7 +229,7 @@ function (array|string|null $v) use ( return Str::specialchars($value); } - /** @return array */ + /** @return array{System, Controller} */ private function prepareServices(): array { /** @var System $system */ From 66fa8fc7fb2cbeb6bc88d5977a4296af769de0b3 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 12 Mar 2024 17:23:35 +0100 Subject: [PATCH 05/26] change formatDcaFieldValue parameter order --- src/Util/FormatterUtil.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Util/FormatterUtil.php b/src/Util/FormatterUtil.php index 4bec241d..55014a90 100644 --- a/src/Util/FormatterUtil.php +++ b/src/Util/FormatterUtil.php @@ -60,8 +60,8 @@ public function formatDcaFieldValue( string $field, array|string|null $value, int $settings = 0, - string $arrayJoiner = ', ', array $dcaOverride = null, + string $arrayJoiner = ', ', ?array $cachedOptions = null ): mixed { $value = Str::deserialize($value); @@ -108,8 +108,8 @@ public function formatDcaFieldValue( function (string $f, array|string|null $v) use ( $dc, $settings, - $arrayJoiner, $dcaOverride, + $arrayJoiner, $cachedOptions ): string { return $this->formatDcaFieldValue( @@ -117,8 +117,8 @@ function (string $f, array|string|null $v) use ( $f, $v, $settings, - $arrayJoiner, $dcaOverride, + $arrayJoiner, $cachedOptions ); } @@ -135,8 +135,8 @@ function (array|string|null $v) use ( $dc, $field, $settings, - $arrayJoiner, $dcaOverride, + $arrayJoiner, $cachedOptions ): string { return $this->formatDcaFieldValue( @@ -144,8 +144,8 @@ function (array|string|null $v) use ( $field, $v, $settings | self::OPTION_CALL_IS_RECURSIVE, - $arrayJoiner, $dcaOverride, + $arrayJoiner, $cachedOptions ); } From a3e5dbdd58f30c05115de6fc29677367bcde1bb2 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Wed, 13 Mar 2024 10:16:47 +0100 Subject: [PATCH 06/26] added FormatterUtilTest.php --- tests/Util/FormatterUtilTest.php | 60 ++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 tests/Util/FormatterUtilTest.php diff --git a/tests/Util/FormatterUtilTest.php b/tests/Util/FormatterUtilTest.php new file mode 100644 index 00000000..cbb845d3 --- /dev/null +++ b/tests/Util/FormatterUtilTest.php @@ -0,0 +1,60 @@ +mockContaoFramework(); + $parameter['insertTagParser'] ??= $this->createMock(InsertTagParser::class); + $parameter['utils'] ??= $this->createMock(Utils::class); + $parameter['system'] ??= $this->createMock(System::class); + $parameter['kernelBundles'] ??= [ + 'multiColumnsEditor' => 'HeimrichHannot\MultiColumnEditorBundle\HeimrichHannotContaoMultiColumnEditorBundle' + ]; + + return new FormatterUtil( + $parameter['framework'], + $parameter['insertTagParser'], + $parameter['utils'], + $parameter['kernelBundles'] + ); + } + + public function _testFormatDcaFieldValue() + { + $formatterUtil = $this->getInstances(); + + $dataContainer = $this->createMock(DataContainer::class); + $dataContainer->table = 'tl_content'; + + $this->assertEquals( + 'test', + $formatterUtil->formatDcaFieldValue( + $dataContainer, + 'test', + 'test', + dcaOverride: ['inputType' => 'text'] + ) + ); + + $this->assertEquals( + 'foo-bar', + $formatterUtil->formatDcaFieldValue( + $dataContainer, + 'test', + serialize(['value' => 'foo', 'unit' => 'bar']), + dcaOverride: ['inputType' => 'inputUnit'], + arrayJoiner: '-' + ) + ); + } +} \ No newline at end of file From 2b5dfb7fac6db21741a9818e39a1e56ccc6abd25 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Wed, 13 Mar 2024 15:56:28 +0100 Subject: [PATCH 07/26] wip FormatterUtilTest.php --- tests/Util/FormatterUtilTest.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/Util/FormatterUtilTest.php b/tests/Util/FormatterUtilTest.php index cbb845d3..5e22e0d2 100644 --- a/tests/Util/FormatterUtilTest.php +++ b/tests/Util/FormatterUtilTest.php @@ -2,6 +2,7 @@ namespace Util; +use Contao\Controller; use Contao\CoreBundle\InsertTag\InsertTagParser; use Contao\DataContainer; use Contao\System; @@ -13,7 +14,10 @@ class FormatterUtilTest extends ContaoTestCase { public function getInstances(array $parameter = []): FormatterUtil { - $parameter['framework'] ??= $this->mockContaoFramework(); + $parameter['framework'] ??= $this->mockContaoFramework([ + System::class => $this->createMock(System::class), + Controller::class => $this->createMock(Controller::class) + ]); $parameter['insertTagParser'] ??= $this->createMock(InsertTagParser::class); $parameter['utils'] ??= $this->createMock(Utils::class); $parameter['system'] ??= $this->createMock(System::class); From fa7c812db29dfbe6625619cddac9d39ac77edab6 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 19 Mar 2024 09:58:25 +0100 Subject: [PATCH 08/26] improved FormatterUtil callback structure and docs --- src/Util/FormatterUtil.php | 76 +++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/src/Util/FormatterUtil.php b/src/Util/FormatterUtil.php index 55014a90..6d2cac2d 100644 --- a/src/Util/FormatterUtil.php +++ b/src/Util/FormatterUtil.php @@ -42,7 +42,8 @@ public function __construct( * * @param DataContainer $dc The data container whose table to use and options-callback to evaluate. * @param string $field The DCA field name. - * @param array|string|null $value The value to format. If an array is passed, the values will be evaluated recursively. + * @param array|string|null $value The value to format. If an array is passed, the values will be evaluated + * recursively. * @param int $settings Additional settings flags. * @param string $arrayJoiner The string that joins array values. Default: ', '. * @param array|null $dcaOverride Override the DCA field settings. If not set, the DCA field settings will be used. @@ -102,54 +103,49 @@ public function formatDcaFieldValue( if ($inputType === 'multiColumnEditor' && $this->isMultiColumnsActive() && is_array($value)) { - return $this->formatMultiColumnField( - $value, - $data, - function (string $f, array|string|null $v) use ( + $callback = function (int|string $f, array|string|null $v) use ( + $dc, + $settings, + $dcaOverride, + $arrayJoiner, + $cachedOptions + ): string { + return $this->formatDcaFieldValue( $dc, + $f, + $v, $settings, $dcaOverride, $arrayJoiner, $cachedOptions - ): string { - return $this->formatDcaFieldValue( - $dc, - $f, - $v, - $settings, - $dcaOverride, - $arrayJoiner, - $cachedOptions - ); - } - ); + ); + }; + + return $this->formatMultiColumnField($value, $data, $callback); } if (is_array($value)) { - return $this->formatArray( - $value, + $callback = function (array|string|null $v) use ( + $dc, + $field, $settings, + $dcaOverride, $arrayJoiner, - function (array|string|null $v) use ( + $cachedOptions + ): string { + return $this->formatDcaFieldValue( $dc, $field, - $settings, + $v, + $settings | self::OPTION_CALL_IS_RECURSIVE, $dcaOverride, $arrayJoiner, $cachedOptions - ): string { - return $this->formatDcaFieldValue( - $dc, - $field, - $v, - $settings | self::OPTION_CALL_IS_RECURSIVE, - $dcaOverride, - $arrayJoiner, - $cachedOptions - ); - } - ); + ); + }; + + return $this->formatArray($value, $settings, $arrayJoiner, $callback); } if ($inputType === 'explanation' && isset($data['eval']['text'])) @@ -260,6 +256,13 @@ private function isMultiColumnsActive(): bool ); } + /** + * @param array $values + * @param int $settings + * @param string $arraySeparator + * @param callable(array|string|null $value): string $callback The callback to format each value, possibly recursively. + * @return string + */ private function formatArray(array $values, int $settings, string $arraySeparator, callable $callback): string { foreach ($values as $k => $v) @@ -291,6 +294,13 @@ private function formatInputUnitField(array|string|null $values, string $arraySe return ($data['value'] ?? '') . $arraySeparator . ($data['unit'] ?? ''); } + /** + * @param array $values + * @param array $data + * @param ?callable(int|string $field, array|string|null $value): string $callback + * Callback used to format each field value, possibly recursively. + * @return string + */ private function formatMultiColumnField(array $values, array $data, callable $callback = null): string { $formatted = ''; From a3e2ecbf0b379340d4335f32584b06bf37ada815 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Mon, 25 Mar 2024 16:44:17 +0100 Subject: [PATCH 09/26] add options factory and formatter options --- src/Options/OptionsFactory.php | 79 +++++++++++++++++++ src/Util/FormatterUtil.php | 52 ++++++------ .../FormatDcaFieldValueOptions.php | 21 +++++ 3 files changed, 123 insertions(+), 29 deletions(-) create mode 100644 src/Options/OptionsFactory.php create mode 100644 src/Util/FormatterUtil/FormatDcaFieldValueOptions.php diff --git a/src/Options/OptionsFactory.php b/src/Options/OptionsFactory.php new file mode 100644 index 00000000..8ad9c04d --- /dev/null +++ b/src/Options/OptionsFactory.php @@ -0,0 +1,79 @@ +$key ?? $this->options[$key] ?? $default; + } + + public function set(string $key, mixed $value): static + { + if (property_exists($this, $key)) { + $this->$key = $value; + } + $this->options[$key] = $value; + return $this; + } + + public function has(string $key): bool + { + return isset($this->$key) || isset($this->options[$key]); + } + + public function del(string $key): static + { + unset($this->options[$key]); + return $this; + } + + protected function methodNameToOptionKey(string $name): string + { + return lcfirst(substr($name, 3)); + } + + public function __call(string $name, array $arguments): mixed + { + if (strlen($name) < 4) { + throw new BadMethodCallException(sprintf('Method %s does not exist', $name)); + } + + $numArgs = count($arguments); + if ($numArgs < 1 || $numArgs > 1) { + throw new BadMethodCallException(sprintf('Method %s expects exactly one argument', $name)); + } + + $key = static::methodNameToOptionKey($name); + $prefix = substr($name, 0, 3); + + return match ($prefix) + { + 'set' => $this->set($key, $arguments[0]), + 'get' => $this->get($key, $arguments[0] ?? null), + 'has' => $this->has($key), + 'del' => $this->del($key), + default => throw new BadMethodCallException(sprintf('Method %s does not exist', $name)), + }; + } + + public function __get(string $name) + { + return $this->get($name); + } + + public function __set(string $name, mixed $value) + { + $this->set($name, $value); + } + + public static function create(): static + { + return new static(); + } +} \ No newline at end of file diff --git a/src/Util/FormatterUtil.php b/src/Util/FormatterUtil.php index 6d2cac2d..ddf884ae 100644 --- a/src/Util/FormatterUtil.php +++ b/src/Util/FormatterUtil.php @@ -14,6 +14,10 @@ use Contao\StringUtil as Str; use Contao\System; use Contao\Validator; +use HeimrichHannot\UtilsBundle\Options\OptionsFactory; +use HeimrichHannot\UtilsBundle\Util\FormatterUtil\FormatDcaFieldValueOptions; + +$cfgt = (new FormatDcaFieldValueOptions())->setPreserveEmptyArrayValues(true); class FormatterUtil { @@ -24,17 +28,6 @@ public function __construct( protected array $kernelBundles ) {} - const OPTION_PRESERVE_EMPTY_ARRAY_VALUES = 2^0; - const OPTION_SKIP_LOCALIZATION = 2^1; - const OPTION_SKIP_DCA_LOADING = 2^2; - const OPTION_SKIP_OPTION_CACHING = 2^3; - const OPTION_SKIP_REPLACE_INSERT_TAGS = 2^4; - /** - * @var FormatterUtil::OPTION_CALL_IS_RECURSIVE Whether the current call is recursive. - * @internal You should not set this option manually. - */ - const OPTION_CALL_IS_RECURSIVE = 2^5; - /** * Makes a DCA field value human-readable. * @@ -44,33 +37,29 @@ public function __construct( * @param string $field The DCA field name. * @param array|string|null $value The value to format. If an array is passed, the values will be evaluated * recursively. - * @param int $settings Additional settings flags. + * @param ?FormatDcaFieldValueOptions $settings Additional settings flags. * @param string $arrayJoiner The string that joins array values. Default: ', '. * @param array|null $dcaOverride Override the DCA field settings. If not set, the DCA field settings will be used. * @param array|null $cachedOptions The cached options to use. If not set, the options-callback will be evaluated. * @return mixed The formatted value. - * - * @see FormatterUtil::OPTION_PRESERVE_EMPTY_ARRAY_VALUES Preserve empty array values. - * @see FormatterUtil::OPTION_SKIP_LOCALIZATION Skip localization. - * @see FormatterUtil::OPTION_SKIP_DCA_LOADING Skip DCA loading. - * @see FormatterUtil::OPTION_SKIP_OPTION_CACHING Skip option caching. - * @see FormatterUtil::OPTION_SKIP_REPLACE_INSERT_TAGS Skip replace insert tags. */ public function formatDcaFieldValue( DataContainer $dc, string $field, array|string|null $value, - int $settings = 0, + OptionsFactory $settings = null, array $dcaOverride = null, string $arrayJoiner = ', ', ?array $cachedOptions = null ): mixed { + $settings ??= new FormatDcaFieldValueOptions(); + $value = Str::deserialize($value); $table = $dc->table; [$system, $controller] = $this->prepareServices(); - if (~$settings & self::OPTION_SKIP_DCA_LOADING) { + if ($settings->loadDca) { $controller->loadDataContainer($table); $system->loadLanguageFile($table); } @@ -91,7 +80,7 @@ public function formatDcaFieldValue( return $this->formatInputUnitField($value, $arrayJoiner); } - if ($cachedOptions === null || $settings & self::OPTION_SKIP_OPTION_CACHING) + if ($cachedOptions === null || !$settings->cacheOptions) { $cachedOptions = $data['options'] ?? $this->utils->dca() ->executeCallback($data['options_callback'] ?? null, $dc); @@ -138,7 +127,7 @@ public function formatDcaFieldValue( $dc, $field, $v, - $settings | self::OPTION_CALL_IS_RECURSIVE, + $settings, $dcaOverride, $arrayJoiner, $cachedOptions @@ -202,8 +191,7 @@ public function formatDcaFieldValue( $value = $cachedOptions[$value] ?? $value; } - if (~$settings & self::OPTION_SKIP_LOCALIZATION - && ($reference = $data['reference'][$value] ?? null)) + if ($settings->localize && ($reference = $data['reference'][$value] ?? null)) { $value = is_array($reference) ? $reference[0] ?? $reference[array_key_first($reference)] ?? $value @@ -217,7 +205,7 @@ public function formatDcaFieldValue( $value = openssl_decrypt($encrypted, 'aes-256-ctr', $key, 0, base64_decode($iv, true)); } - if (~$settings & self::OPTION_SKIP_REPLACE_INSERT_TAGS) + if ($settings->replaceInsertTags) { $value = $this->insertTagParser->replace($value); } @@ -258,18 +246,24 @@ private function isMultiColumnsActive(): bool /** * @param array $values - * @param int $settings + * @param FormatDcaFieldValueOptions $settings * @param string $arraySeparator - * @param callable(array|string|null $value): string $callback The callback to format each value, possibly recursively. + * @param callable(array|string|null $value): string $callback The callback to format each value, possibly + * recursively. * @return string */ - private function formatArray(array $values, int $settings, string $arraySeparator, callable $callback): string + private function formatArray( + array $values, + OptionsFactory $settings, + string $arraySeparator, + callable $callback + ): string { foreach ($values as $k => $v) { $result = $callback($v); - if ($settings & self::OPTION_PRESERVE_EMPTY_ARRAY_VALUES) + if ($settings->preserveEmptyArrayValues) { $values[$k] = $result; continue; diff --git a/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php b/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php new file mode 100644 index 00000000..8edc5fe9 --- /dev/null +++ b/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php @@ -0,0 +1,21 @@ + Date: Mon, 25 Mar 2024 16:46:53 +0100 Subject: [PATCH 10/26] patch out test line --- src/Util/FormatterUtil.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Util/FormatterUtil.php b/src/Util/FormatterUtil.php index ddf884ae..f5662cc8 100644 --- a/src/Util/FormatterUtil.php +++ b/src/Util/FormatterUtil.php @@ -17,8 +17,6 @@ use HeimrichHannot\UtilsBundle\Options\OptionsFactory; use HeimrichHannot\UtilsBundle\Util\FormatterUtil\FormatDcaFieldValueOptions; -$cfgt = (new FormatDcaFieldValueOptions())->setPreserveEmptyArrayValues(true); - class FormatterUtil { public function __construct( From 0ffce58b5944c8c5f64e890e2761a6c287859931 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Mon, 25 Mar 2024 17:10:35 +0100 Subject: [PATCH 11/26] add DcaUtilTest->testExecuteCallback() --- src/Options/OptionsFactory.php | 1 + tests/Util/Dca/DcaUtilTest.php | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/Options/OptionsFactory.php b/src/Options/OptionsFactory.php index 8ad9c04d..bab869ef 100644 --- a/src/Options/OptionsFactory.php +++ b/src/Options/OptionsFactory.php @@ -29,6 +29,7 @@ public function has(string $key): bool public function del(string $key): static { + unset($this->$key); unset($this->options[$key]); return $this; } diff --git a/tests/Util/Dca/DcaUtilTest.php b/tests/Util/Dca/DcaUtilTest.php index f804e23f..ac045c04 100644 --- a/tests/Util/Dca/DcaUtilTest.php +++ b/tests/Util/Dca/DcaUtilTest.php @@ -136,4 +136,21 @@ public function testGetDcaFields() 'title', ], $fields); } + + public function testExecuteCallback() + { + $instance = $this->getTestInstance(); + + $this->assertSame('ham', $instance->executeCallback(function () { + return 'ham'; + })); + + $this->assertSame('spam', $instance->executeCallback(function ($value) { + return $value; + }, ['spam'])); + + $this->assertSame('spam_ham', $instance->executeCallback( + [\HeimrichHannot\UtilsBundle\Util\StringUtil::class, 'camelCaseToSnake'], ['spamHam']) + ); + } } From a0d3cfd72314e35fd216a8d903f9a3138fd7fa45 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Mon, 25 Mar 2024 17:15:20 +0100 Subject: [PATCH 12/26] improve DcaUtilTest --- tests/Util/Dca/DcaUtilTest.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/Util/Dca/DcaUtilTest.php b/tests/Util/Dca/DcaUtilTest.php index ac045c04..f5c11021 100644 --- a/tests/Util/Dca/DcaUtilTest.php +++ b/tests/Util/Dca/DcaUtilTest.php @@ -9,6 +9,7 @@ namespace HeimrichHannot\UtilsBundle\Tests\Util\Dca; use Contao\Controller; +use Exception; use HeimrichHannot\UtilsBundle\Tests\AbstractUtilsTestCase; use HeimrichHannot\UtilsBundle\Util\DcaUtil; use HeimrichHannot\UtilsBundle\Util\DcaUtil\GetDcaFieldsOptions; @@ -152,5 +153,22 @@ public function testExecuteCallback() $this->assertSame('spam_ham', $instance->executeCallback( [\HeimrichHannot\UtilsBundle\Util\StringUtil::class, 'camelCaseToSnake'], ['spamHam']) ); + + $this->assertNull($instance->executeCallback(null)); + $this->assertNull($instance->executeCallback([static::class, 'thisIsNotCallable'])); + $this->assertNull($instance->executeCallback(['\This\Is\Unheard\Of', 'notCallable'])); + $this->assertNull($instance->executeCallback(['toFewArguments'])); + + try { + $instance->executeCallback([static::class, 'thisThrowsAnError']); + $this->fail('An exception should have been thrown'); + } catch (Exception $e) { + $this->assertSame('I was thrown on purpose', $e->getMessage()); + } + } + + public function thisThrowsAnError() + { + throw new Exception('I was thrown on purpose'); } } From 40be021514f88ab17f50fb72cf0944693e96b516 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Mon, 25 Mar 2024 17:17:23 +0100 Subject: [PATCH 13/26] fix DcaUtilTest --- tests/Util/Dca/DcaUtilTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Util/Dca/DcaUtilTest.php b/tests/Util/Dca/DcaUtilTest.php index f5c11021..bab20132 100644 --- a/tests/Util/Dca/DcaUtilTest.php +++ b/tests/Util/Dca/DcaUtilTest.php @@ -148,10 +148,10 @@ public function testExecuteCallback() $this->assertSame('spam', $instance->executeCallback(function ($value) { return $value; - }, ['spam'])); + }, 'spam')); $this->assertSame('spam_ham', $instance->executeCallback( - [\HeimrichHannot\UtilsBundle\Util\StringUtil::class, 'camelCaseToSnake'], ['spamHam']) + [\HeimrichHannot\UtilsBundle\Util\StringUtil::class, 'camelCaseToSnake'], 'spamHam') ); $this->assertNull($instance->executeCallback(null)); From ab18c1f65a35bafa638d75d0e463793adf68b0c8 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Mon, 25 Mar 2024 17:39:12 +0100 Subject: [PATCH 14/26] fix DcaUtilTest --- tests/Util/Dca/DcaUtilTest.php | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/tests/Util/Dca/DcaUtilTest.php b/tests/Util/Dca/DcaUtilTest.php index bab20132..d87ffbf6 100644 --- a/tests/Util/Dca/DcaUtilTest.php +++ b/tests/Util/Dca/DcaUtilTest.php @@ -140,24 +140,28 @@ public function testGetDcaFields() public function testExecuteCallback() { - $instance = $this->getTestInstance(); + $controllerAdapter = $this->mockAdapter(['importStatic']); + $controllerAdapter->method('importStatic')->willReturn($this); - $this->assertSame('ham', $instance->executeCallback(function () { - return 'ham'; - })); + $contaoFramework = $this->mockContaoFramework([ + Controller::class => $controllerAdapter, + ]); + + $instance = $this->getTestInstance([ + 'contaoFramework' => $contaoFramework, + ]); + $this->assertSame('ham', $instance->executeCallback(function () { return 'ham'; })); $this->assertSame('spam', $instance->executeCallback(function ($value) { return $value; }, 'spam')); - $this->assertSame('spam_ham', $instance->executeCallback( - [\HeimrichHannot\UtilsBundle\Util\StringUtil::class, 'camelCaseToSnake'], 'spamHam') - ); + $this->assertSame('ham', $instance->executeCallback([static::class, 'thisReturnsHam'])); $this->assertNull($instance->executeCallback(null)); + $this->assertNull($instance->executeCallback(['toFewArguments'])); $this->assertNull($instance->executeCallback([static::class, 'thisIsNotCallable'])); $this->assertNull($instance->executeCallback(['\This\Is\Unheard\Of', 'notCallable'])); - $this->assertNull($instance->executeCallback(['toFewArguments'])); try { $instance->executeCallback([static::class, 'thisThrowsAnError']); @@ -167,8 +171,16 @@ public function testExecuteCallback() } } - public function thisThrowsAnError() + /** + * @throws Exception + */ + public function thisThrowsAnError(): void { throw new Exception('I was thrown on purpose'); } + + public function thisReturnsHam(): string + { + return 'ham'; + } } From b683f886ad7f2d0c763ba6d95babd6350469fa01 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Mon, 25 Mar 2024 17:49:21 +0100 Subject: [PATCH 15/26] add OptionsFactoryTest.php --- tests/Options/OptionsFactoryTest.php | 59 ++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 tests/Options/OptionsFactoryTest.php diff --git a/tests/Options/OptionsFactoryTest.php b/tests/Options/OptionsFactoryTest.php new file mode 100644 index 00000000..ba7f0473 --- /dev/null +++ b/tests/Options/OptionsFactoryTest.php @@ -0,0 +1,59 @@ +set('foo', 'foo'); + $options->bar = 'bar'; + + $this->assertTrue($options->has('foo')); + $this->assertTrue($options->has('bar')); + $this->assertFalse($options->has('baz')); + + $this->assertSame('foo', $options->get('foo')); + $this->assertSame('bar', $options->get('bar')); + $this->assertNull($options->get('baz')); + $this->assertSame('foobar', $options->get('baz', 'foobar')); + $this->assertSame('foo', $options->foo); + $this->assertSame('bar', $options->bar); + $this->assertNull($options->baz); + + $options->foo = 'bar'; + $options->del('bar'); + + $this->assertSame('bar', $options->foo); + $this->assertFalse($options->has('bar')); + } + + public function testOptionsFactoryInheritance() + { + $options = new class extends OptionsFactory { + public string $foo = 'foo'; + }; + + $this->assertTrue($options->has('foo')); + $this->assertSame('foo', $options->foo); + $this->assertSame('foo', $options->get('foo')); + + $options->set('foo', 'baz'); + $options->set('bar', 'bar'); + + $this->assertTrue($options->has('foo')); + $this->assertTrue($options->has('bar')); + $this->assertSame('baz', $options->foo); + $this->assertSame('bar', $options->bar); + + $options->del('foo'); + $options->del('bar'); + + $this->assertFalse($options->has('foo')); + $this->assertFalse($options->has('bar')); + } +} \ No newline at end of file From bcc437da096969d5bf07c6acbf6aeaf8adcfff39 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Mon, 25 Mar 2024 18:25:54 +0100 Subject: [PATCH 16/26] rename OptionsFactory to OptionsManager --- src/Options/{OptionsFactory.php => OptionsManager.php} | 2 +- src/Util/FormatterUtil.php | 6 +++--- src/Util/FormatterUtil/FormatDcaFieldValueOptions.php | 4 ++-- .../{OptionsFactoryTest.php => OptionsManagerTest.php} | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) rename src/Options/{OptionsFactory.php => OptionsManager.php} (98%) rename tests/Options/{OptionsFactoryTest.php => OptionsManagerTest.php} (91%) diff --git a/src/Options/OptionsFactory.php b/src/Options/OptionsManager.php similarity index 98% rename from src/Options/OptionsFactory.php rename to src/Options/OptionsManager.php index bab869ef..46ee6518 100644 --- a/src/Options/OptionsFactory.php +++ b/src/Options/OptionsManager.php @@ -4,7 +4,7 @@ use BadMethodCallException; -class OptionsFactory +class OptionsManager { protected array $options = []; diff --git a/src/Util/FormatterUtil.php b/src/Util/FormatterUtil.php index f5662cc8..e9f6836b 100644 --- a/src/Util/FormatterUtil.php +++ b/src/Util/FormatterUtil.php @@ -14,7 +14,7 @@ use Contao\StringUtil as Str; use Contao\System; use Contao\Validator; -use HeimrichHannot\UtilsBundle\Options\OptionsFactory; +use HeimrichHannot\UtilsBundle\Options\OptionsManager; use HeimrichHannot\UtilsBundle\Util\FormatterUtil\FormatDcaFieldValueOptions; class FormatterUtil @@ -45,7 +45,7 @@ public function formatDcaFieldValue( DataContainer $dc, string $field, array|string|null $value, - OptionsFactory $settings = null, + OptionsManager $settings = null, array $dcaOverride = null, string $arrayJoiner = ', ', ?array $cachedOptions = null @@ -252,7 +252,7 @@ private function isMultiColumnsActive(): bool */ private function formatArray( array $values, - OptionsFactory $settings, + OptionsManager $settings, string $arraySeparator, callable $callback ): string diff --git a/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php b/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php index 8edc5fe9..5102fd38 100644 --- a/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php +++ b/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php @@ -2,7 +2,7 @@ namespace HeimrichHannot\UtilsBundle\Util\FormatterUtil; -use HeimrichHannot\UtilsBundle\Options\OptionsFactory; +use HeimrichHannot\UtilsBundle\Options\OptionsManager; /** * @method $this setPreserveEmptyArrayValues(bool $value) @@ -11,7 +11,7 @@ * @method $this setCacheOptions(bool $value) * @method $this setReplaceInsertTags(bool $value) */ -class FormatDcaFieldValueOptions extends OptionsFactory +class FormatDcaFieldValueOptions extends OptionsManager { public bool $preserveEmptyArrayValues = false; public bool $localize = true; diff --git a/tests/Options/OptionsFactoryTest.php b/tests/Options/OptionsManagerTest.php similarity index 91% rename from tests/Options/OptionsFactoryTest.php rename to tests/Options/OptionsManagerTest.php index ba7f0473..e37b616b 100644 --- a/tests/Options/OptionsFactoryTest.php +++ b/tests/Options/OptionsManagerTest.php @@ -3,13 +3,13 @@ namespace Options; use Contao\TestCase\ContaoTestCase; -use HeimrichHannot\UtilsBundle\Options\OptionsFactory; +use HeimrichHannot\UtilsBundle\Options\OptionsManager; class OptionsFactoryTest extends ContaoTestCase { public function testOptionsFactory() { - $options = new OptionsFactory(); + $options = new OptionsManager(); $options->set('foo', 'foo'); $options->bar = 'bar'; @@ -34,7 +34,7 @@ public function testOptionsFactory() public function testOptionsFactoryInheritance() { - $options = new class extends OptionsFactory { + $options = new class extends OptionsManager { public string $foo = 'foo'; }; From 5847cd3183b215eb14d002975dfd452230dad3d6 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 26 Mar 2024 10:31:57 +0100 Subject: [PATCH 17/26] change FormatDcaFieldValueOptions --- src/Options/OptionsManager.php | 80 ----------------- src/Util/FormatterUtil.php | 90 ++++++------------- .../FormatDcaFieldValueOptions.php | 73 ++++++++++++--- 3 files changed, 89 insertions(+), 154 deletions(-) delete mode 100644 src/Options/OptionsManager.php diff --git a/src/Options/OptionsManager.php b/src/Options/OptionsManager.php deleted file mode 100644 index 46ee6518..00000000 --- a/src/Options/OptionsManager.php +++ /dev/null @@ -1,80 +0,0 @@ -$key ?? $this->options[$key] ?? $default; - } - - public function set(string $key, mixed $value): static - { - if (property_exists($this, $key)) { - $this->$key = $value; - } - $this->options[$key] = $value; - return $this; - } - - public function has(string $key): bool - { - return isset($this->$key) || isset($this->options[$key]); - } - - public function del(string $key): static - { - unset($this->$key); - unset($this->options[$key]); - return $this; - } - - protected function methodNameToOptionKey(string $name): string - { - return lcfirst(substr($name, 3)); - } - - public function __call(string $name, array $arguments): mixed - { - if (strlen($name) < 4) { - throw new BadMethodCallException(sprintf('Method %s does not exist', $name)); - } - - $numArgs = count($arguments); - if ($numArgs < 1 || $numArgs > 1) { - throw new BadMethodCallException(sprintf('Method %s expects exactly one argument', $name)); - } - - $key = static::methodNameToOptionKey($name); - $prefix = substr($name, 0, 3); - - return match ($prefix) - { - 'set' => $this->set($key, $arguments[0]), - 'get' => $this->get($key, $arguments[0] ?? null), - 'has' => $this->has($key), - 'del' => $this->del($key), - default => throw new BadMethodCallException(sprintf('Method %s does not exist', $name)), - }; - } - - public function __get(string $name) - { - return $this->get($name); - } - - public function __set(string $name, mixed $value) - { - $this->set($name, $value); - } - - public static function create(): static - { - return new static(); - } -} \ No newline at end of file diff --git a/src/Util/FormatterUtil.php b/src/Util/FormatterUtil.php index e9f6836b..3a9714f6 100644 --- a/src/Util/FormatterUtil.php +++ b/src/Util/FormatterUtil.php @@ -14,7 +14,6 @@ use Contao\StringUtil as Str; use Contao\System; use Contao\Validator; -use HeimrichHannot\UtilsBundle\Options\OptionsManager; use HeimrichHannot\UtilsBundle\Util\FormatterUtil\FormatDcaFieldValueOptions; class FormatterUtil @@ -29,26 +28,20 @@ public function __construct( /** * Makes a DCA field value human-readable. * - * {@see https://github.com/heimrichhannot/contao-utils-bundle/blob/ee122d2e267a60aa3200ce0f40d92c22028988e8/src/Form/FormUtil.php#L99 This succeeds `prepareSpecialValueForOutput(...)` from Utils v2.} + * This succeeds {@see https://github.com/heimrichhannot/contao-utils-bundle/blob/ee122d2e267a60aa3200ce0f40d92c22028988e8/src/Form/FormUtil.php#L99 `prepareSpecialValueForOutput(...)`} from Utils v2. * * @param DataContainer $dc The data container whose table to use and options-callback to evaluate. * @param string $field The DCA field name. * @param array|string|null $value The value to format. If an array is passed, the values will be evaluated * recursively. - * @param ?FormatDcaFieldValueOptions $settings Additional settings flags. - * @param string $arrayJoiner The string that joins array values. Default: ', '. - * @param array|null $dcaOverride Override the DCA field settings. If not set, the DCA field settings will be used. - * @param array|null $cachedOptions The cached options to use. If not set, the options-callback will be evaluated. + * @param ?FormatDcaFieldValueOptions $settings Additional settings. * @return mixed The formatted value. */ public function formatDcaFieldValue( - DataContainer $dc, - string $field, - array|string|null $value, - OptionsManager $settings = null, - array $dcaOverride = null, - string $arrayJoiner = ', ', - ?array $cachedOptions = null + DataContainer $dc, + string $field, + array|string|null $value, + FormatDcaFieldValueOptions $settings = null ): mixed { $settings ??= new FormatDcaFieldValueOptions(); @@ -63,8 +56,8 @@ public function formatDcaFieldValue( } // dca can be overridden from outside - $data = is_array($dcaOverride) - ? $dcaOverride + $data = is_array($settings->dcaOverride) + ? $settings->dcaOverride : ($GLOBALS['TL_DCA'][$table]['fields'][$field] ?? null); if (!is_array($data)) { @@ -75,37 +68,23 @@ public function formatDcaFieldValue( if ($inputType === 'inputUnit') { - return $this->formatInputUnitField($value, $arrayJoiner); + return $this->formatInputUnitField($value, $settings->arrayJoiner); } - if ($cachedOptions === null || !$settings->cacheOptions) + if ($settings->optionsCache === null || !$settings->cacheOptions) { - $cachedOptions = $data['options'] ?? $this->utils->dca() + $settings->optionsCache = $data['options'] ?? $this->utils->dca() ->executeCallback($data['options_callback'] ?? null, $dc); } - if (!is_array($cachedOptions)) { - $cachedOptions = []; + if (!is_array($settings->optionsCache)) { + $settings->optionsCache = []; } if ($inputType === 'multiColumnEditor' && $this->isMultiColumnsActive() && is_array($value)) { - $callback = function (int|string $f, array|string|null $v) use ( - $dc, - $settings, - $dcaOverride, - $arrayJoiner, - $cachedOptions - ): string { - return $this->formatDcaFieldValue( - $dc, - $f, - $v, - $settings, - $dcaOverride, - $arrayJoiner, - $cachedOptions - ); + $callback = function (int|string $f, array|string|null $v) use ($dc, $settings): string { + return $this->formatDcaFieldValue($dc, $f, $v, $settings); }; return $this->formatMultiColumnField($value, $data, $callback); @@ -113,26 +92,11 @@ public function formatDcaFieldValue( if (is_array($value)) { - $callback = function (array|string|null $v) use ( - $dc, - $field, - $settings, - $dcaOverride, - $arrayJoiner, - $cachedOptions - ): string { - return $this->formatDcaFieldValue( - $dc, - $field, - $v, - $settings, - $dcaOverride, - $arrayJoiner, - $cachedOptions - ); + $callback = function (array|string|null $v) use ($dc, $field, $settings): string { + return $this->formatDcaFieldValue($dc, $field, $v, $settings); }; - return $this->formatArray($value, $settings, $arrayJoiner, $callback); + return $this->formatArray($value, $settings, $callback); } if ($inputType === 'explanation' && isset($data['eval']['text'])) @@ -159,7 +123,7 @@ public function formatDcaFieldValue( if (null !== $collection) { $result = $collection->fetchEach('name'); - $value = implode($arrayJoiner, $result); + $value = implode($settings->arrayJoiner, $result); } } elseif ($rgxp === 'date') @@ -184,9 +148,9 @@ public function formatDcaFieldValue( { $value = ('' != $value) ? $GLOBALS['TL_LANG']['MSC']['yes'] : $GLOBALS['TL_LANG']['MSC']['no']; } - elseif (is_array($cachedOptions) && !array_is_list($cachedOptions)) + elseif (is_array($settings->optionsCache) && !array_is_list($settings->optionsCache)) { - $value = $cachedOptions[$value] ?? $value; + $value = $settings->optionsCache[$value] ?? $value; } if ($settings->localize && ($reference = $data['reference'][$value] ?? null)) @@ -251,12 +215,10 @@ private function isMultiColumnsActive(): bool * @return string */ private function formatArray( - array $values, - OptionsManager $settings, - string $arraySeparator, - callable $callback - ): string - { + array $values, + FormatDcaFieldValueOptions $settings, + callable $callback + ): string { foreach ($values as $k => $v) { $result = $callback($v); @@ -277,7 +239,7 @@ private function formatArray( } } - return implode($arraySeparator, $values); + return implode($settings->arrayJoiner, $values); } private function formatInputUnitField(array|string|null $values, string $arraySeparator): string diff --git a/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php b/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php index 5102fd38..d0ba67f0 100644 --- a/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php +++ b/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php @@ -2,20 +2,73 @@ namespace HeimrichHannot\UtilsBundle\Util\FormatterUtil; -use HeimrichHannot\UtilsBundle\Options\OptionsManager; - -/** - * @method $this setPreserveEmptyArrayValues(bool $value) - * @method $this setLocalize(bool $value) - * @method $this setLoadDca(bool $value) - * @method $this setCacheOptions(bool $value) - * @method $this setReplaceInsertTags(bool $value) - */ -class FormatDcaFieldValueOptions extends OptionsManager +class FormatDcaFieldValueOptions { public bool $preserveEmptyArrayValues = false; public bool $localize = true; public bool $loadDca = true; public bool $cacheOptions = true; public bool $replaceInsertTags = true; + public ?array $dcaOverride = null; + public string $arrayJoiner = ', '; + /** @internal Used for caching options. */ + public ?array $optionsCache = null; + + public static function create(): FormatDcaFieldValueOptions + { + return new static(); + } + + public function setPreserveEmptyArrayValues(bool $preserveEmptyArrayValues): FormatDcaFieldValueOptions + { + $this->preserveEmptyArrayValues = $preserveEmptyArrayValues; + return $this; + } + + public function setLocalize(bool $localize): FormatDcaFieldValueOptions + { + $this->localize = $localize; + return $this; + } + + public function setLoadDca(bool $loadDca): FormatDcaFieldValueOptions + { + $this->loadDca = $loadDca; + return $this; + } + + public function setCacheOptions(bool $cacheOptions): FormatDcaFieldValueOptions + { + $this->cacheOptions = $cacheOptions; + return $this; + } + + public function setReplaceInsertTags(bool $replaceInsertTags): FormatDcaFieldValueOptions + { + $this->replaceInsertTags = $replaceInsertTags; + return $this; + } + + /** + * Override the DCA field settings. If not set, the DCA field settings will be used. + * @param array|null $dcaOverride + * @return $this + */ + public function setDcaOverride(?array $dcaOverride): FormatDcaFieldValueOptions + { + $this->dcaOverride = $dcaOverride; + return $this; + } + + /** + * The string that joins array values. Default: ', '. + * @param string $arrayJoiner + * @return $this + */ + public function setArrayJoiner(string $arrayJoiner): FormatDcaFieldValueOptions + { + $this->arrayJoiner = $arrayJoiner; + return $this; + } + } \ No newline at end of file From 57f1af7ed2b7e1f20b5d8eff9ea840800852abf2 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 26 Mar 2024 11:07:32 +0100 Subject: [PATCH 18/26] fix FormatterUtilTest --- src/Util/FormatterUtil.php | 19 ++------- tests/Options/OptionsManagerTest.php | 59 ---------------------------- tests/Util/FormatterUtilTest.php | 32 +++++++++------ 3 files changed, 24 insertions(+), 86 deletions(-) delete mode 100644 tests/Options/OptionsManagerTest.php diff --git a/src/Util/FormatterUtil.php b/src/Util/FormatterUtil.php index 3a9714f6..baea9d7f 100644 --- a/src/Util/FormatterUtil.php +++ b/src/Util/FormatterUtil.php @@ -48,11 +48,12 @@ public function formatDcaFieldValue( $value = Str::deserialize($value); $table = $dc->table; - [$system, $controller] = $this->prepareServices(); + $controller = $this->framework->getAdapter(Controller::class); + $controller->loadLanguageFile('default'); if ($settings->loadDca) { $controller->loadDataContainer($table); - $system->loadLanguageFile($table); + $controller->loadLanguageFile($table); } // dca can be overridden from outside @@ -175,20 +176,6 @@ public function formatDcaFieldValue( return Str::specialchars($value); } - /** @return array{System, Controller} */ - private function prepareServices(): array - { - /** @var System $system */ - $system = $this->framework->getAdapter(System::class); - - /** @var Controller $controller */ - $controller = $this->framework->getAdapter(Controller::class); - - $system->loadLanguageFile('default'); - - return [$system, $controller]; - } - private function getTagModel(): ?Model { if (class_exists(TagModel::class)) { diff --git a/tests/Options/OptionsManagerTest.php b/tests/Options/OptionsManagerTest.php deleted file mode 100644 index e37b616b..00000000 --- a/tests/Options/OptionsManagerTest.php +++ /dev/null @@ -1,59 +0,0 @@ -set('foo', 'foo'); - $options->bar = 'bar'; - - $this->assertTrue($options->has('foo')); - $this->assertTrue($options->has('bar')); - $this->assertFalse($options->has('baz')); - - $this->assertSame('foo', $options->get('foo')); - $this->assertSame('bar', $options->get('bar')); - $this->assertNull($options->get('baz')); - $this->assertSame('foobar', $options->get('baz', 'foobar')); - $this->assertSame('foo', $options->foo); - $this->assertSame('bar', $options->bar); - $this->assertNull($options->baz); - - $options->foo = 'bar'; - $options->del('bar'); - - $this->assertSame('bar', $options->foo); - $this->assertFalse($options->has('bar')); - } - - public function testOptionsFactoryInheritance() - { - $options = new class extends OptionsManager { - public string $foo = 'foo'; - }; - - $this->assertTrue($options->has('foo')); - $this->assertSame('foo', $options->foo); - $this->assertSame('foo', $options->get('foo')); - - $options->set('foo', 'baz'); - $options->set('bar', 'bar'); - - $this->assertTrue($options->has('foo')); - $this->assertTrue($options->has('bar')); - $this->assertSame('baz', $options->foo); - $this->assertSame('bar', $options->bar); - - $options->del('foo'); - $options->del('bar'); - - $this->assertFalse($options->has('foo')); - $this->assertFalse($options->has('bar')); - } -} \ No newline at end of file diff --git a/tests/Util/FormatterUtilTest.php b/tests/Util/FormatterUtilTest.php index 5e22e0d2..9f58883b 100644 --- a/tests/Util/FormatterUtilTest.php +++ b/tests/Util/FormatterUtilTest.php @@ -12,13 +12,21 @@ class FormatterUtilTest extends ContaoTestCase { + const TAGS_REPLACED = 'tags_replaced'; + public function getInstances(array $parameter = []): FormatterUtil { + $controllerAdapter = $this->mockAdapter(['loadLanguageFile', 'loadDataContainer']); + $controllerAdapter->method('loadLanguageFile')->willReturn(null); + $controllerAdapter->method('loadDataContainer')->willReturn(null); + + $insertTagParser = $this->createMock(InsertTagParser::class); + $insertTagParser->method('replace')->willReturn(static::TAGS_REPLACED); + $parameter['framework'] ??= $this->mockContaoFramework([ - System::class => $this->createMock(System::class), - Controller::class => $this->createMock(Controller::class) + Controller::class => $controllerAdapter ]); - $parameter['insertTagParser'] ??= $this->createMock(InsertTagParser::class); + $parameter['insertTagParser'] ??= $insertTagParser; $parameter['utils'] ??= $this->createMock(Utils::class); $parameter['system'] ??= $this->createMock(System::class); $parameter['kernelBundles'] ??= [ @@ -33,7 +41,7 @@ public function getInstances(array $parameter = []): FormatterUtil ); } - public function _testFormatDcaFieldValue() + public function testFormatDcaFieldValue() { $formatterUtil = $this->getInstances(); @@ -41,23 +49,25 @@ public function _testFormatDcaFieldValue() $dataContainer->table = 'tl_content'; $this->assertEquals( - 'test', + 'foo-bar', $formatterUtil->formatDcaFieldValue( $dataContainer, 'test', - 'test', - dcaOverride: ['inputType' => 'text'] + serialize(['value' => 'foo', 'unit' => 'bar']), + FormatterUtil\FormatDcaFieldValueOptions::create() + ->setDcaOverride(['inputType' => 'inputUnit']) + ->setArrayJoiner('-') ) ); $this->assertEquals( - 'foo-bar', + static::TAGS_REPLACED, $formatterUtil->formatDcaFieldValue( $dataContainer, 'test', - serialize(['value' => 'foo', 'unit' => 'bar']), - dcaOverride: ['inputType' => 'inputUnit'], - arrayJoiner: '-' + 'test', + FormatterUtil\FormatDcaFieldValueOptions::create() + ->setDcaOverride(['inputType' => 'text']) ) ); } From 02ac3f3558f2895ea059d26a50f699d25c3e8063 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 26 Mar 2024 11:10:34 +0100 Subject: [PATCH 19/26] fix phpstan --- src/Util/FormatterUtil.php | 1 - src/Util/FormatterUtil/FormatDcaFieldValueOptions.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Util/FormatterUtil.php b/src/Util/FormatterUtil.php index baea9d7f..c2cc61fb 100644 --- a/src/Util/FormatterUtil.php +++ b/src/Util/FormatterUtil.php @@ -196,7 +196,6 @@ private function isMultiColumnsActive(): bool /** * @param array $values * @param FormatDcaFieldValueOptions $settings - * @param string $arraySeparator * @param callable(array|string|null $value): string $callback The callback to format each value, possibly * recursively. * @return string diff --git a/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php b/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php index d0ba67f0..beb8803f 100644 --- a/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php +++ b/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php @@ -16,7 +16,7 @@ class FormatDcaFieldValueOptions public static function create(): FormatDcaFieldValueOptions { - return new static(); + return new self(); } public function setPreserveEmptyArrayValues(bool $preserveEmptyArrayValues): FormatDcaFieldValueOptions From d738346b7a663caa359f4ca9970b8c3fe6d99c60 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 26 Mar 2024 11:21:49 +0100 Subject: [PATCH 20/26] add missing tests --- .../FormatDcaFieldValueOptions.php | 3 ++ tests/Util/UtilsTest.php | 35 ++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php b/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php index beb8803f..f6511812 100644 --- a/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php +++ b/src/Util/FormatterUtil/FormatDcaFieldValueOptions.php @@ -2,6 +2,9 @@ namespace HeimrichHannot\UtilsBundle\Util\FormatterUtil; +/** + * @codeCoverageIgnore This is a simple DTO. + */ class FormatDcaFieldValueOptions { public bool $preserveEmptyArrayValues = false; diff --git a/tests/Util/UtilsTest.php b/tests/Util/UtilsTest.php index 2d4dcb5d..88bca641 100644 --- a/tests/Util/UtilsTest.php +++ b/tests/Util/UtilsTest.php @@ -9,19 +9,22 @@ namespace HeimrichHannot\UtilsBundle\Tests\Util; use Contao\TestCase\ContaoTestCase; -use HeimrichHannot\UtilsBundle\Util\ContainerUtil; +use HeimrichHannot\UtilsBundle\Util\AccordionUtil; use HeimrichHannot\UtilsBundle\Util\AnonymizeUtil; +use HeimrichHannot\UtilsBundle\Util\ArrayUtil; +use HeimrichHannot\UtilsBundle\Util\ClassUtil; +use HeimrichHannot\UtilsBundle\Util\ContainerUtil; use HeimrichHannot\UtilsBundle\Util\DatabaseUtil; use HeimrichHannot\UtilsBundle\Util\DcaUtil; +use HeimrichHannot\UtilsBundle\Util\FileUtil; +use HeimrichHannot\UtilsBundle\Util\FormatterUtil; use HeimrichHannot\UtilsBundle\Util\HtmlUtil; use HeimrichHannot\UtilsBundle\Util\LocaleUtil; use HeimrichHannot\UtilsBundle\Util\ModelUtil; use HeimrichHannot\UtilsBundle\Util\RequestUtil; -use HeimrichHannot\UtilsBundle\Util\UrlUtil; use HeimrichHannot\UtilsBundle\Util\RoutingUtil; -use HeimrichHannot\UtilsBundle\Util\ArrayUtil; use HeimrichHannot\UtilsBundle\Util\StringUtil; -use HeimrichHannot\UtilsBundle\Util\AccordionUtil; +use HeimrichHannot\UtilsBundle\Util\UrlUtil; use HeimrichHannot\UtilsBundle\Util\UserUtil; use HeimrichHannot\UtilsBundle\Util\Utils; use Symfony\Component\DependencyInjection\ServiceLocator; @@ -43,6 +46,9 @@ public function getTestInstance(array $parameter = []) case ArrayUtil::class: return $this->createMock(ArrayUtil::class); + case ClassUtil::class: + return $this->createMock(ClassUtil::class); + case ContainerUtil::class: return $this->createMock(ContainerUtil::class); @@ -52,6 +58,12 @@ public function getTestInstance(array $parameter = []) case DcaUtil::class: return $this->createMock(DcaUtil::class); + case FileUtil::class: + return $this->createMock(FileUtil::class); + + case FormatterUtil::class: + return $this->createMock(FormatterUtil::class); + case HtmlUtil::class: return $this->createMock(HtmlUtil::class); @@ -103,11 +115,26 @@ public function testContainer() $this->assertInstanceOf(ContainerUtil::class, $this->getTestInstance()->container()); } + public function testClass() + { + $this->assertInstanceOf(ClassUtil::class, $this->getTestInstance()->class()); + } + public function testDatabase() { $this->assertInstanceOf(DatabaseUtil::class, $this->getTestInstance()->database()); } + public function testFile() + { + $this->assertInstanceOf(FileUtil::class, $this->getTestInstance()->file()); + } + + public function testFormatter() + { + $this->assertInstanceOf(FormatterUtil::class, $this->getTestInstance()->formatter()); + } + public function testDca() { $this->assertInstanceOf(DcaUtil::class, $this->getTestInstance()->dca()); From 8b752717c91a4204a1327c064c1a4c4653d2acc7 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 26 Mar 2024 11:36:30 +0100 Subject: [PATCH 21/26] improve execute callback tests --- tests/Util/Dca/DcaUtilTest.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/Util/Dca/DcaUtilTest.php b/tests/Util/Dca/DcaUtilTest.php index d87ffbf6..9a44ec18 100644 --- a/tests/Util/Dca/DcaUtilTest.php +++ b/tests/Util/Dca/DcaUtilTest.php @@ -14,6 +14,7 @@ use HeimrichHannot\UtilsBundle\Util\DcaUtil; use HeimrichHannot\UtilsBundle\Util\DcaUtil\GetDcaFieldsOptions; use PHPUnit\Framework\MockObject\MockBuilder; +use TypeError; class DcaUtilTest extends AbstractUtilsTestCase { @@ -141,7 +142,12 @@ public function testGetDcaFields() public function testExecuteCallback() { $controllerAdapter = $this->mockAdapter(['importStatic']); - $controllerAdapter->method('importStatic')->willReturn($this); + $controllerAdapter->method('importStatic')->willReturnCallback(function($import) { + if (!class_exists($import)) { + throw new Exception('Class not found'); + } + return $this; + }); $contaoFramework = $this->mockContaoFramework([ Controller::class => $controllerAdapter, @@ -169,6 +175,11 @@ public function testExecuteCallback() } catch (Exception $e) { $this->assertSame('I was thrown on purpose', $e->getMessage()); } + + try { + $instance->executeCallback('thisIsNotCallable'); + $this->fail('An exception should have been thrown'); + } catch (TypeError) {} } /** From 493874b6e06d38e84aae46c870a873800d03f5f3 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 26 Mar 2024 14:21:54 +0100 Subject: [PATCH 22/26] fix suggestions --- src/Util/FormatterUtil.php | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Util/FormatterUtil.php b/src/Util/FormatterUtil.php index c2cc61fb..910e68fe 100644 --- a/src/Util/FormatterUtil.php +++ b/src/Util/FormatterUtil.php @@ -5,6 +5,7 @@ use Codefog\TagsBundle\Model\TagModel; use Contao\Config; use Contao\Controller; +use Contao\CoreBundle\Framework\Adapter; use Contao\CoreBundle\Framework\ContaoFramework; use Contao\CoreBundle\InsertTag\InsertTagParser; use Contao\DataContainer; @@ -12,8 +13,8 @@ use Contao\Environment; use Contao\Model; use Contao\StringUtil as Str; -use Contao\System; use Contao\Validator; +use Contao\Widget; use HeimrichHannot\UtilsBundle\Util\FormatterUtil\FormatDcaFieldValueOptions; class FormatterUtil @@ -100,8 +101,14 @@ public function formatDcaFieldValue( return $this->formatArray($value, $settings, $callback); } - if ($inputType === 'explanation' && isset($data['eval']['text'])) + if ($inputType === 'explanation' + && (!empty($textCallback = $data['eval']['text_callback'] ?? null) + || isset($data['eval']['text']))) { + if ($textCallback) { + $attributes = Widget::getAttributesFromDca($data, $field, $value, $field, $table, $dc); + return $this->utils->dca()->executeCallback($textCallback, $attributes); + } return $data['eval']['text']; } @@ -161,13 +168,6 @@ public function formatDcaFieldValue( : $reference; } - if ($data['eval']['encrypt'] ?? false) - { - [$encrypted, $iv] = explode('.', $value); - $key = System::getContainer()->getParameter('secret'); - $value = openssl_decrypt($encrypted, 'aes-256-ctr', $key, 0, base64_decode($iv, true)); - } - if ($settings->replaceInsertTags) { $value = $this->insertTagParser->replace($value); @@ -176,7 +176,11 @@ public function formatDcaFieldValue( return Str::specialchars($value); } - private function getTagModel(): ?Model + /** + * @return Adapter|TagModel|Model|null + * @noinspection PhpMixedReturnTypeCanBeReducedInspection + */ + private function getTagModel(): mixed { if (class_exists(TagModel::class)) { return $this->framework->getAdapter(TagModel::class); From d5854a193b7ef5da7910bf5d8ba2d94f4cca03d3 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 26 Mar 2024 14:26:13 +0100 Subject: [PATCH 23/26] fix options_callback priority --- src/Util/FormatterUtil.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Util/FormatterUtil.php b/src/Util/FormatterUtil.php index 910e68fe..3e8deda8 100644 --- a/src/Util/FormatterUtil.php +++ b/src/Util/FormatterUtil.php @@ -75,8 +75,11 @@ public function formatDcaFieldValue( if ($settings->optionsCache === null || !$settings->cacheOptions) { - $settings->optionsCache = $data['options'] ?? $this->utils->dca() - ->executeCallback($data['options_callback'] ?? null, $dc); + $optionsCallback = $data['options_callback'] ?? null; + $options = $data['options'] ?? null; + $settings->optionsCache = $optionsCallback + ? $this->utils->dca()->executeCallback($optionsCallback, $dc) ?? $options + : $options; } if (!is_array($settings->optionsCache)) { From ae58040bec0d72cc5006fa2a9940e145bb3b797f Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 26 Mar 2024 14:33:31 +0100 Subject: [PATCH 24/26] fix PHPStan --- src/Util/FormatterUtil.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Util/FormatterUtil.php b/src/Util/FormatterUtil.php index 3e8deda8..0b69e3b8 100644 --- a/src/Util/FormatterUtil.php +++ b/src/Util/FormatterUtil.php @@ -127,6 +127,7 @@ public function formatDcaFieldValue( } } + /** @var Model $tagModel */ if ($inputType === 'cfgTags' && ($tagModel = $this->getTagModel())) { $collection = $tagModel->findBy(['source=?', 'id = ?'], [$data['eval']['tagsManager'], $value]); @@ -180,8 +181,9 @@ public function formatDcaFieldValue( } /** - * @return Adapter|TagModel|Model|null + * @return Adapter|Adapter|Model|TagModel|null * @noinspection PhpMixedReturnTypeCanBeReducedInspection + * @phpstan-ignore-next-line For PHPStan, this method returns an object of an unknown class. */ private function getTagModel(): mixed { From 2a03a2aa3ab3e931d4054ea8cae81b32e75a6bf2 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 26 Mar 2024 14:36:35 +0100 Subject: [PATCH 25/26] fix PHPStan --- src/Util/FormatterUtil.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Util/FormatterUtil.php b/src/Util/FormatterUtil.php index 0b69e3b8..e76d6064 100644 --- a/src/Util/FormatterUtil.php +++ b/src/Util/FormatterUtil.php @@ -127,9 +127,12 @@ public function formatDcaFieldValue( } } - /** @var Model $tagModel */ - if ($inputType === 'cfgTags' && ($tagModel = $this->getTagModel())) - { + if ($inputType === 'cfgTags' + && ( + /** @var Model $tagModel */ + $tagModel = $this->getTagModel() + ) + ) { $collection = $tagModel->findBy(['source=?', 'id = ?'], [$data['eval']['tagsManager'], $value]); $value = null; From 25a9050df8615268dfc1c25cd98c94c1347c8f32 Mon Sep 17 00:00:00 2001 From: Eric Gesemann Date: Tue, 26 Mar 2024 14:38:03 +0100 Subject: [PATCH 26/26] fix PHPStan --- src/Util/FormatterUtil.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Util/FormatterUtil.php b/src/Util/FormatterUtil.php index e76d6064..32a1efac 100644 --- a/src/Util/FormatterUtil.php +++ b/src/Util/FormatterUtil.php @@ -127,12 +127,9 @@ public function formatDcaFieldValue( } } - if ($inputType === 'cfgTags' - && ( - /** @var Model $tagModel */ - $tagModel = $this->getTagModel() - ) - ) { + if ($inputType === 'cfgTags' && $tagModel = $this->getTagModel()) + { + /** @var Model $tagModel */ $collection = $tagModel->findBy(['source=?', 'id = ?'], [$data['eval']['tagsManager'], $value]); $value = null;