Merge pull request #55261 from nextcloud/fix/hide-empty-settings-section

fix(admin): Hide empty section for users admin delegation
pull/53048/merge
Côme Chilliet 1 week ago committed by GitHub
commit 7a327b98cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      apps/settings/appinfo/info.xml
  2. 1
      apps/settings/composer/composer/autoload_classmap.php
  3. 1
      apps/settings/composer/composer/autoload_static.php
  4. 36
      apps/settings/lib/Sections/Admin/Users.php
  5. 33
      apps/settings/lib/Settings/Admin/Delegation.php
  6. 32
      apps/settings/lib/Settings/Admin/Users.php
  7. 3
      apps/webhook_listeners/appinfo/info.xml
  8. 1
      apps/webhook_listeners/composer/composer/autoload_classmap.php
  9. 1
      apps/webhook_listeners/composer/composer/autoload_static.php
  10. 33
      apps/webhook_listeners/lib/Settings/Admin.php
  11. 37
      apps/webhook_listeners/lib/Settings/AdminSection.php
  12. 12
      lib/private/App/AppManager.php
  13. 6
      lib/private/App/InfoParser.php
  14. 144
      lib/private/Settings/Manager.php
  15. 13
      lib/public/Settings/IManager.php
  16. 4
      resources/app-info-shipped.xsd
  17. 4
      resources/app-info.xsd

@ -35,7 +35,6 @@
<admin>OCA\Settings\Settings\Admin\Sharing</admin>
<admin>OCA\Settings\Settings\Admin\Security</admin>
<admin>OCA\Settings\Settings\Admin\Delegation</admin>
<admin>OCA\Settings\Settings\Admin\Users</admin>
<admin-section>OCA\Settings\Sections\Admin\Additional</admin-section>
<admin-section>OCA\Settings\Sections\Admin\Delegation</admin-section>
<admin-section>OCA\Settings\Sections\Admin\Groupware</admin-section>
@ -45,6 +44,8 @@
<admin-section>OCA\Settings\Sections\Admin\Security</admin-section>
<admin-section>OCA\Settings\Sections\Admin\Server</admin-section>
<admin-section>OCA\Settings\Sections\Admin\Sharing</admin-section>
<admin-delegation>OCA\Settings\Settings\Admin\Users</admin-delegation>
<admin-delegation-section>OCA\Settings\Sections\Admin\Users</admin-delegation-section>
<personal>OCA\Settings\Settings\Personal\Additional</personal>
<personal>OCA\Settings\Settings\Personal\PersonalInfo</personal>
<personal>OCA\Settings\Settings\Personal\ServerDevNotice</personal>

