From 3a3c8164c0ba30e0fde1db42c8dc885085d3af6f Mon Sep 17 00:00:00 2001 From: catch <6915-catch@users.noreply.drupalcode.org> Date: Wed, 9 Oct 2024 14:24:23 +0100 Subject: [PATCH] Issue #3471932 by mstrelan, bbrala, quietone: Fix callables that are expected to return bool but don't --- .../Drupal/Component/Utility/FilterArray.php | 31 ++++++++++ .../Drupal/Core/Datetime/Element/Datelist.php | 3 +- .../TypedData/Plugin/DataType/ItemList.php | 3 +- .../Core/TypedData/Plugin/DataType/Map.php | 3 +- .../d6/FieldInstanceOptionTranslation.php | 3 +- .../process/d6/FieldOptionTranslation.php | 3 +- .../migrate/process/d6/FieldSettings.php | 3 +- .../src/Driver/Database/sqlite/Connection.php | 3 +- .../d6/ProfileFieldOptionTranslation.php | 3 +- .../views/src/Plugin/views/HandlerBase.php | 3 +- .../Component/Utility/FilterArrayTest.php | 62 +++++++++++++++++++ 11 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 core/lib/Drupal/Component/Utility/FilterArray.php create mode 100644 core/tests/Drupal/Tests/Component/Utility/FilterArrayTest.php diff --git a/core/lib/Drupal/Component/Utility/FilterArray.php b/core/lib/Drupal/Component/Utility/FilterArray.php new file mode 100644 index 000000000000..172348df1163 --- /dev/null +++ b/core/lib/Drupal/Component/Utility/FilterArray.php @@ -0,0 +1,31 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Component\Utility; + +/** + * Provides methods to filter arrays. + * + * @ingroup utility + */ +class FilterArray { + + /** + * Removes empty strings from an array. + * + * This method removes all empty strings from the input array. This is + * particularly useful to preserve 0 whilst filtering other falsy values. The + * values are first cast to a string before comparison. + * + * @param array $value + * The array to filter. + * + * @return array + * The filtered array. + */ + public static function removeEmptyStrings(array $value): array { + return array_filter($value, static fn ($item) => (string) $item !== ''); + } + +} diff --git a/core/lib/Drupal/Core/Datetime/Element/Datelist.php b/core/lib/Drupal/Core/Datetime/Element/Datelist.php index 1b918131a870..c94ecfa58c3f 100644 --- a/core/lib/Drupal/Core/Datetime/Element/Datelist.php +++ b/core/lib/Drupal/Core/Datetime/Element/Datelist.php @@ -2,6 +2,7 @@ namespace Drupal\Core\Datetime\Element; +use Drupal\Component\Utility\FilterArray; use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\Variable; use Drupal\Core\Datetime\DateHelper; @@ -345,7 +346,7 @@ protected static function checkEmptyInputs($input, $parts) { // \Drupal\Core\Datetime\Element\Datelist::valueCallback(). unset($input['object']); // Filters out empty array values, any valid value would have a string length. - $filtered_input = array_filter($input, 'strlen'); + $filtered_input = FilterArray::removeEmptyStrings($input); return array_diff($parts, array_keys($filtered_input)); } diff --git a/core/lib/Drupal/Core/TypedData/Plugin/DataType/ItemList.php b/core/lib/Drupal/Core/TypedData/Plugin/DataType/ItemList.php index ee648a50f86f..9454862ff979 100644 --- a/core/lib/Drupal/Core/TypedData/Plugin/DataType/ItemList.php +++ b/core/lib/Drupal/Core/TypedData/Plugin/DataType/ItemList.php @@ -2,6 +2,7 @@ namespace Drupal\Core\TypedData\Plugin\DataType; +use Drupal\Component\Utility\FilterArray; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\TypedData\Attribute\DataType; use Drupal\Core\TypedData\ComplexDataInterface; @@ -92,7 +93,7 @@ public function getString() { $strings[] = $item->getString(); } // Remove any empty strings resulting from empty items. - return implode(', ', array_filter($strings, 'mb_strlen')); + return implode(', ', FilterArray::removeEmptyStrings($strings)); } /** diff --git a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Map.php b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Map.php index e08e9312e0c7..16aaa68fdc44 100644 --- a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Map.php +++ b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Map.php @@ -2,6 +2,7 @@ namespace Drupal\Core\TypedData\Plugin\DataType; +use Drupal\Component\Utility\FilterArray; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\TypedData\Attribute\DataType; use Drupal\Core\TypedData\ComplexDataInterface; @@ -106,7 +107,7 @@ public function getString() { $strings[] = $property->getString(); } // Remove any empty strings resulting from empty items. - return implode(', ', array_filter($strings, 'mb_strlen')); + return implode(', ', FilterArray::removeEmptyStrings($strings)); } /** diff --git a/core/modules/field/src/Plugin/migrate/process/d6/FieldInstanceOptionTranslation.php b/core/modules/field/src/Plugin/migrate/process/d6/FieldInstanceOptionTranslation.php index 6342c027de09..9ddca5490265 100644 --- a/core/modules/field/src/Plugin/migrate/process/d6/FieldInstanceOptionTranslation.php +++ b/core/modules/field/src/Plugin/migrate/process/d6/FieldInstanceOptionTranslation.php @@ -2,6 +2,7 @@ namespace Drupal\field\Plugin\migrate\process\d6; +use Drupal\Component\Utility\FilterArray; use Drupal\migrate\Attribute\MigrateProcess; use Drupal\migrate\MigrateExecutableInterface; use Drupal\migrate\ProcessPluginBase; @@ -27,7 +28,7 @@ public function transform($value, MigrateExecutableInterface $migrate_executable if (isset($global_settings['allowed_values'])) { $list = explode("\n", $global_settings['allowed_values']); $list = array_map('trim', $list); - $list = array_filter($list, 'strlen'); + $list = FilterArray::removeEmptyStrings($list); switch ($field_type) { case 'boolean'; $option = preg_replace('/^option_/', '', $row->getSourceProperty('property')); diff --git a/core/modules/field/src/Plugin/migrate/process/d6/FieldOptionTranslation.php b/core/modules/field/src/Plugin/migrate/process/d6/FieldOptionTranslation.php index 6c6cf100fa93..94842bda74e0 100644 --- a/core/modules/field/src/Plugin/migrate/process/d6/FieldOptionTranslation.php +++ b/core/modules/field/src/Plugin/migrate/process/d6/FieldOptionTranslation.php @@ -2,6 +2,7 @@ namespace Drupal\field\Plugin\migrate\process\d6; +use Drupal\Component\Utility\FilterArray; use Drupal\migrate\Attribute\MigrateProcess; use Drupal\migrate\MigrateExecutableInterface; use Drupal\migrate\ProcessPluginBase; @@ -29,7 +30,7 @@ public function transform($value, MigrateExecutableInterface $migrate_executable if (isset($global_settings['allowed_values'])) { $list = explode("\n", $global_settings['allowed_values']); $list = array_map('trim', $list); - $list = array_filter($list, 'strlen'); + $list = FilterArray::removeEmptyStrings($list); switch ($field_type) { case 'list_string': case 'list_integer': diff --git a/core/modules/field/src/Plugin/migrate/process/d6/FieldSettings.php b/core/modules/field/src/Plugin/migrate/process/d6/FieldSettings.php index ce247d025d99..ffdb2e6fcb57 100644 --- a/core/modules/field/src/Plugin/migrate/process/d6/FieldSettings.php +++ b/core/modules/field/src/Plugin/migrate/process/d6/FieldSettings.php @@ -2,6 +2,7 @@ namespace Drupal\field\Plugin\migrate\process\d6; +use Drupal\Component\Utility\FilterArray; use Drupal\migrate\Attribute\MigrateProcess; use Drupal\migrate\MigrateExecutableInterface; use Drupal\migrate\ProcessPluginBase; @@ -50,7 +51,7 @@ public function getSettings($field_type, $global_settings, $original_field_type if (isset($global_settings['allowed_values'])) { $list = explode("\n", $global_settings['allowed_values']); $list = array_map('trim', $list); - $list = array_filter($list, 'strlen'); + $list = FilterArray::removeEmptyStrings($list); switch ($field_type) { case 'list_string': case 'list_integer': diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php index 8e6d06b6fe2f..53c2fa0c1fe0 100644 --- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php +++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php @@ -2,6 +2,7 @@ namespace Drupal\sqlite\Driver\Database\sqlite; +use Drupal\Component\Utility\FilterArray; use Drupal\Core\Database\Connection as DatabaseConnection; use Drupal\Core\Database\DatabaseNotFoundException; use Drupal\Core\Database\ExceptionHandler; @@ -246,7 +247,7 @@ public static function sqlFunctionGreatest() { */ public static function sqlFunctionLeast() { // Remove all NULL, FALSE and empty strings values but leaves 0 (zero) values. - $values = array_filter(func_get_args(), 'strlen'); + $values = FilterArray::removeEmptyStrings(func_get_args()); return count($values) < 1 ? NULL : min($values); } diff --git a/core/modules/user/src/Plugin/migrate/process/d6/ProfileFieldOptionTranslation.php b/core/modules/user/src/Plugin/migrate/process/d6/ProfileFieldOptionTranslation.php index fd7aea36bce7..1cd4efff49b9 100644 --- a/core/modules/user/src/Plugin/migrate/process/d6/ProfileFieldOptionTranslation.php +++ b/core/modules/user/src/Plugin/migrate/process/d6/ProfileFieldOptionTranslation.php @@ -2,6 +2,7 @@ namespace Drupal\user\Plugin\migrate\process\d6; +use Drupal\Component\Utility\FilterArray; use Drupal\migrate\Attribute\MigrateProcess; use Drupal\migrate\MigrateExecutableInterface; use Drupal\migrate\ProcessPluginBase; @@ -27,7 +28,7 @@ public function transform($value, MigrateExecutableInterface $migrate_executable $allowed_values = []; $list = explode("\n", $translation); $list = array_map('trim', $list); - $list = array_filter($list, 'strlen'); + $list = FilterArray::removeEmptyStrings($list); if ($field_type === 'list_string') { foreach ($list as $value) { $allowed_values[] = ['label' => $value]; diff --git a/core/modules/views/src/Plugin/views/HandlerBase.php b/core/modules/views/src/Plugin/views/HandlerBase.php index 3c576d13d520..583efd832c4c 100644 --- a/core/modules/views/src/Plugin/views/HandlerBase.php +++ b/core/modules/views/src/Plugin/views/HandlerBase.php @@ -2,6 +2,7 @@ namespace Drupal\views\Plugin\views; +use Drupal\Component\Utility\FilterArray; use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Unicode; use Drupal\Component\Utility\UrlHelper; @@ -758,7 +759,7 @@ public static function breakString($str, $force_int = FALSE) { // Filter any empty matches (Like from '++' in a string) and reset the // array keys. 'strlen' is used as the filter callback so we do not lose // 0 values (would otherwise evaluate == FALSE). - $value = array_values(array_filter($value, 'strlen')); + $value = array_values(FilterArray::removeEmptyStrings($value)); if ($force_int) { $value = array_map('intval', $value); diff --git a/core/tests/Drupal/Tests/Component/Utility/FilterArrayTest.php b/core/tests/Drupal/Tests/Component/Utility/FilterArrayTest.php new file mode 100644 index 000000000000..289b19bfe824 --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Utility/FilterArrayTest.php @@ -0,0 +1,62 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\Component\Utility; + +use Drupal\Component\Utility\FilterArray; +use PHPUnit\Framework\TestCase; + +/** + * Test filter array functions. + * + * @group Utility + * + * @coversDefaultClass \Drupal\Component\Utility\FilterArray + */ +class FilterArrayTest extends TestCase { + + /** + * Tests removing empty strings. + * + * @dataProvider providerRemoveEmptyStrings + * @covers ::removeEmptyStrings + */ + public function testRemoveEmptyStrings(array $values, array $expected): void { + $this->assertEquals($expected, array_values(FilterArray::removeEmptyStrings($values))); + } + + /** + * Data provider for testRemoveEmptyStrings(). + * + * @see testRemoveEmptyStrings() + */ + public static function providerRemoveEmptyStrings(): \Generator { + yield 'strings' => [ + ['', ' ', '0', 'true', 'false'], + [' ', '0', 'true', 'false'], + ]; + yield 'integers' => [ + [-1, 0, 1], + [-1, 0, 1], + ]; + yield 'null, true, false' => [ + [NULL, TRUE, FALSE], + [TRUE], + ]; + + $stringable = new class implements \Stringable { + + public function __toString(): string { + return 'foo'; + } + + }; + + yield 'non-scalar' => [ + [new $stringable()], + [new $stringable()], + ]; + } + +} -- GitLab