Skip to content

Commit 403f949

Browse files
committed
Merge branch '5.x' into 5.10
2 parents 74ede60 + ed7c3a0 commit 403f949

3 files changed

Lines changed: 40 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
## Unreleased
44

55
- Most classes can now be instantiated via the `create()` Twig function. ([#18376](https://github.com/craftcms/cms/discussions/18376))
6+
- Added `craft\helpers\ProjectConfig::pathDepth()`.
7+
- Deprecated `craft\services\ProjectConfig::getPendingChangeSummary()`.
68
- Fixed a bug where element search query caches weren’t getting invalidated when elements’ search keywords were indexed. ([#18275](https://github.com/craftcms/cms/issues/18275))
79
- Fixed a bug where disabled sites weren’t getting loaded when running Codeception tests. ([#18638](https://github.com/craftcms/cms/issues/18638))
10+
- Fixed a bug where custom entry index page icons weren’t getting stored properly if the source name contained periods. ([#18631](https://github.com/craftcms/cms/issues/18631))
811

912
## 5.9.18 - 2026-03-26
1013

src/helpers/ProjectConfig.php

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,9 @@ public static function unpackAssociativeArray(array $array, bool $recursive = tr
485485
public static function flattenConfigArray(array $array, string $path, array &$result): void
486486
{
487487
foreach ($array as $key => $value) {
488-
$thisPath = ltrim($path . '.' . $key, '.');
488+
// escape periods within keys, so they don't get confused as multiple path segments
489+
// (see https://github.com/craftcms/cms/issues/18631)
490+
$thisPath = ltrim(sprintf('%s.%s', $path, str_replace('.', '\.', $key)), '.');
489491

490492
if (is_array($value)) {
491493
self::flattenConfigArray($value, $thisPath, $result);
@@ -527,7 +529,7 @@ public static function splitConfigIntoComponents(array $config): array
527529
public static function traverseDataArray(array &$data, string|array $path, mixed $value = null, bool $delete = false): mixed
528530
{
529531
if (is_string($path)) {
530-
$path = explode('.', $path);
532+
$path = static::pathSegments($path);
531533
}
532534

533535
$nextSegment = array_shift($path);
@@ -784,9 +786,31 @@ public static function pathSegments(string $path): array
784786
if ($path === '') {
785787
throw new InvalidArgumentException('No project config path provided.');
786788
}
789+
790+
if (str_contains($path, '\.')) {
791+
$segments = preg_split('/(?<!\\\)\./', $path);
792+
return array_map(fn(string $segment) => str_replace('\.', '.', $segment), $segments);
793+
}
794+
787795
return explode('.', $path);
788796
}
789797

798+
/**
799+
* Returns the number of segments in the given path.
800+
*
801+
* @param string $path
802+
* @return int
803+
* @since 5.9.19
804+
*/
805+
public static function pathDepth(string $path): int
806+
{
807+
if (str_contains($path, '\.')) {
808+
return preg_match_all('/(?<!\\\)\./', $path);
809+
}
810+
811+
return substr_count($path, '.');
812+
}
813+
790814
/**
791815
* Returns the last segment in a given project config path.
792816
*
@@ -813,6 +837,10 @@ public static function pathWithoutLastSegment(string $path): ?string
813837
{
814838
$segments = static::pathSegments($path);
815839
array_pop($segments);
816-
return !empty($segments) ? implode('.', $segments) : null;
840+
if (empty($segments)) {
841+
return null;
842+
}
843+
$segments = array_map(fn(string $segment) => str_replace('.', '\.', $segment), $segments);
844+
return implode('.', $segments);
817845
}
818846
}

src/services/ProjectConfig.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,7 @@ protected function persistInternalConfigValues(array $values): void
918918
* Returns a summary of all pending config changes.
919919
*
920920
* @return array
921+
* @deprecated in 5.9.19
921922
*/
922923
public function getPendingChangeSummary(): array
923924
{
@@ -929,7 +930,7 @@ public function getPendingChangeSummary(): array
929930
foreach ($pendingChanges as $type => $changes) {
930931
$summary[$type] = [];
931932
foreach ($changes as $path) {
932-
$pathParts = explode('.', $path);
933+
$pathParts = ProjectConfigHelper::pathSegments($path);
933934
if (count($pathParts) > 1) {
934935
$summary[$type][$pathParts[0] . '.' . $pathParts[1]] = true;
935936
}
@@ -1125,7 +1126,7 @@ public function defer(ConfigEvent $event, callable $handler): void
11251126
*/
11261127
public function registerChangeEventHandler(string $event, string $path, callable $handler, mixed $data = null): void
11271128
{
1128-
$specificity = substr_count($path, '.');
1129+
$specificity = ProjectConfigHelper::pathDepth($path);
11291130
$pattern = '/^(?P<path>' . preg_quote($path, '/') . ')(?P<extra>\..+)?$/';
11301131
$pattern = str_replace('\\{uid\\}', '(' . self::UID_PATTERN . ')', $pattern);
11311132

@@ -1487,8 +1488,8 @@ private function _getPendingChanges(?array $configData = null, bool $existsOnly
14871488

14881489
// Sort by number of dots to ensure deepest paths listed first
14891490
$sorter = function($a, $b) {
1490-
$aDepth = substr_count($a, '.');
1491-
$bDepth = substr_count($b, '.');
1491+
$aDepth = ProjectConfigHelper::pathDepth($a);
1492+
$bDepth = ProjectConfigHelper::pathDepth($b);
14921493
return $bDepth <=> $aDepth;
14931494
};
14941495

@@ -1848,7 +1849,7 @@ private function _loadInternalConfig(): ReadOnlyProjectConfigData
18481849
$rows = $this->_createProjectConfigQuery()->orderBy('path')->pairs();
18491850
foreach ($rows as $path => $value) {
18501851
$current = &$data;
1851-
$segments = explode('.', $path);
1852+
$segments = ProjectConfigHelper::pathSegments($path);
18521853
foreach ($segments as $segment) {
18531854
// If we're still traversing, enforce array to avoid errors.
18541855
/** @phpstan-ignore-next-line */

0 commit comments

Comments
 (0)