Skip to content

Commit ac87a7f

Browse files
committed
Fix project config ordering for partial loaded elementSources entries
Applying project config to a clean install only sorted keys by depth. This allowed `sections` changes to run before related `elementSources` entries were fully built. Resulting in the error: In ElementSources.php line 147: Undefined array key "type" Sort pending changes by top-level key first, then by depth within each group. This makes sure only complete elements are applied.
1 parent 3156467 commit ac87a7f

2 files changed

Lines changed: 74 additions & 1 deletion

File tree

src/services/ProjectConfig.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1486,8 +1486,16 @@ private function _getPendingChanges(?array $configData = null, bool $existsOnly
14861486

14871487
unset($removedItem);
14881488

1489-
// Sort by number of dots to ensure deepest paths listed first
1489+
// Sort by top-level key first, then deepest paths first within each group.
14901490
$sorter = function($a, $b) {
1491+
$aTop = explode('.', $a, 2)[0];
1492+
$bTop = explode('.', $b, 2)[0];
1493+
$topCmp = strcmp($aTop, $bTop);
1494+
1495+
if ($topCmp !== 0) {
1496+
return $topCmp;
1497+
}
1498+
14911499
$aDepth = ProjectConfigHelper::pathDepth($a);
14921500
$bDepth = ProjectConfigHelper::pathDepth($b);
14931501
return $bDepth <=> $aDepth;

tests/unit/services/ProjectConfigTest.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99

1010
use Codeception\Stub\Expected;
1111
use Craft;
12+
use craft\elements\Category;
1213
use craft\helpers\StringHelper;
14+
use craft\models\ProjectConfigData;
1315
use craft\models\ReadOnlyProjectConfigData;
1416
use craft\mutex\Mutex;
1517
use craft\mutex\NullMutex;
@@ -216,6 +218,69 @@ public function testEventsFiredAndDeltaStored(): void
216218
$pc->saveModifiedConfigData();
217219
}
218220

221+
public function testApplyingConfigChangesDoesNotExposePartialElementSourcesToSectionHandlers(): void
222+
{
223+
$projectConfig = new ProjectConfig();
224+
$internalConfig = new ReadOnlyProjectConfigData([], $projectConfig);
225+
$currentWorkingConfig = new ProjectConfigData([], $projectConfig);
226+
227+
$internalConfigProperty = new \ReflectionProperty(ProjectConfig::class, '_internalConfig');
228+
$internalConfigProperty->setValue($projectConfig, $internalConfig);
229+
230+
$currentWorkingConfigProperty = new \ReflectionProperty(ProjectConfig::class, '_currentWorkingConfig');
231+
$currentWorkingConfigProperty->setValue($projectConfig, $currentWorkingConfig);
232+
233+
$incomingConfig = [
234+
'elementSources' => [
235+
'craft\\elements\\Category' => [[
236+
'defaultSort' => ['structure', 'asc'],
237+
'tableAttributes' => ['link'],
238+
'type' => 'native',
239+
'key' => 'group:bedad454-861f-4d7f-b0f6-40ce023cffc5',
240+
'disabled' => false,
241+
]],
242+
],
243+
'sections' => [
244+
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' => [
245+
'siteSettings' => [
246+
'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb' => [
247+
'hasUrls' => true,
248+
'uriFormat' => 'test/{slug}',
249+
'template' => 'test/_entry',
250+
'enabledByDefault' => true,
251+
],
252+
],
253+
'entryTypes' => ['cccccccc-cccc-cccc-cccc-cccccccccccc'],
254+
],
255+
],
256+
];
257+
258+
$externalConfig = new ReadOnlyProjectConfigData($incomingConfig, $projectConfig);
259+
$externalConfigProperty = new \ReflectionProperty(ProjectConfig::class, '_externalConfig');
260+
$externalConfigProperty->setValue($projectConfig, $externalConfig);
261+
262+
$originalProjectConfig = Craft::$app->getProjectConfig();
263+
$originalElementSources = Craft::$app->getElementSources();
264+
$handlerWasCalled = false;
265+
266+
Craft::$app->set('projectConfig', $projectConfig);
267+
Craft::$app->set('elementSources', new \craft\services\ElementSources());
268+
269+
$projectConfig->onAdd(ProjectConfig::PATH_SECTIONS . '.{uid}', function() use (&$handlerWasCalled) {
270+
$handlerWasCalled = true;
271+
Craft::$app->getElementSources()->getSources(Category::class);
272+
});
273+
274+
try {
275+
$projectConfig->applyConfigChanges($incomingConfig);
276+
} finally {
277+
Craft::$app->set('projectConfig', $originalProjectConfig);
278+
Craft::$app->set('elementSources', $originalElementSources);
279+
}
280+
281+
self::assertTrue($handlerWasCalled);
282+
}
283+
219284
public function getConfigProvider(): array
220285
{
221286
return [

0 commit comments

Comments
 (0)