Skip to content

Commit 442efad

Browse files
Merge pull request #57522 from nextcloud/feature/add_postinstall_event
2 parents 61a9fe6 + abe86a9 commit 442efad

6 files changed

Lines changed: 184 additions & 1 deletion

File tree

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,7 @@
649649
'OCP\\IUserManager' => $baseDir . '/lib/public/IUserManager.php',
650650
'OCP\\IUserSession' => $baseDir . '/lib/public/IUserSession.php',
651651
'OCP\\Image' => $baseDir . '/lib/public/Image.php',
652+
'OCP\\Install\\Events\\InstallationCompletedEvent' => $baseDir . '/lib/public/Install/Events/InstallationCompletedEvent.php',
652653
'OCP\\L10N\\IFactory' => $baseDir . '/lib/public/L10N/IFactory.php',
653654
'OCP\\L10N\\ILanguageIterator' => $baseDir . '/lib/public/L10N/ILanguageIterator.php',
654655
'OCP\\LDAP\\IDeletionFlagSupport' => $baseDir . '/lib/public/LDAP/IDeletionFlagSupport.php',

lib/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
690690
'OCP\\IUserManager' => __DIR__ . '/../../..' . '/lib/public/IUserManager.php',
691691
'OCP\\IUserSession' => __DIR__ . '/../../..' . '/lib/public/IUserSession.php',
692692
'OCP\\Image' => __DIR__ . '/../../..' . '/lib/public/Image.php',
693+
'OCP\\Install\\Events\\InstallationCompletedEvent' => __DIR__ . '/../../..' . '/lib/public/Install/Events/InstallationCompletedEvent.php',
693694
'OCP\\L10N\\IFactory' => __DIR__ . '/../../..' . '/lib/public/L10N/IFactory.php',
694695
'OCP\\L10N\\ILanguageIterator' => __DIR__ . '/../../..' . '/lib/public/L10N/ILanguageIterator.php',
695696
'OCP\\LDAP\\IDeletionFlagSupport' => __DIR__ . '/../../..' . '/lib/public/LDAP/IDeletionFlagSupport.php',

lib/private/Setup.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@
2323
use OCP\AppFramework\Utility\ITimeFactory;
2424
use OCP\BackgroundJob\IJobList;
2525
use OCP\Defaults;
26+
use OCP\EventDispatcher\IEventDispatcher;
2627
use OCP\Http\Client\IClientService;
2728
use OCP\IAppConfig;
2829
use OCP\IConfig;
2930
use OCP\IGroup;
3031
use OCP\IGroupManager;
3132
use OCP\IL10N;
33+
use OCP\Install\Events\InstallationCompletedEvent;
3234
use OCP\IRequest;
3335
use OCP\IURLGenerator;
3436
use OCP\IUserManager;
@@ -51,6 +53,7 @@ public function __construct(
5153
protected LoggerInterface $logger,
5254
protected ISecureRandom $random,
5355
protected Installer $installer,
56+
protected IEventDispatcher $eventDispatcher,
5457
) {
5558
$this->l10n = $l10nFactory->get('lib');
5659
}
@@ -495,6 +498,13 @@ public function install(array $options, ?IOutput $output = null): array {
495498
}
496499
}
497500

