Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
<?php

/*
* This file is part of the MODX Revolution package.
*
* Copyright (c) MODX, LLC
*
* For complete copyright and license information, see the COPYRIGHT and LICENSE
* files found in the top-level directory of this distribution.
*
* @package modx-test
*/

namespace MODX\Revolution\Tests\Processors\System\Settings;

use MODX\Revolution\modContextSetting;
use MODX\Revolution\modSystemSetting;
use MODX\Revolution\modUserSetting;
use MODX\Revolution\MODxTestCase;
use MODX\Revolution\Processors\Security\User\Setting\GetList as UserSettingGetList;
use MODX\Revolution\Processors\System\Settings\GetList;

/**
* Regression for #16472: system settings list marks context/user overrides.
*
* @package modx-test
* @subpackage modx
* @group Processors
* @group System
* @group Settings
*/
class SettingsAlsoInGetListTest extends MODxTestCase
{
private const KEY = 'unittest_also_in_setting';

/**
* @before
*/
public function setUpFixtures()
{
parent::setUpFixtures();
$this->cleanupFixtures();

$system = $this->modx->newObject(modSystemSetting::class);
$system->fromArray([
'key' => self::KEY,
'value' => 'system',
'xtype' => 'textfield',
'namespace' => 'core',
'area' => 'system',
], '', true, true);
$this->assertTrue((bool)$system->save());
}

/**
* @after
*/
public function tearDownFixtures()
{
$this->cleanupFixtures();
parent::tearDownFixtures();
}

public function testSystemSettingsListMarksContextAndUserOverrides()
{
$context = $this->modx->newObject(modContextSetting::class);
$context->fromArray([
'context_key' => 'web',
'key' => self::KEY,
'value' => 'context',
'xtype' => 'textfield',
'namespace' => 'core',
'area' => 'system',
], '', true, true);
$this->assertTrue((bool)$context->save());

$user = $this->modx->newObject(modUserSetting::class);
$user->fromArray([
'user' => 1,
'key' => self::KEY,
'value' => 'user',
'xtype' => 'textfield',
'namespace' => 'core',
'area' => 'system',
], '', true, true);
$this->assertTrue((bool)$user->save());

$result = $this->modx->runProcessor(GetList::class, [
'query' => self::KEY,
'limit' => 20,
'start' => 0,
]);
$this->assertTrue($this->checkForSuccess($result));

$row = $this->findSettingRow($result, self::KEY);
$this->assertNotNull($row);
$this->assertTrue($row['has_context_override']);
$this->assertTrue($row['has_user_override']);
}

public function testSystemSettingsListWithoutOverrides()
{
$result = $this->modx->runProcessor(GetList::class, [
'query' => self::KEY,
'limit' => 20,
'start' => 0,
]);
$this->assertTrue($this->checkForSuccess($result));

$row = $this->findSettingRow($result, self::KEY);
$this->assertNotNull($row);
$this->assertFalse($row['has_context_override']);
$this->assertFalse($row['has_user_override']);
}

public function testUserSettingsListDoesNotAddAlsoInFlags()
{
$result = $this->modx->runProcessor(UserSettingGetList::class, [
'user' => 1,
'limit' => 5,
'start' => 0,
]);
// May succeed with empty list depending on fixtures; assert no also-in fields when rows exist.
if (!$this->checkForSuccess($result)) {
$this->markTestSkipped('User setting list processor unavailable in test harness.');
}

$results = $this->getResults($result);
foreach ($results as $row) {
$this->assertArrayNotHasKey('has_context_override', $row);
$this->assertArrayNotHasKey('has_user_override', $row);
}
$this->assertTrue(true);
}

private function cleanupFixtures(): void
{
$system = $this->modx->getObject(modSystemSetting::class, ['key' => self::KEY]);
if ($system) {
$system->remove();
}

$contexts = $this->modx->getCollection(modContextSetting::class, ['key' => self::KEY]);
foreach ($contexts as $context) {
$context->remove();
}

$users = $this->modx->getCollection(modUserSetting::class, ['key' => self::KEY]);
foreach ($users as $user) {
$user->remove();
}
}

/**
* @param mixed $result
* @param string $key
* @return array|null
*/
private function findSettingRow($result, string $key): ?array
{
foreach ($this->getResults($result) as $row) {
if (($row['oldkey'] ?? $row['key'] ?? null) === $key || ($row['key'] ?? null) === $key) {
return $row;
}
}

return null;
}
}
1 change: 1 addition & 0 deletions _build/test/phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<directory>Tests/Processors/Context</directory>
<directory>Tests/Processors/Element</directory>
<directory>Tests/Processors/Resource</directory>
<directory>Tests/Processors/System</directory>
</testsuite>
<testsuite name="Transport">
<directory>Tests/Transport</directory>
Expand Down
3 changes: 3 additions & 0 deletions core/lexicon/en/setting.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
$_lang['settings_users'] = 'User';
$_lang['system_settings'] = 'System Settings';
$_lang['usergroup'] = 'User Group';
$_lang['setting_also_in'] = 'Also in';
$_lang['setting_also_in_context'] = 'Context';
$_lang['setting_also_in_user'] = 'User';