@ -60,6 +60,7 @@ return array(
'OCA\\Settings\\Sections\\Admin\\Security' => $baseDir . '/../lib/Sections/Admin/Security.php',
'OCA\\Settings\\Sections\\Admin\\Server' => $baseDir . '/../lib/Sections/Admin/Server.php',
'OCA\\Settings\\Sections\\Admin\\Sharing' => $baseDir . '/../lib/Sections/Admin/Sharing.php',
'OCA\\Settings\\Sections\\Admin\\Users' => $baseDir . '/../lib/Sections/Admin/Users.php',
'OCA\\Settings\\Sections\\Personal\\Availability' => $baseDir . '/../lib/Sections/Personal/Availability.php',
'OCA\\Settings\\Sections\\Personal\\Calendar' => $baseDir . '/../lib/Sections/Personal/Calendar.php',
'OCA\\Settings\\Sections\\Personal\\PersonalInfo' => $baseDir . '/../lib/Sections/Personal/PersonalInfo.php',

@ -75,6 +75,7 @@ class ComposerStaticInitSettings
'OCA\\Settings\\Sections\\Admin\\Security' => __DIR__ . '/..' . '/../lib/Sections/Admin/Security.php',
'OCA\\Settings\\Sections\\Admin\\Server' => __DIR__ . '/..' . '/../lib/Sections/Admin/Server.php',
'OCA\\Settings\\Sections\\Admin\\Sharing' => __DIR__ . '/..' . '/../lib/Sections/Admin/Sharing.php',
'OCA\\Settings\\Sections\\Admin\\Users' => __DIR__ . '/..' . '/../lib/Sections/Admin/Users.php',
'OCA\\Settings\\Sections\\Personal\\Availability' => __DIR__ . '/..' . '/../lib/Sections/Personal/Availability.php',
'OCA\\Settings\\Sections\\Personal\\Calendar' => __DIR__ . '/..' . '/../lib/Sections/Personal/Calendar.php',
'OCA\\Settings\\Sections\\Personal\\PersonalInfo' => __DIR__ . '/..' . '/../lib/Sections/Personal/PersonalInfo.php',

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Settings\Sections\Admin;
use OCP\IL10N;
use OCP\Settings\IIconSection;
class Users implements IIconSection {
public function __construct(
private IL10N $l,
) {
}
public function getID(): string {
return 'usersdelegation';
}
public function getName(): string {
return $this->l->t('Users');
}
public function getPriority(): int {
return 55;
}
public function getIcon(): string {
return '';
}
}

@ -45,32 +45,19 @@ class Delegation implements ISettings {
private function initSettingState(): void {
// Available settings page initialization
$sections = $this->settingManager->getAdminSections();
$delegatedSettings = $this->settingManager->getAdminDelegatedSettings();
$settings = [];
foreach ($sections as $sectionPriority) {
foreach ($sectionPriority as $section) {
$sectionSettings = $this->settingManager->getAdminSettings($section->getId());
$sectionSettings = array_reduce($sectionSettings, [$this, 'getDelegatedSettings'], []);
$settings = array_merge(
$settings,
array_map(function (IDelegatedSettings $setting) use ($section) {
$sectionName = $section->getName() . ($setting->getName() !== null ? ' - ' . $setting->getName() : '');
return [
'class' => get_class($setting),
'sectionName' => $sectionName,
'id' => mb_strtolower(str_replace(' ', '-', $sectionName)),
'priority' => $section->getPriority(),
];
}, $sectionSettings)
);
foreach ($delegatedSettings as ['section' => $section, 'settings' => $sectionSettings]) {
foreach ($sectionSettings as $setting) {
$sectionName = $section->getName() . ($setting->getName() !== null ? ' - ' . $setting->getName() : '');
$settings[] = [
'class' => get_class($setting),
'sectionName' => $sectionName,
'id' => mb_strtolower(str_replace(' ', '-', $sectionName)),
'priority' => $section->getPriority(),
];
}
}
usort($settings, function (array $a, array $b) {
if ($a['priority'] == $b['priority']) {
return 0;
}
return ($a['priority'] < $b['priority']) ? -1 : 1;
});
$this->initialStateService->provideInitialState('available-settings', $settings);
}

@ -10,49 +10,27 @@ declare(strict_types=1);
namespace OCA\Settings\Settings\Admin;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IL10N;
use OCP\Settings\IDelegatedSettings;
/**
* Empty settings class, used only for admin delegation.
*/
class Users implements IDelegatedSettings {
public function __construct(
protected string $appName,
private IL10N $l10n,
) {
}
/**
* Empty template response
*/
public function getForm(): TemplateResponse {
return new /** @template-extends TemplateResponse<\OCP\AppFramework\Http::STATUS_OK, array{}> */ class($this->appName, '') extends TemplateResponse {
public function render(): string {
return '';
}
};
throw new \Exception('Admin delegation settings should never be rendered');
}
public function getSection(): ?string {
return 'admindelegation';
return 'usersdelegation';
}
/**
* @return int whether the form should be rather on the top or bottom of
* the admin section. The forms are arranged in ascending order of the
* priority values. It is required to return a value between 0 and 100.
*
* E.g.: 70
*/
public function getPriority(): int {
return 0;
}
public function getName(): string {
return $this->l10n->t('Users');
public function getName(): ?string {
/* Use section name alone */
return null;
}
public function getAuthorizedAppConfig(): array {

@ -32,6 +32,7 @@
</commands>
<settings>
<admin>OCA\WebhookListeners\Settings\Admin</admin>
<admin-delegation>OCA\WebhookListeners\Settings\Admin</admin-delegation>
<admin-delegation-section>OCA\WebhookListeners\Settings\AdminSection</admin-delegation-section>
</settings>
</info>

@ -20,4 +20,5 @@ return array(
'OCA\\WebhookListeners\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php',
'OCA\\WebhookListeners\\Service\\PHPMongoQuery' => $baseDir . '/../lib/Service/PHPMongoQuery.php',
'OCA\\WebhookListeners\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php',
'OCA\\WebhookListeners\\Settings\\AdminSection' => $baseDir . '/../lib/Settings/AdminSection.php',
);

@ -35,6 +35,7 @@ class ComposerStaticInitWebhookListeners
'OCA\\WebhookListeners\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
'OCA\\WebhookListeners\\Service\\PHPMongoQuery' => __DIR__ . '/..' . '/../lib/Service/PHPMongoQuery.php',
'OCA\\WebhookListeners\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php',
'OCA\\WebhookListeners\\Settings\\AdminSection' => __DIR__ . '/..' . '/../lib/Settings/AdminSection.php',
);
public static function getInitializer(ClassLoader $loader)

@ -9,51 +9,32 @@ declare(strict_types=1);
namespace OCA\WebhookListeners\Settings;
use OCP\AppFramework\Http;
use OCA\WebhookListeners\AppInfo\Application;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IL10N;
use OCP\Settings\IDelegatedSettings;
/**
* Empty settings class, used only for admin delegation for now as there is no UI
*/
class Admin implements IDelegatedSettings {
public function __construct(
protected string $appName,
private IL10N $l10n,
) {
}
/**
* Empty template response
*/
public function getForm(): TemplateResponse {
return new /** @template-extends TemplateResponse<Http::STATUS_OK, array{}> */ class($this->appName, '') extends TemplateResponse {
public function render(): string {
return '';
}
};
throw new \Exception('Admin delegation settings should never be rendered');
}
public function getSection(): ?string {
return 'admindelegation';
public function getSection(): string {
return Application::APP_ID . '-admin';
}
/**
* @return int whether the form should be rather on the top or bottom of
* the admin section. The forms are arranged in ascending order of the
* priority values. It is required to return a value between 0 and 100.
*
* E.g.: 70
*/
public function getPriority(): int {
return 0;
}
public function getName(): string {
return $this->l10n->t('Webhooks');
public function getName(): ?string {
/* Use section name alone */
return null;
}
public function getAuthorizedAppConfig(): array {

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\WebhookListeners\Settings;
use OCA\WebhookListeners\AppInfo\Application;
use OCP\IL10N;
use OCP\Settings\IIconSection;
class AdminSection implements IIconSection {
public function __construct(
private IL10N $l,
) {
}
public function getID(): string {
return Application::APP_ID . '-admin';
}
public function getName(): string {
return $this->l->t('Webhooks');
}
public function getPriority(): int {
return 56;
}
public function getIcon(): string {
return '';
}
}

@ -502,7 +502,7 @@ class AppManager implements IAppManager {
}
if (!empty($info['settings'])) {
$settingsManager = \OC::$server->get(ISettingsManager::class);
$settingsManager = \OCP\Server::get(ISettingsManager::class);
if (!empty($info['settings']['admin'])) {
foreach ($info['settings']['admin'] as $setting) {
$settingsManager->registerSetting('admin', $setting);
@ -523,6 +523,16 @@ class AppManager implements IAppManager {
$settingsManager->registerSection('personal', $section);
}
}
if (!empty($info['settings']['admin-delegation'])) {
foreach ($info['settings']['admin-delegation'] as $setting) {
$settingsManager->registerSetting(ISettingsManager::SETTINGS_DELEGATION, $setting);
}
}
if (!empty($info['settings']['admin-delegation-section'])) {
foreach ($info['settings']['admin-delegation-section'] as $section) {
$settingsManager->registerSection(ISettingsManager::SETTINGS_DELEGATION, $section);
}
}
}
if (!empty($info['collaboration']['plugins'])) {

@ -184,6 +184,12 @@ class InfoParser {
if (isset($array['settings']['personal-section']) && !is_array($array['settings']['personal-section'])) {
$array['settings']['personal-section'] = [$array['settings']['personal-section']];
}
if (isset($array['settings']['admin-delegation']) && !is_array($array['settings']['admin-delegation'])) {
$array['settings']['admin-delegation'] = [$array['settings']['admin-delegation']];
}
if (isset($array['settings']['admin-delegation-section']) && !is_array($array['settings']['admin-delegation-section'])) {
$array['settings']['admin-delegation-section'] = [$array['settings']['admin-delegation-section']];
}
if (isset($array['navigations']['navigation']) && $this->isNavigationItem($array['navigations']['navigation'])) {
$array['navigations']['navigation'] = [$array['navigations']['navigation']];
}

@ -16,6 +16,7 @@ use OCP\IServerContainer;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\L10N\IFactory;
use OCP\Settings\IDelegatedSettings;
use OCP\Settings\IIconSection;
use OCP\Settings\IManager;
use OCP\Settings\ISettings;
@ -23,54 +24,31 @@ use OCP\Settings\ISubAdminSettings;
use Psr\Log\LoggerInterface;
class Manager implements IManager {
/** @var LoggerInterface */
private $log;
private ?IL10N $l = null;
/** @var IL10N */
private $l;
/** @var IFactory */
private $l10nFactory;
/** @var IURLGenerator */
private $url;
/** @var IServerContainer */
private $container;
/** @var array<self::SETTINGS_*, list<class-string<IIconSection>>> */
protected array $sectionClasses = [];
/** @var AuthorizedGroupMapper $mapper */
private $mapper;
/** @var array<self::SETTINGS_*, array<string, IIconSection>> */
protected array $sections = [];
/** @var IGroupManager $groupManager */
private $groupManager;
/** @var array<class-string<ISettings>, self::SETTINGS_*> */
protected array $settingClasses = [];
/** @var ISubAdmin $subAdmin */
private $subAdmin;
/** @var array<self::SETTINGS_*, array<string, list<ISettings>>> */
protected array $settings = [];
public function __construct(
LoggerInterface $log,
IFactory $l10nFactory,
IURLGenerator $url,
IServerContainer $container,
AuthorizedGroupMapper $mapper,
IGroupManager $groupManager,
ISubAdmin $subAdmin,
private LoggerInterface $log,
private IFactory $l10nFactory,
private IURLGenerator $url,
private IServerContainer $container,
private AuthorizedGroupMapper $mapper,
private IGroupManager $groupManager,
private ISubAdmin $subAdmin,
) {
$this->log = $log;
$this->l10nFactory = $l10nFactory;
$this->url = $url;
$this->container = $container;
$this->mapper = $mapper;
$this->groupManager = $groupManager;
$this->subAdmin = $subAdmin;
}
/** @var array<self::SETTINGS_*, list<class-string<IIconSection>>> */
protected $sectionClasses = [];
/** @var array<self::SETTINGS_*, array<string, IIconSection>> */
protected $sections = [];
/**
* @inheritdoc
*/
@ -138,12 +116,6 @@ class Manager implements IManager {
], true);
}
/** @var array<class-string<ISettings>, self::SETTINGS_*> */
protected $settingClasses = [];
/** @var array<self::SETTINGS_*, array<string, list<ISettings>>> */
protected $settings = [];
/**
* @inheritdoc
*/
@ -164,40 +136,41 @@ class Manager implements IManager {
}
if (!isset($this->settings[$type][$section])) {
$this->settings[$type][$section] = [];
}
foreach ($this->settingClasses as $class => $settingsType) {
if ($type !== $settingsType) {
continue;
}
foreach ($this->settingClasses as $class => $settingsType) {
if ($type !== $settingsType) {
continue;
}
try {
/** @var ISettings $setting */
$setting = $this->container->get($class);
} catch (QueryException $e) {
$this->log->info($e->getMessage(), ['exception' => $e]);
continue;
}
try {
/** @var ISettings $setting */
$setting = $this->container->get($class);
} catch (QueryException $e) {
$this->log->info($e->getMessage(), ['exception' => $e]);
continue;
}
if (!$setting instanceof ISettings) {
$e = new \InvalidArgumentException('Invalid settings setting registered (' . $class . ')');
$this->log->info($e->getMessage(), ['exception' => $e]);
continue;
}
if (!$setting instanceof ISettings) {
$e = new \InvalidArgumentException('Invalid settings setting registered (' . $class . ')');
$this->log->info($e->getMessage(), ['exception' => $e]);
continue;
}
$settingSection = $setting->getSection();
if ($settingSection === null) {
continue;
}
if ($filter !== null && !$filter($setting)) {
continue;
}
if ($setting->getSection() === null) {
continue;
}
if (!isset($this->settings[$settingsType][$settingSection])) {
$this->settings[$settingsType][$settingSection] = [];
}
$this->settings[$settingsType][$settingSection][] = $setting;
if (!isset($this->settings[$settingsType][$setting->getSection()])) {
$this->settings[$settingsType][$setting->getSection()] = [];
unset($this->settingClasses[$class]);
}
$this->settings[$settingsType][$setting->getSection()][] = $setting;
}
unset($this->settingClasses[$class]);
if ($filter !== null) {
return array_values(array_filter($this->settings[$type][$section], $filter));
}
return $this->settings[$type][$section];
@ -349,4 +322,31 @@ class Manager implements IManager {
}
return $settings;
}
/**
* @return array<string, array{section:IIconSection,settings:list<IDelegatedSettings>}>
*/
public function getAdminDelegatedSettings(): array {
$sections = $this->getAdminSections();
$settings = [];
foreach ($sections as $sectionPriority) {
foreach ($sectionPriority as $section) {
/** @var IDelegatedSettings[] */
$sectionSettings = array_merge(
$this->getSettings(self::SETTINGS_ADMIN, $section->getID(), fn (ISettings $settings): bool => $settings instanceof IDelegatedSettings),
$this->getSettings(self::SETTINGS_DELEGATION, $section->getID(), fn (ISettings $settings): bool => $settings instanceof IDelegatedSettings),
);
usort(
$sectionSettings,
fn (ISettings $s1, ISettings $s2) => $s1->getPriority() <=> $s2->getPriority()
);
$settings[$section->getID()] = [
'section' => $section,
'settings' => $sectionSettings,
];
}
}
uasort($settings, fn (array $a, array $b) => $a['section']->getPriority() <=> $b['section']->getPriority());
return $settings;
}
}

@ -47,6 +47,12 @@ interface IManager {
*/
public const SETTINGS_PERSONAL = 'personal';
/**
* @since 33.0.0
* For settings only used for delegation but not appearing in settings menu
*/
public const SETTINGS_DELEGATION = 'delegation';
/**
* @psalm-param self::SETTINGS_* $type
* @param class-string<IIconSection> $section
@ -118,4 +124,11 @@ interface IManager {
* @since 25.0.0
*/
public function getSection(string $type, string $sectionId): ?IIconSection;
/**
* Return admin delegated settings, sorted by priority and grouped by section
* @return array<string, array{section:IIconSection,settings:list<IDelegatedSettings>}>
* @since 33.0.0
*/
public function getAdminDelegatedSettings(): array;
}

@ -415,6 +415,10 @@
maxOccurs="unbounded"/>
<xs:element name="personal-section" type="php-class" minOccurs="0"
maxOccurs="unbounded"/>
<xs:element name="admin-delegation" type="php-class" minOccurs="0"
maxOccurs="unbounded"/>
<xs:element name="admin-delegation-section" type="php-class" minOccurs="0"
maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>

@ -411,6 +411,10 @@
maxOccurs="unbounded"/>
<xs:element name="personal-section" type="php-class" minOccurs="0"
maxOccurs="unbounded"/>
<xs:element name="admin-delegation" type="php-class" minOccurs="0"
maxOccurs="unbounded"/>
<xs:element name="admin-delegation-section" type="php-class" minOccurs="0"
maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>

Loading…
Cancel
Save