501+
// Dispatch installation completed event
502+
$adminUsername = !$disableAdminUser ? ($options['adminlogin'] ?? null) : null;
503+
$adminEmail = !empty($options['adminemail']) ? $options['adminemail'] : null;
504+
$this->eventDispatcher->dispatchTyped(
505+
new InstallationCompletedEvent($dataDir, $adminUsername, $adminEmail)
506+
);
507+
498508
return $error;
499509
}
500510

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
namespace OCP\Install\Events;
10+
11+
use OCP\EventDispatcher\Event;
12+
13+
/**
14+
* Emitted when the Nextcloud installation has been completed successfully.
15+
*
16+
* This event is dispatched after:
17+
* - The database has been configured and migrations have run
18+
* - The admin user has been created (if applicable)
19+
* - Default apps have been installed
20+
* - Background jobs have been configured
21+
* - The system has been marked as installed
22+
*
23+
* Apps can listen to this event to perform additional actions after installation,
24+
* such as:
25+
* - Sending notification emails
26+
* - Triggering external APIs
27+
* - Initializing app-specific data
28+
* - Setting up integrations
29+
*
30+
* @since 33.0.0
31+
*/
32+
class InstallationCompletedEvent extends Event {
33+
/**
34+
* @since 33.0.0
35+
*/
36+
public function __construct(
37+
private string $dataDirectory,
38+
private ?string $adminUsername = null,
39+
private ?string $adminEmail = null,
40+
) {
41+
parent::__construct();
42+
}
43+
44+
/**
45+
* Get the configured data directory path
46+
*
47+
* @since 33.0.0
48+
*/
49+
public function getDataDirectory(): string {
50+
return $this->dataDirectory;
51+
}
52+
53+
/**
54+
* Get the admin username if an admin user was created
55+
*
56+
* @since 33.0.0
57+
*/
58+
public function getAdminUsername(): ?string {
59+
return $this->adminUsername;
60+
}
61+
62+
/**
63+
* Get the admin email if configured
64+
*
65+
* @since 33.0.0
66+
*/
67+
public function getAdminEmail(): ?string {
68+
return $this->adminEmail;
69+
}
70+
71+
/**
72+
* Check if an admin user was created during installation
73+
*
74+
* @since 33.0.0
75+
*/
76+
public function hasAdminUser(): bool {
77+
return $this->adminUsername !== null;
78+
}
79+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace Test\Install\Events;
11+
12+
use OCP\Install\Events\InstallationCompletedEvent;
13+
14+
class InstallationCompletedEventTest extends \Test\TestCase {
15+
public function testConstructorWithAllParameters(): void {
16+
$dataDir = '/path/to/data';
17+
$adminUsername = 'admin';
18+
$adminEmail = 'admin@example.com';
19+
20+
$event = new InstallationCompletedEvent($dataDir, $adminUsername, $adminEmail);
21+
22+
$this->assertEquals($dataDir, $event->getDataDirectory());
23+
$this->assertEquals($adminUsername, $event->getAdminUsername());
24+
$this->assertEquals($adminEmail, $event->getAdminEmail());
25+
$this->assertTrue($event->hasAdminUser());
26+
}
27+
28+
public function testConstructorWithMinimalParameters(): void {
29+
$dataDir = '/path/to/data';
30+
31+
$event = new InstallationCompletedEvent($dataDir);
32+
33+
$this->assertEquals($dataDir, $event->getDataDirectory());
34+
$this->assertNull($event->getAdminUsername());
35+
$this->assertNull($event->getAdminEmail());
36+
$this->assertFalse($event->hasAdminUser());
37+
}
38+
39+
public function testConstructorWithUsernameOnly(): void {
40+
$dataDir = '/path/to/data';
41+
$adminUsername = 'admin';
42+
43+
$event = new InstallationCompletedEvent($dataDir, $adminUsername);
44+
45+
$this->assertEquals($dataDir, $event->getDataDirectory());
46+
$this->assertEquals($adminUsername, $event->getAdminUsername());
47+
$this->assertNull($event->getAdminEmail());
48+
$this->assertTrue($event->hasAdminUser());
49+
}
50+
51+
public function testConstructorWithUsernameAndEmail(): void {
52+
$dataDir = '/path/to/data';
53+
$adminUsername = 'admin';
54+
$adminEmail = 'admin@example.com';
55+
56+
$event = new InstallationCompletedEvent($dataDir, $adminUsername, $adminEmail);
57+
58+
$this->assertEquals($dataDir, $event->getDataDirectory());
59+
$this->assertEquals($adminUsername, $event->getAdminUsername());
60+
$this->assertEquals($adminEmail, $event->getAdminEmail());
61+
$this->assertTrue($event->hasAdminUser());
62+
}
63+
64+
public function testHasAdminUserReturnsFalseWhenUsernameIsNull(): void {
65+
$event = new InstallationCompletedEvent('/path/to/data', null, 'admin@example.com');
66+
67+
$this->assertFalse($event->hasAdminUser());
68+
$this->assertNull($event->getAdminUsername());
69+
$this->assertEquals('admin@example.com', $event->getAdminEmail());
70+
}
71+
72+
public function testDataDirectoryCanBeAnyString(): void {
73+
$customPath = '/custom/data/directory';
74+
$event = new InstallationCompletedEvent($customPath);
75+
76+
$this->assertEquals($customPath, $event->getDataDirectory());
77+
}
78+
79+
public function testEmailCanBeSetWithoutUsername(): void {
80+
$dataDir = '/path/to/data';
81+
$email = 'admin@example.com';
82+
83+
$event = new InstallationCompletedEvent($dataDir, null, $email);
84+
85+
$this->assertNull($event->getAdminUsername());
86+
$this->assertEquals($email, $event->getAdminEmail());
87+
$this->assertFalse($event->hasAdminUser());
88+
}
89+
}

tests/lib/SetupTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use OC\Setup;
1414
use OC\SystemConfig;
1515
use OCP\Defaults;
16+
use OCP\EventDispatcher\IEventDispatcher;
1617
use OCP\IL10N;
1718
use OCP\L10N\IFactory as IL10NFactory;
1819
use OCP\Security\ISecureRandom;
@@ -28,6 +29,7 @@ class SetupTest extends \Test\TestCase {
2829
protected LoggerInterface $logger;
2930
protected ISecureRandom $random;
3031
protected Installer $installer;
32+
protected IEventDispatcher $eventDispatcher;
3133

3234
protected function setUp(): void {
3335
parent::setUp();
@@ -42,9 +44,10 @@ protected function setUp(): void {
4244
$this->logger = $this->createMock(LoggerInterface::class);
4345
$this->random = $this->createMock(ISecureRandom::class);
4446
$this->installer = $this->createMock(Installer::class);
47+
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
4548
$this->setupClass = $this->getMockBuilder(Setup::class)
4649
->onlyMethods(['class_exists', 'is_callable', 'getAvailableDbDriversForPdo'])
47-
->setConstructorArgs([$this->config, $this->iniWrapper, $this->l10nFactory, $this->defaults, $this->logger, $this->random, $this->installer])
50+
->setConstructorArgs([$this->config, $this->iniWrapper, $this->l10nFactory, $this->defaults, $this->logger, $this->random, $this->installer, $this->eventDispatcher])
4851
->getMock();
4952
}
5053

0 commit comments

Comments
 (0)