diff --git a/apps/profile/appinfo/info.xml b/apps/profile/appinfo/info.xml
index 1310eb1f7f05a..b0faec62e454f 100644
--- a/apps/profile/appinfo/info.xml
+++ b/apps/profile/appinfo/info.xml
@@ -8,9 +8,9 @@
profile
Profile
This application provides the profile
- Provides a customisable user profile interface.
+ Provides a customizable user profile interface.
2.0.0-dev.0
- agpl
+ AGPL-3.0-or-later
Nextcloud GmbH
Profile
social
diff --git a/apps/profile/composer/composer/autoload_classmap.php b/apps/profile/composer/composer/autoload_classmap.php
index 8e2b1a20ee04e..623dce51d5393 100644
--- a/apps/profile/composer/composer/autoload_classmap.php
+++ b/apps/profile/composer/composer/autoload_classmap.php
@@ -9,6 +9,7 @@
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'OCA\\Profile\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
'OCA\\Profile\\Controller\\ProfilePageController' => $baseDir . '/../lib/Controller/ProfilePageController.php',
+ 'OCA\\Profile\\Listener\\LoadAdditionalEntriesListener' => $baseDir . '/../lib/Listener/LoadAdditionalEntriesListener.php',
'OCA\\Profile\\Listener\\ProfilePickerReferenceListener' => $baseDir . '/../lib/Listener/ProfilePickerReferenceListener.php',
'OCA\\Profile\\Reference\\ProfilePickerReferenceProvider' => $baseDir . '/../lib/Reference/ProfilePickerReferenceProvider.php',
);
diff --git a/apps/profile/composer/composer/autoload_static.php b/apps/profile/composer/composer/autoload_static.php
index a12287d97196e..41efca598c418 100644
--- a/apps/profile/composer/composer/autoload_static.php
+++ b/apps/profile/composer/composer/autoload_static.php
@@ -24,6 +24,7 @@ class ComposerStaticInitProfile
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'OCA\\Profile\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
'OCA\\Profile\\Controller\\ProfilePageController' => __DIR__ . '/..' . '/../lib/Controller/ProfilePageController.php',
+ 'OCA\\Profile\\Listener\\LoadAdditionalEntriesListener' => __DIR__ . '/..' . '/../lib/Listener/LoadAdditionalEntriesListener.php',
'OCA\\Profile\\Listener\\ProfilePickerReferenceListener' => __DIR__ . '/..' . '/../lib/Listener/ProfilePickerReferenceListener.php',
'OCA\\Profile\\Reference\\ProfilePickerReferenceProvider' => __DIR__ . '/..' . '/../lib/Reference/ProfilePickerReferenceProvider.php',
);
diff --git a/apps/profile/lib/AppInfo/Application.php b/apps/profile/lib/AppInfo/Application.php
index 35569c8c6ae31..52d5939cdb6eb 100644
--- a/apps/profile/lib/AppInfo/Application.php
+++ b/apps/profile/lib/AppInfo/Application.php
@@ -9,6 +9,7 @@
namespace OCA\Profile\AppInfo;
+use OCA\Profile\Listener\LoadAdditionalEntriesListener;
use OCA\Profile\Listener\ProfilePickerReferenceListener;
use OCA\Profile\Reference\ProfilePickerReferenceProvider;
use OCP\AppFramework\App;
@@ -16,11 +17,7 @@
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\Collaboration\Reference\RenderReferenceEvent;
-use OCP\INavigationManager;
-use OCP\IURLGenerator;
-use OCP\IUserSession;
-use OCP\L10N\IFactory;
-use OCP\Server;
+use OCP\Navigation\Events\LoadAdditionalEntriesEvent;
class Application extends App implements IBootstrap {
public const APP_ID = 'profile';
@@ -33,37 +30,10 @@ public function __construct(array $urlParams = []) {
public function register(IRegistrationContext $context): void {
$context->registerReferenceProvider(ProfilePickerReferenceProvider::class);
$context->registerEventListener(RenderReferenceEvent::class, ProfilePickerReferenceListener::class);
+ $context->registerEventListener(LoadAdditionalEntriesEvent::class, LoadAdditionalEntriesListener::class);
}
#[\Override]
public function boot(IBootContext $context): void {
- $context->injectFn($this->registerNavigationEntry(...));
- }
-
- /**
- * Registers the navigation entry for the profile app in the user settings.
- * Needed as the href is dynamic and thus we cannot use the appinfo/info.xml
- */
- public function registerNavigationEntry(
- INavigationManager $navigationManager,
- IUserSession $userSession,
- IURLGenerator $urlGenerator,
- ): void {
- if (!$userSession->isLoggedIn()) {
- return;
- }
-
- $l = Server::get(IFactory::class)->get('profile');
- // Profile
- $navigationManager->add([
- 'type' => 'settings',
- 'id' => 'profile',
- 'order' => 1,
- 'href' => $urlGenerator->linkToRoute(
- 'profile.ProfilePage.index',
- ['targetUserId' => $userSession->getUser()->getUID()],
- ),
- 'name' => $l->t('View profile'),
- ]);
}
}
diff --git a/apps/profile/lib/Listener/LoadAdditionalEntriesListener.php b/apps/profile/lib/Listener/LoadAdditionalEntriesListener.php
new file mode 100644
index 0000000000000..e6edfed1fa1c7
--- /dev/null
+++ b/apps/profile/lib/Listener/LoadAdditionalEntriesListener.php
@@ -0,0 +1,67 @@
+ */
+class LoadAdditionalEntriesListener implements IEventListener {
+
+ public function __construct(
+ private readonly IL10N $l10n,
+ private readonly IAppManager $appManger,
+ private readonly INavigationManager $navigationManager,
+ private readonly IURLGenerator $urlGenerator,
+ private readonly IUserSession $userSession,
+ ) {
+ }
+
+ #[\Override]
+ public function handle(Event $event): void {
+ if (!($event instanceof LoadAdditionalEntriesEvent)) {
+ return;
+ }
+
+ if (!$this->userSession->isLoggedIn()) {
+ return;
+ }
+
+ if ($this->appManger->isAppLoaded(Application::APP_ID)) {
+ $this->registerNavigationEntries();
+ }
+
+ }
+
+ private function registerNavigationEntries(): void {
+ $user = $this->userSession->getUser();
+ if ($user === null) {
+ return;
+ }
+
+ $this->navigationManager->add([
+ 'type' => 'settings',
+ 'id' => 'profile',
+ 'order' => 1,
+ 'href' => $this->urlGenerator->linkToRoute(
+ 'profile.ProfilePage.index',
+ ['targetUserId' => $user->getUID()],
+ ),
+ 'name' => $this->l10n->t('View profile'),
+ ]);
+ }
+
+}
diff --git a/apps/settings/composer/composer/autoload_classmap.php b/apps/settings/composer/composer/autoload_classmap.php
index cfa1b1ec0c11d..e75a76b324ed2 100644
--- a/apps/settings/composer/composer/autoload_classmap.php
+++ b/apps/settings/composer/composer/autoload_classmap.php
@@ -41,6 +41,7 @@
'OCA\\Settings\\Hooks' => $baseDir . '/../lib/Hooks.php',
'OCA\\Settings\\Listener\\AppPasswordCreatedActivityListener' => $baseDir . '/../lib/Listener/AppPasswordCreatedActivityListener.php',
'OCA\\Settings\\Listener\\GroupRemovedListener' => $baseDir . '/../lib/Listener/GroupRemovedListener.php',
+ 'OCA\\Settings\\Listener\\LoadAdditionalEntriesListener' => $baseDir . '/../lib/Listener/LoadAdditionalEntriesListener.php',
'OCA\\Settings\\Listener\\MailProviderListener' => $baseDir . '/../lib/Listener/MailProviderListener.php',
'OCA\\Settings\\Listener\\UserAddedToGroupActivityListener' => $baseDir . '/../lib/Listener/UserAddedToGroupActivityListener.php',
'OCA\\Settings\\Listener\\UserRemovedFromGroupActivityListener' => $baseDir . '/../lib/Listener/UserRemovedFromGroupActivityListener.php',
diff --git a/apps/settings/composer/composer/autoload_static.php b/apps/settings/composer/composer/autoload_static.php
index 2900792c91478..cd2a0c902d43b 100644
--- a/apps/settings/composer/composer/autoload_static.php
+++ b/apps/settings/composer/composer/autoload_static.php
@@ -56,6 +56,7 @@ class ComposerStaticInitSettings
'OCA\\Settings\\Hooks' => __DIR__ . '/..' . '/../lib/Hooks.php',
'OCA\\Settings\\Listener\\AppPasswordCreatedActivityListener' => __DIR__ . '/..' . '/../lib/Listener/AppPasswordCreatedActivityListener.php',
'OCA\\Settings\\Listener\\GroupRemovedListener' => __DIR__ . '/..' . '/../lib/Listener/GroupRemovedListener.php',
+ 'OCA\\Settings\\Listener\\LoadAdditionalEntriesListener' => __DIR__ . '/..' . '/../lib/Listener/LoadAdditionalEntriesListener.php',
'OCA\\Settings\\Listener\\MailProviderListener' => __DIR__ . '/..' . '/../lib/Listener/MailProviderListener.php',
'OCA\\Settings\\Listener\\UserAddedToGroupActivityListener' => __DIR__ . '/..' . '/../lib/Listener/UserAddedToGroupActivityListener.php',
'OCA\\Settings\\Listener\\UserRemovedFromGroupActivityListener' => __DIR__ . '/..' . '/../lib/Listener/UserRemovedFromGroupActivityListener.php',
diff --git a/apps/settings/lib/AppInfo/Application.php b/apps/settings/lib/AppInfo/Application.php
index 09ef7c54bd966..d533f687ed049 100644
--- a/apps/settings/lib/AppInfo/Application.php
+++ b/apps/settings/lib/AppInfo/Application.php
@@ -17,6 +17,7 @@
use OCA\Settings\Hooks;
use OCA\Settings\Listener\AppPasswordCreatedActivityListener;
use OCA\Settings\Listener\GroupRemovedListener;
+use OCA\Settings\Listener\LoadAdditionalEntriesListener;
use OCA\Settings\Listener\MailProviderListener;
use OCA\Settings\Listener\UserAddedToGroupActivityListener;
use OCA\Settings\Listener\UserRemovedFromGroupActivityListener;
@@ -91,14 +92,11 @@
use OCP\Group\Events\GroupDeletedEvent;
use OCP\Group\Events\UserAddedEvent;
use OCP\Group\Events\UserRemovedEvent;
-use OCP\Group\ISubAdmin;
use OCP\IConfig;
-use OCP\IGroupManager;
-use OCP\INavigationManager;
use OCP\IURLGenerator;
-use OCP\IUserSession;
use OCP\L10N\IFactory;
use OCP\Mail\IMailer;
+use OCP\Navigation\Events\LoadAdditionalEntriesEvent;
use OCP\Security\ICrypto;
use OCP\Security\ISecureRandom;
use OCP\Server;
@@ -134,6 +132,7 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(GroupDeletedEvent::class, GroupRemovedListener::class);
$context->registerEventListener(PasswordUpdatedEvent::class, Hooks::class);
$context->registerEventListener(UserChangedEvent::class, Hooks::class);
+ $context->registerEventListener(LoadAdditionalEntriesEvent::class, LoadAdditionalEntriesListener::class);
// Register Mail Provider listeners
$context->registerEventListener(DeclarativeSettingsGetValueEvent::class, MailProviderListener::class);
@@ -231,93 +230,5 @@ public function register(IRegistrationContext $context): void {
#[\Override]
public function boot(IBootContext $context): void {
- $context->injectFn($this->registerNavigationEntries(...));
- }
-
- /**
- * Registers the navigation entries for the user settings.
- * Needed as some entries are dynamic and thus we cannot use the appinfo/info.xml
- *
- * Registers the following entries:
- * - Appearance and accessibility
- * - Personal settings (named "Settings" for non-admins)
- * - Accounts (only for subadmins)
- * - Help & privacy (conditionally enabled based on config)
- */
- public function registerNavigationEntries(
- INavigationManager $navigationManager,
- IURLGenerator $urlGenerator,
- IUserSession $userSession,
- IConfig $config,
- ): void {
- if ($userSession->getUser() === null) {
- return;
- }
-
- $l = Server::get(IFactory::class)
- ->get('settings');
- $groupManager = Server::get(IGroupManager::class);
- $subAdmin = Server::get(ISubAdmin::class);
- $isAdmin = $groupManager->isAdmin($userSession->getUser()->getUID());
- $isSubAdmin = $subAdmin->isSubAdmin($userSession->getUser());
-
- // Accessibility settings - the URL is dynamic (route parameters) which is currently not supported by appinfo.xml
- $navigationManager->add([
- 'type' => 'settings',
- 'id' => 'accessibility_settings',
- 'order' => 2,
- 'href' => $urlGenerator->linkToRoute('settings.PersonalSettings.index', ['section' => 'theming']),
- 'name' => $l->t('Appearance and accessibility'),
- 'icon' => $urlGenerator->imagePath('theming', 'accessibility-dark.svg'),
- ]);
-
- // Personal settings - this entry is dynamic so we cannot use appinfo
- $navigationManager->add([
- 'type' => 'settings',
- 'id' => 'settings_personal',
- 'order' => 3,
- 'href' => $urlGenerator->linkToRoute('settings.PersonalSettings.index'),
- 'name' => $isAdmin
- ? $l->t('Personal settings')
- : $l->t('Settings'),
- 'icon' => $isAdmin
- ? $urlGenerator->imagePath('settings', 'personal.svg')
- : $urlGenerator->imagePath('settings', 'admin.svg'),
- ]);
-
- if ($isAdmin) {
- $navigationManager->add([
- 'type' => 'settings',
- 'id' => 'settings_administration',
- 'order' => 4,
- 'href' => $urlGenerator->linkToRoute('settings.adminSettings.index'),
- 'name' => $l->t('Administration settings'),
- 'icon' => $urlGenerator->imagePath('settings', 'admin.svg'),
- ]);
- }
-
- // User management is conditionally enabled for subadmins, but appinfo currently only supports full admins
- if ($isSubAdmin) {
- $navigationManager->add([
- 'type' => 'settings',
- 'id' => 'core_users',
- 'order' => 6,
- 'href' => $urlGenerator->linkToRoute('settings.Users.usersList'),
- 'name' => $l->t('Accounts'),
- 'icon' => $urlGenerator->imagePath('settings', 'users.svg'),
- ]);
- }
-
- // conditionally enabled navigation entry
- if ($config->getSystemValueBool('knowledgebaseenabled', true)) {
- $navigationManager->add([
- 'type' => 'settings',
- 'id' => 'help',
- 'order' => 99998,
- 'href' => $urlGenerator->linkToRoute('settings.Help.help'),
- 'name' => $l->t('Help & privacy'),
- 'icon' => $urlGenerator->imagePath('settings', 'help.svg'),
- ]);
- }
}
}
diff --git a/apps/settings/lib/Listener/LoadAdditionalEntriesListener.php b/apps/settings/lib/Listener/LoadAdditionalEntriesListener.php
new file mode 100644
index 0000000000000..3d653439708bd
--- /dev/null
+++ b/apps/settings/lib/Listener/LoadAdditionalEntriesListener.php
@@ -0,0 +1,134 @@
+ */
+class LoadAdditionalEntriesListener implements IEventListener {
+
+ public function __construct(
+ private readonly IConfig $config,
+ private readonly IL10N $l10n,
+ private readonly IAppManager $appManger,
+ private readonly INavigationManager $navigationManager,
+ private readonly IURLGenerator $urlGenerator,
+ private readonly IUserSession $userSession,
+ private readonly IGroupManager $groupManager,
+ private readonly ISubAdmin $subAdmin,
+ ) {
+ }
+
+ #[\Override]
+ public function handle(Event $event): void {
+ if (!($event instanceof LoadAdditionalEntriesEvent)) {
+ return;
+ }
+
+ if (!$this->userSession->isLoggedIn()) {
+ return;
+ }
+
+ if ($this->appManger->isAppLoaded(Application::APP_ID)) {
+ $this->registerNavigationEntries();
+ }
+
+ }
+
+ /**
+ * Registers the navigation entries for the user settings.
+ * Needed as some entries are dynamic and thus we cannot use the appinfo/info.xml
+ *
+ * Registers the following entries:
+ * - Appearance and accessibility
+ * - Personal settings (named "Settings" for non-admins)
+ * - Accounts (only for subadmins)
+ * - Help & privacy (conditionally enabled based on config)
+ */
+ private function registerNavigationEntries(): void {
+ $user = $this->userSession->getUser();
+ if ($user === null) {
+ return;
+ }
+
+ $isAdmin = $this->groupManager->isAdmin($user->getUID());
+ $isSubAdmin = $this->subAdmin->isSubAdmin($user);
+
+ // Accessibility settings - the URL is dynamic (route parameters) which is currently not supported by appinfo.xml
+ $this->navigationManager->add([
+ 'type' => 'settings',
+ 'id' => 'accessibility_settings',
+ 'order' => 2,
+ 'href' => $this->urlGenerator->linkToRoute('settings.PersonalSettings.index', ['section' => 'theming']),
+ 'name' => $this->l10n->t('Appearance and accessibility'),
+ 'icon' => $this->urlGenerator->imagePath('theming', 'accessibility-dark.svg'),
+ ]);
+
+ // Personal settings - this entry is dynamic so we cannot use appinfo
+ $this->navigationManager->add([
+ 'type' => 'settings',
+ 'id' => 'settings_personal',
+ 'order' => 3,
+ 'href' => $this->urlGenerator->linkToRoute('settings.PersonalSettings.index'),
+ 'name' => $isAdmin
+ ? $this->l10n->t('Personal settings')
+ : $this->l10n->t('Settings'),
+ 'icon' => $isAdmin
+ ? $this->urlGenerator->imagePath('settings', 'personal.svg')
+ : $this->urlGenerator->imagePath('settings', 'admin.svg'),
+ ]);
+
+ if ($isAdmin) {
+ $this->navigationManager->add([
+ 'type' => 'settings',
+ 'id' => 'settings_administration',
+ 'order' => 4,
+ 'href' => $this->urlGenerator->linkToRoute('settings.adminSettings.index'),
+ 'name' => $this->l10n->t('Administration settings'),
+ 'icon' => $this->urlGenerator->imagePath('settings', 'admin.svg'),
+ ]);
+ }
+
+ // User management is conditionally enabled for subadmins, but appinfo currently only supports full admins
+ if ($isSubAdmin) {
+ $this->navigationManager->add([
+ 'type' => 'settings',
+ 'id' => 'core_users',
+ 'order' => 6,
+ 'href' => $this->urlGenerator->linkToRoute('settings.Users.usersList'),
+ 'name' => $this->l10n->t('Accounts'),
+ 'icon' => $this->urlGenerator->imagePath('settings', 'users.svg'),
+ ]);
+ }
+
+ // conditionally enabled navigation entry
+ if ($this->config->getSystemValueBool('knowledgebaseenabled', true)) {
+ $this->navigationManager->add([
+ 'type' => 'settings',
+ 'id' => 'help',
+ 'order' => 99998,
+ 'href' => $this->urlGenerator->linkToRoute('settings.Help.help'),
+ 'name' => $this->l10n->t('Help & privacy'),
+ 'icon' => $this->urlGenerator->imagePath('settings', 'help.svg'),
+ ]);
+ }
+ }
+
+}
diff --git a/core/AppInfo/Application.php b/core/AppInfo/Application.php
index 89ca4eb1ccf3a..3e9b7189e7fc1 100644
--- a/core/AppInfo/Application.php
+++ b/core/AppInfo/Application.php
@@ -21,6 +21,7 @@
use OC\Core\Listener\AddMissingIndicesListener;
use OC\Core\Listener\AddMissingPrimaryKeyListener;
use OC\Core\Listener\BeforeTemplateRenderedListener;
+use OC\Core\Listener\LoadAdditionalEntriesListener;
use OC\Core\Listener\PasswordUpdatedListener;
use OC\Core\Notification\CoreNotifier;
use OC\OCM\OCMDiscoveryHandler;
@@ -34,11 +35,7 @@
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\DB\Events\AddMissingIndicesEvent;
use OCP\DB\Events\AddMissingPrimaryKeyEvent;
-use OCP\INavigationManager;
-use OCP\IURLGenerator;
-use OCP\IUserSession;
-use OCP\L10N\IFactory;
-use OCP\Server;
+use OCP\Navigation\Events\LoadAdditionalEntriesEvent;
use OCP\User\Events\BeforeUserDeletedEvent;
use OCP\User\Events\PasswordUpdatedEvent;
use OCP\User\Events\UserDeletedEvent;
@@ -75,6 +72,7 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(AddMissingPrimaryKeyEvent::class, AddMissingPrimaryKeyListener::class);
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
$context->registerEventListener(BeforeLoginTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
+ $context->registerEventListener(LoadAdditionalEntriesEvent::class, LoadAdditionalEntriesListener::class);
$context->registerEventListener(RemoteWipeStarted::class, RemoteWipeActivityListener::class);
$context->registerEventListener(RemoteWipeStarted::class, RemoteWipeNotificationsListener::class);
$context->registerEventListener(RemoteWipeStarted::class, RemoteWipeEmailListener::class);
@@ -101,36 +99,6 @@ public function register(IRegistrationContext $context): void {
#[\Override]
public function boot(IBootContext $context): void {
- $context->injectFn($this->registerNavigationEntries(...));
- }
-
- /**
- * Registers the navigation entries for the core app:
- * - The logout button in the settings menu
- */
- public function registerNavigationEntries(
- INavigationManager $navigationManager,
- IUserSession $userSession,
- IURLGenerator $urlGenerator,
- ): void {
- if (!$userSession->isLoggedIn()) {
- return;
- }
-
- $l = Server::get(IFactory::class)->get('core');
-
- // Register the logout button in the user settings
- $logoutUrl = \OC_User::getLogoutUrl($urlGenerator);
- if ($logoutUrl !== '') {
- $navigationManager->add([
- 'type' => 'settings',
- 'id' => 'logout',
- 'order' => 99999,
- 'href' => $logoutUrl,
- 'name' => $l->t('Log out'),
- 'icon' => $urlGenerator->imagePath('core', 'actions/logout.svg'),
- ]);
- }
}
}
diff --git a/core/Listener/LoadAdditionalEntriesListener.php b/core/Listener/LoadAdditionalEntriesListener.php
new file mode 100644
index 0000000000000..1e4a799f7db7b
--- /dev/null
+++ b/core/Listener/LoadAdditionalEntriesListener.php
@@ -0,0 +1,71 @@
+ */
+class LoadAdditionalEntriesListener implements IEventListener {
+ private readonly IL10N $l10n;
+
+ public function __construct(
+ public readonly IFactory $l10nFactory,
+ private readonly IAppManager $appManger,
+ private readonly INavigationManager $navigationManager,
+ private readonly IURLGenerator $urlGenerator,
+ private readonly IUserSession $userSession,
+ ) {
+ $this->l10n = $this->l10nFactory->get('core');
+ }
+
+ #[\Override]
+ public function handle(Event $event): void {
+ if (!($event instanceof LoadAdditionalEntriesEvent)) {
+ return;
+ }
+
+ if (!$this->userSession->isLoggedIn()) {
+ return;
+ }
+
+ if ($this->appManger->isAppLoaded(Application::APP_ID)) {
+ $this->registerNavigationEntries();
+ }
+
+ }
+
+ /**
+ * Registers the navigation entries for the core app:
+ * - The logout button in the settings menu
+ */
+ private function registerNavigationEntries(): void {
+ // Register the logout button in the user settings
+ $logoutUrl = \OC_User::getLogoutUrl($this->urlGenerator);
+ if ($logoutUrl !== '') {
+ $this->navigationManager->add([
+ 'type' => 'settings',
+ 'id' => 'logout',
+ 'order' => 99999,
+ 'href' => $logoutUrl,
+ 'name' => $this->l10n->t('Log out'),
+ 'icon' => $this->urlGenerator->imagePath('core', 'actions/logout.svg'),
+ ]);
+ }
+ }
+
+}
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index fd65df519e729..a9575d2eb5a5d 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -1523,6 +1523,7 @@
'OC\\Core\\Listener\\BeforeMessageLoggedEventListener' => $baseDir . '/core/Listener/BeforeMessageLoggedEventListener.php',
'OC\\Core\\Listener\\BeforeTemplateRenderedListener' => $baseDir . '/core/Listener/BeforeTemplateRenderedListener.php',
'OC\\Core\\Listener\\FeedBackHandler' => $baseDir . '/core/Listener/FeedBackHandler.php',
+ 'OC\\Core\\Listener\\LoadAdditionalEntriesListener' => $baseDir . '/core/Listener/LoadAdditionalEntriesListener.php',
'OC\\Core\\Listener\\PasswordUpdatedListener' => $baseDir . '/core/Listener/PasswordUpdatedListener.php',
'OC\\Core\\Middleware\\TwoFactorMiddleware' => $baseDir . '/core/Middleware/TwoFactorMiddleware.php',
'OC\\Core\\Migrations\\Version13000Date20170705121758' => $baseDir . '/core/Migrations/Version13000Date20170705121758.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 046195ba125ef..981e83803f7cc 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -1564,6 +1564,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\Listener\\BeforeMessageLoggedEventListener' => __DIR__ . '/../../..' . '/core/Listener/BeforeMessageLoggedEventListener.php',
'OC\\Core\\Listener\\BeforeTemplateRenderedListener' => __DIR__ . '/../../..' . '/core/Listener/BeforeTemplateRenderedListener.php',
'OC\\Core\\Listener\\FeedBackHandler' => __DIR__ . '/../../..' . '/core/Listener/FeedBackHandler.php',
+ 'OC\\Core\\Listener\\LoadAdditionalEntriesListener' => __DIR__ . '/../../..' . '/core/Listener/LoadAdditionalEntriesListener.php',
'OC\\Core\\Listener\\PasswordUpdatedListener' => __DIR__ . '/../../..' . '/core/Listener/PasswordUpdatedListener.php',
'OC\\Core\\Middleware\\TwoFactorMiddleware' => __DIR__ . '/../../..' . '/core/Middleware/TwoFactorMiddleware.php',
'OC\\Core\\Migrations\\Version13000Date20170705121758' => __DIR__ . '/../../..' . '/core/Migrations/Version13000Date20170705121758.php',
diff --git a/lib/private/NavigationManager.php b/lib/private/NavigationManager.php
index 468e2617d67f0..c21dc607c61f8 100644
--- a/lib/private/NavigationManager.php
+++ b/lib/private/NavigationManager.php
@@ -39,6 +39,7 @@ class NavigationManager implements INavigationManager {
private ?array $customAppOrder = null;
/** List of loaded app info */
private array $loadedAppInfo = [];
+ private bool $additionalEntriesLoaded = false;
public function __construct(
protected IAppManager $appManager,
@@ -166,10 +167,15 @@ private function proceedNavigation(array $list, string $type): array {
/**
* removes all the entries
*/
- public function clear(bool $loadDefaultLinks = true): void {
+ public function clear(bool $resetInit = true): void {
$this->entries = [];
$this->closureEntries = [];
- $this->init = !$loadDefaultLinks;
+
+ if ($resetInit) {
+ $this->loadedAppInfo = [];
+ $this->additionalEntriesLoaded = false;
+ $this->init = false;
+ }
}
#[Override]
@@ -206,17 +212,28 @@ private function init(): void {
* Resolve the app navigation entries from closures and info.xml files.
*/
private function resolveAppNavigationEntries(): void {
- // Resolve app navigation closures
- while ($c = array_pop($this->closureEntries)) {
- $this->add($c());
- }
+ $this->resolveAppInfoEntries();
+
+ // we do not really know the current bootstrapping state
+ // but we know that the files app is always enabled and loaded when "filesystem" is loaded thus the server is ready or close-to-ready.
+ if ($this->appManager->isAppLoaded('files')) {
+ // Resolve app navigation closures
+ while ($c = array_pop($this->closureEntries)) {
+ $this->add($c());
+ }
- // Resolve dynamically added navigation entries via event listeners
- if ($this->loadedAppInfo === []) {
- $this->eventDispatcher->dispatchTyped(new LoadAdditionalEntriesEvent());
+ // Resolve dynamically added navigation entries via event listeners
+ if (!$this->additionalEntriesLoaded) {
+ $this->additionalEntriesLoaded = true;
+ $this->eventDispatcher->dispatchTyped(new LoadAdditionalEntriesEvent());
+ }
}
+ }
- // Resolve classic info.xml based navigation entries
+ /**
+ * Resolve classic info.xml based navigation entires
+ */
+ private function resolveAppInfoEntries(): void {
if ($this->userSession->isLoggedIn()) {
$user = $this->userSession->getUser();
$apps = $this->appManager->getEnabledAppsForUser($user);
@@ -224,17 +241,21 @@ private function resolveAppNavigationEntries(): void {
$apps = $this->appManager->getEnabledApps();
}
- foreach ($apps as $app) {
- // skip already loaded apps
- if (in_array($app, $this->loadedAppInfo)) {
- continue;
- }
+ $appsToLoad = array_diff($apps, $this->loadedAppInfo);
+ $appsToLoad = array_filter($appsToLoad, $this->appManager->isAppLoaded(...));
+ if ($appsToLoad === []) {
+ return;
+ }
+ foreach ($appsToLoad as $app) {
// load plugins and collections from info.xml
$info = $this->appManager->getAppInfo($app);
if (!isset($info['navigations']['navigation'])) {
+ // this app does not have any navigation entries, skip it
+ $this->loadedAppInfo[] = $app;
continue;
}
+
foreach ($info['navigations']['navigation'] as $key => $nav) {
$nav['type'] = $nav['type'] ?? 'link';
if (!isset($nav['name'])) {
@@ -250,8 +271,11 @@ private function resolveAppNavigationEntries(): void {
}
$id = $nav['id'] ?? $app . ($key === 0 ? '' : $key);
$order = $nav['order'] ?? 100;
- $type = $nav['type'];
- $route = !empty($nav['route']) ? $this->urlGenerator->linkToRoute($nav['route']) : '';
+ $type = $nav['type'] ?? 'link';
+ $route = $nav['route'] ?? '';
+ if ($route !== '') {
+ $route = $this->urlGenerator->linkToRoute($route);
+ }
$icon = $nav['icon'] ?? null;
if ($icon !== null) {
try {
diff --git a/tests/lib/NavigationManagerTest.php b/tests/lib/NavigationManagerTest.php
index 1b2805f9c0860..09ccfbc0c7d03 100644
--- a/tests/lib/NavigationManagerTest.php
+++ b/tests/lib/NavigationManagerTest.php
@@ -148,6 +148,12 @@ public function testAddClosure(array $entry, array $expectedEntry): void {
global $testAddClosureNumberOfCalls;
$testAddClosureNumberOfCalls = 0;
+ $this->appManager->expects($this->atLeastOnce())
+ ->method('isAppLoaded')
+ ->willReturnMap([
+ ['files', true],
+ ]);
+
$this->navigationManager->add(function () use ($entry) {
global $testAddClosureNumberOfCalls;
$testAddClosureNumberOfCalls++;
@@ -233,6 +239,12 @@ public function testWithAppManager($expected, $navigation, $isAdmin = false): vo
->method('getAppInfo')
->with('test')
->willReturn($navigation);
+ $this->appManager->expects($this->any())
+ ->method('isAppLoaded')
+ ->willReturnMap([
+ ['test', true],
+ ['files', true],
+ ]);
$this->urlGenerator->expects($this->any())
->method('imagePath')
->willReturnCallback(function ($appName, $file) {
@@ -259,7 +271,7 @@ public function testWithAppManager($expected, $navigation, $isAdmin = false): vo
$this->groupManager->expects($this->any())->method('isAdmin')->willReturn($isAdmin);
$this->navigationManager->clear();
- $this->dispatcher->expects($this->once())
+ $this->dispatcher->expects($this->atLeastOnce())
->method('dispatchTyped')
->willReturnCallback(function ($event): void {
$this->assertInstanceOf(LoadAdditionalEntriesEvent::class, $event);
@@ -427,8 +439,20 @@ function (string $userId, string $appName, string $key, mixed $default = '') use
->method('isEnabledForUser')
->with('theming')
->willReturn(true);
- $this->appManager->expects($this->once())->method('getAppInfo')->with('test')->willReturn($navigation);
- $this->appManager->expects($this->once())->method('getAppIcon')->with('test')->willReturn('/apps/test/img/app.svg');
+ $this->appManager->expects($this->once())
+ ->method('getAppIcon')
+ ->with('test')
+ ->willReturn('/apps/test/img/app.svg');
+ $this->appManager->expects($this->once())
+ ->method('getAppInfo')
+ ->with('test')
+ ->willReturn($navigation);
+ $this->appManager->expects($this->atLeastOnce())
+ ->method('isAppLoaded')
+ ->willReturnMap([
+ ['test', true],
+ ['files', true],
+ ]);
$this->l10nFac->expects($this->any())->method('get')->willReturn($l);
$this->urlGenerator->expects($this->any())->method('imagePath')->willReturnCallback(function ($appName, $file) {
return "/apps/$appName/img/$file";
@@ -630,7 +654,13 @@ public function testGetDefaultEntryIdForUser(string $defaultApps, string $userDe
];
});
- $this->appManager->method('getEnabledApps')->willReturn([]);
+ $this->appManager->method('getEnabledApps')->willReturn(['files']);
+ $this->appManager->expects($this->atLeastOnce())
+ ->method('isAppLoaded')
+ ->willReturnMap([
+ ['test', true],
+ ['files', true],
+ ]);
$user = $this->createMock(IUser::class);
$user->method('getUID')->willReturn('user1');