From e4adcbd36e6ca36004d32d83a5b9d4814fe0ffde Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Thu, 22 Jan 2026 15:37:41 +0100 Subject: [PATCH 1/2] feat(install): add InstallationCompletedEvent for post-installation hooks Add InstallationCompletedEvent class in public API (OCP namespace) that provides installation details: data directory, admin username, and admin email. Event will be dispatched after successful installation. Include comprehensive unit tests covering all event scenarios. Signed-off-by: Misha M.-Kupriyanov --- .../Events/InstallationCompletedEvent.php | 79 ++++++++++++++++ .../Events/InstallationCompletedEventTest.php | 89 +++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 lib/public/Install/Events/InstallationCompletedEvent.php create mode 100644 tests/lib/Install/Events/InstallationCompletedEventTest.php diff --git a/lib/public/Install/Events/InstallationCompletedEvent.php b/lib/public/Install/Events/InstallationCompletedEvent.php new file mode 100644 index 0000000000000..7cf75d76d6fce --- /dev/null +++ b/lib/public/Install/Events/InstallationCompletedEvent.php @@ -0,0 +1,79 @@ +dataDirectory; + } + + /** + * Get the admin username if an admin user was created + * + * @since 33.0.0 + */ + public function getAdminUsername(): ?string { + return $this->adminUsername; + } + + /** + * Get the admin email if configured + * + * @since 33.0.0 + */ + public function getAdminEmail(): ?string { + return $this->adminEmail; + } + + /** + * Check if an admin user was created during installation + * + * @since 33.0.0 + */ + public function hasAdminUser(): bool { + return $this->adminUsername !== null; + } +} diff --git a/tests/lib/Install/Events/InstallationCompletedEventTest.php b/tests/lib/Install/Events/InstallationCompletedEventTest.php new file mode 100644 index 0000000000000..3a973e008b90b --- /dev/null +++ b/tests/lib/Install/Events/InstallationCompletedEventTest.php @@ -0,0 +1,89 @@ +assertEquals($dataDir, $event->getDataDirectory()); + $this->assertEquals($adminUsername, $event->getAdminUsername()); + $this->assertEquals($adminEmail, $event->getAdminEmail()); + $this->assertTrue($event->hasAdminUser()); + } + + public function testConstructorWithMinimalParameters(): void { + $dataDir = '/path/to/data'; + + $event = new InstallationCompletedEvent($dataDir); + + $this->assertEquals($dataDir, $event->getDataDirectory()); + $this->assertNull($event->getAdminUsername()); + $this->assertNull($event->getAdminEmail()); + $this->assertFalse($event->hasAdminUser()); + } + + public function testConstructorWithUsernameOnly(): void { + $dataDir = '/path/to/data'; + $adminUsername = 'admin'; + + $event = new InstallationCompletedEvent($dataDir, $adminUsername); + + $this->assertEquals($dataDir, $event->getDataDirectory()); + $this->assertEquals($adminUsername, $event->getAdminUsername()); + $this->assertNull($event->getAdminEmail()); + $this->assertTrue($event->hasAdminUser()); + } + + public function testConstructorWithUsernameAndEmail(): void { + $dataDir = '/path/to/data'; + $adminUsername = 'admin'; + $adminEmail = 'admin@example.com'; + + $event = new InstallationCompletedEvent($dataDir, $adminUsername, $adminEmail); + + $this->assertEquals($dataDir, $event->getDataDirectory()); + $this->assertEquals($adminUsername, $event->getAdminUsername()); + $this->assertEquals($adminEmail, $event->getAdminEmail()); + $this->assertTrue($event->hasAdminUser()); + } + + public function testHasAdminUserReturnsFalseWhenUsernameIsNull(): void { + $event = new InstallationCompletedEvent('/path/to/data', null, 'admin@example.com'); + + $this->assertFalse($event->hasAdminUser()); + $this->assertNull($event->getAdminUsername()); + $this->assertEquals('admin@example.com', $event->getAdminEmail()); + } + + public function testDataDirectoryCanBeAnyString(): void { + $customPath = '/custom/data/directory'; + $event = new InstallationCompletedEvent($customPath); + + $this->assertEquals($customPath, $event->getDataDirectory()); + } + + public function testEmailCanBeSetWithoutUsername(): void { + $dataDir = '/path/to/data'; + $email = 'admin@example.com'; + + $event = new InstallationCompletedEvent($dataDir, null, $email); + + $this->assertNull($event->getAdminUsername()); + $this->assertEquals($email, $event->getAdminEmail()); + $this->assertFalse($event->hasAdminUser()); + } +} From ffd4c5c4f8991158f38c76bae1e98dd3056cc697 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Thu, 22 Jan 2026 15:38:08 +0100 Subject: [PATCH 2/2] feat(install): dispatch InstallationCompletedEvent in Setup Integrate event dispatching into Setup class: - Inject IEventDispatcher dependency - Dispatch InstallationCompletedEvent after successful installation - Add Setup tests for event integration - Update composer autoload for new class Signed-off-by: Misha M.-Kupriyanov --- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + lib/private/Setup.php | 10 ++++++++++ tests/lib/SetupTest.php | 5 ++++- 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index ea981fe16150e..cedb4ac22fbde 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -649,6 +649,7 @@ 'OCP\\IUserManager' => $baseDir . '/lib/public/IUserManager.php', 'OCP\\IUserSession' => $baseDir . '/lib/public/IUserSession.php', 'OCP\\Image' => $baseDir . '/lib/public/Image.php', + 'OCP\\Install\\Events\\InstallationCompletedEvent' => $baseDir . '/lib/public/Install/Events/InstallationCompletedEvent.php', 'OCP\\L10N\\IFactory' => $baseDir . '/lib/public/L10N/IFactory.php', 'OCP\\L10N\\ILanguageIterator' => $baseDir . '/lib/public/L10N/ILanguageIterator.php', 'OCP\\LDAP\\IDeletionFlagSupport' => $baseDir . '/lib/public/LDAP/IDeletionFlagSupport.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index cd0ee28a3fb37..0e1e22d708857 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -690,6 +690,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\IUserManager' => __DIR__ . '/../../..' . '/lib/public/IUserManager.php', 'OCP\\IUserSession' => __DIR__ . '/../../..' . '/lib/public/IUserSession.php', 'OCP\\Image' => __DIR__ . '/../../..' . '/lib/public/Image.php', + 'OCP\\Install\\Events\\InstallationCompletedEvent' => __DIR__ . '/../../..' . '/lib/public/Install/Events/InstallationCompletedEvent.php', 'OCP\\L10N\\IFactory' => __DIR__ . '/../../..' . '/lib/public/L10N/IFactory.php', 'OCP\\L10N\\ILanguageIterator' => __DIR__ . '/../../..' . '/lib/public/L10N/ILanguageIterator.php', 'OCP\\LDAP\\IDeletionFlagSupport' => __DIR__ . '/../../..' . '/lib/public/LDAP/IDeletionFlagSupport.php', diff --git a/lib/private/Setup.php b/lib/private/Setup.php index 5f91dc1069261..24fe3df409679 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -23,12 +23,14 @@ use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; use OCP\Defaults; +use OCP\EventDispatcher\IEventDispatcher; use OCP\Http\Client\IClientService; use OCP\IAppConfig; use OCP\IConfig; use OCP\IGroup; use OCP\IGroupManager; use OCP\IL10N; +use OCP\Install\Events\InstallationCompletedEvent; use OCP\IRequest; use OCP\IURLGenerator; use OCP\IUserManager; @@ -51,6 +53,7 @@ public function __construct( protected LoggerInterface $logger, protected ISecureRandom $random, protected Installer $installer, + protected IEventDispatcher $eventDispatcher, ) { $this->l10n = $l10nFactory->get('lib'); } @@ -495,6 +498,13 @@ public function install(array $options, ?IOutput $output = null): array { } } + // Dispatch installation completed event + $adminUsername = !$disableAdminUser ? ($options['adminlogin'] ?? null) : null; + $adminEmail = !empty($options['adminemail']) ? $options['adminemail'] : null; + $this->eventDispatcher->dispatchTyped( + new InstallationCompletedEvent($dataDir, $adminUsername, $adminEmail) + ); + return $error; } diff --git a/tests/lib/SetupTest.php b/tests/lib/SetupTest.php index 0be7eab36f6b2..1a48803ecd65d 100644 --- a/tests/lib/SetupTest.php +++ b/tests/lib/SetupTest.php @@ -13,6 +13,7 @@ use OC\Setup; use OC\SystemConfig; use OCP\Defaults; +use OCP\EventDispatcher\IEventDispatcher; use OCP\IL10N; use OCP\L10N\IFactory as IL10NFactory; use OCP\Security\ISecureRandom; @@ -28,6 +29,7 @@ class SetupTest extends \Test\TestCase { protected LoggerInterface $logger; protected ISecureRandom $random; protected Installer $installer; + protected IEventDispatcher $eventDispatcher; protected function setUp(): void { parent::setUp(); @@ -42,9 +44,10 @@ protected function setUp(): void { $this->logger = $this->createMock(LoggerInterface::class); $this->random = $this->createMock(ISecureRandom::class); $this->installer = $this->createMock(Installer::class); + $this->eventDispatcher = $this->createMock(IEventDispatcher::class); $this->setupClass = $this->getMockBuilder(Setup::class) ->onlyMethods(['class_exists', 'is_callable', 'getAvailableDbDriversForPdo']) - ->setConstructorArgs([$this->config, $this->iniWrapper, $this->l10nFactory, $this->defaults, $this->logger, $this->random, $this->installer]) + ->setConstructorArgs([$this->config, $this->iniWrapper, $this->l10nFactory, $this->defaults, $this->logger, $this->random, $this->installer, $this->eventDispatcher]) ->getMock(); }