// user settings
$_lang['setting_access_category_enabled'] = 'Check Category Access';
Expand Down
85 changes: 84 additions & 1 deletion core/src/Revolution/Processors/System/Settings/GetList.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
namespace MODX\Revolution\Processors\System\Settings;

use MODX\Revolution\Formatter\modManagerDateFormatter;
use MODX\Revolution\modContextSetting;
use MODX\Revolution\modNamespace;
use MODX\Revolution\modSystemSetting;
use MODX\Revolution\modUserSetting;
use MODX\Revolution\Processors\Model\GetListProcessor;
use xPDO\Om\xPDOObject;
use xPDO\Om\xPDOQuery;

/**
* Get a list of system settings
Expand All @@ -37,6 +38,12 @@ class GetList extends GetListProcessor

private modManagerDateFormatter $formatter;

/** @var array<string, bool> Keys that exist in context_setting (overrides) */
private array $contextOverrideKeys = [];

/** @var array<string, bool> Keys that exist in user_settings (overrides) */
private array $userOverrideKeys = [];

/**
* @return bool
*/
Expand All @@ -59,6 +66,76 @@ public function prepareCriteria()
return [];
}

/**
* {@inheritDoc}
* For system settings only, preload which keys also exist as context/user overrides.
*/
public function iterate(array $data)
{
if ($this->classKey === modSystemSetting::class) {
$keys = [];
foreach ($data['results'] as $object) {
$key = $object->get('key');
if ($key !== null && $key !== '') {
$keys[] = $key;
}
}
$this->loadOverrideKeys(array_values(array_unique($keys)));
}

return parent::iterate($data);
}

/**
* Build set maps of override keys without hydrating xPDO objects.
*
* @param array $keys
* @return void
*/
protected function loadOverrideKeys(array $keys)
{
$this->contextOverrideKeys = [];
$this->userOverrideKeys = [];
if (empty($keys)) {
return;
}

$this->contextOverrideKeys = $this->fetchDistinctSettingKeys(modContextSetting::class, $keys);
$this->userOverrideKeys = $this->fetchDistinctSettingKeys(modUserSetting::class, $keys);
}

/**
* @param string $class
* @param array $keys
* @return array<string, bool>
*/
protected function fetchDistinctSettingKeys(string $class, array $keys): array
{
$table = $this->modx->getTableName($class);
if ($table === '' || $table === null) {
return [];
}

$quotedKeys = [];
foreach ($keys as $key) {
$quotedKeys[] = $this->modx->quote((string)$key);
}

$statement = $this->modx->query(
'SELECT DISTINCT `key` FROM ' . $table . ' WHERE `key` IN (' . implode(',', $quotedKeys) . ')'
);
if (!$statement) {
return [];
}

$found = $statement->fetchAll(\PDO::FETCH_COLUMN);
if (!is_array($found) || empty($found)) {
return [];
}

return array_fill_keys($found, true);
}

/**
* Get a collection of modSystemSetting objects
* @return array
Expand Down Expand Up @@ -175,6 +252,12 @@ public function prepareRow(xPDOObject $object)
: $this->formatter->formatDateTime($editedOn)
;

if ($this->classKey === modSystemSetting::class) {
$settingKey = $object->get('key');
$settingArray['has_context_override'] = isset($this->contextOverrideKeys[$settingKey]);
$settingArray['has_user_override'] = isset($this->userOverrideKeys[$settingKey]);
}

return $settingArray;
}
}
Loading
Loading