@@ -30,53 +30,53 @@ |
||
30 | 30 | use Psr\Log\LoggerInterface; |
31 | 31 | |
32 | 32 | class Application extends App implements IBootstrap { |
33 | - public const APP_NAME = 'updatenotification'; |
|
33 | + public const APP_NAME = 'updatenotification'; |
|
34 | 34 | |
35 | - public function __construct() { |
|
36 | - parent::__construct(self::APP_NAME, []); |
|
37 | - } |
|
35 | + public function __construct() { |
|
36 | + parent::__construct(self::APP_NAME, []); |
|
37 | + } |
|
38 | 38 | |
39 | - public function register(IRegistrationContext $context): void { |
|
40 | - $context->registerNotifierService(Notifier::class); |
|
41 | - $context->registerNotifierService(AppUpdateNotifier::class); |
|
39 | + public function register(IRegistrationContext $context): void { |
|
40 | + $context->registerNotifierService(Notifier::class); |
|
41 | + $context->registerNotifierService(AppUpdateNotifier::class); |
|
42 | 42 | |
43 | - $context->registerEventListener(AppUpdateEvent::class, AppUpdateEventListener::class); |
|
44 | - $context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedEventListener::class); |
|
45 | - } |
|
43 | + $context->registerEventListener(AppUpdateEvent::class, AppUpdateEventListener::class); |
|
44 | + $context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedEventListener::class); |
|
45 | + } |
|
46 | 46 | |
47 | - public function boot(IBootContext $context): void { |
|
48 | - $context->injectFn(function (IConfig $config, |
|
49 | - IUserSession $userSession, |
|
50 | - IAppManager $appManager, |
|
51 | - IGroupManager $groupManager, |
|
52 | - ContainerInterface $container, |
|
53 | - LoggerInterface $logger, |
|
54 | - ): void { |
|
55 | - if ($config->getSystemValue('updatechecker', true) !== true) { |
|
56 | - // Updater check is disabled |
|
57 | - return; |
|
58 | - } |
|
47 | + public function boot(IBootContext $context): void { |
|
48 | + $context->injectFn(function (IConfig $config, |
|
49 | + IUserSession $userSession, |
|
50 | + IAppManager $appManager, |
|
51 | + IGroupManager $groupManager, |
|
52 | + ContainerInterface $container, |
|
53 | + LoggerInterface $logger, |
|
54 | + ): void { |
|
55 | + if ($config->getSystemValue('updatechecker', true) !== true) { |
|
56 | + // Updater check is disabled |
|
57 | + return; |
|
58 | + } |
|
59 | 59 | |
60 | - $user = $userSession->getUser(); |
|
61 | - if (!$user instanceof IUser) { |
|
62 | - // Nothing to do for guests |
|
63 | - return; |
|
64 | - } |
|
60 | + $user = $userSession->getUser(); |
|
61 | + if (!$user instanceof IUser) { |
|
62 | + // Nothing to do for guests |
|
63 | + return; |
|
64 | + } |
|
65 | 65 | |
66 | - if (!$appManager->isEnabledForUser('notifications') && |
|
67 | - $groupManager->isAdmin($user->getUID())) { |
|
68 | - try { |
|
69 | - $updateChecker = $container->get(UpdateChecker::class); |
|
70 | - } catch (ContainerExceptionInterface $e) { |
|
71 | - $logger->error($e->getMessage(), ['exception' => $e]); |
|
72 | - return; |
|
73 | - } |
|
66 | + if (!$appManager->isEnabledForUser('notifications') && |
|
67 | + $groupManager->isAdmin($user->getUID())) { |
|
68 | + try { |
|
69 | + $updateChecker = $container->get(UpdateChecker::class); |
|
70 | + } catch (ContainerExceptionInterface $e) { |
|
71 | + $logger->error($e->getMessage(), ['exception' => $e]); |
|
72 | + return; |
|
73 | + } |
|
74 | 74 | |
75 | - if ($updateChecker->getUpdateState() !== []) { |
|
76 | - Util::addScript(self::APP_NAME, 'update-notification-legacy'); |
|
77 | - $updateChecker->setInitialState(); |
|
78 | - } |
|
79 | - } |
|
80 | - }); |
|
81 | - } |
|
75 | + if ($updateChecker->getUpdateState() !== []) { |
|
76 | + Util::addScript(self::APP_NAME, 'update-notification-legacy'); |
|
77 | + $updateChecker->setInitialState(); |
|
78 | + } |
|
79 | + } |
|
80 | + }); |
|
81 | + } |
|
82 | 82 | } |
@@ -17,51 +17,51 @@ |
||
17 | 17 | |
18 | 18 | class Check extends Command { |
19 | 19 | |
20 | - public function __construct( |
|
21 | - private AppManager $appManager, |
|
22 | - private UpdateChecker $updateChecker, |
|
23 | - private Installer $installer, |
|
24 | - ) { |
|
25 | - parent::__construct(); |
|
26 | - } |
|
20 | + public function __construct( |
|
21 | + private AppManager $appManager, |
|
22 | + private UpdateChecker $updateChecker, |
|
23 | + private Installer $installer, |
|
24 | + ) { |
|
25 | + parent::__construct(); |
|
26 | + } |
|
27 | 27 | |
28 | - protected function configure(): void { |
|
29 | - $this |
|
30 | - ->setName('update:check') |
|
31 | - ->setDescription('Check for server and app updates') |
|
32 | - ; |
|
33 | - } |
|
28 | + protected function configure(): void { |
|
29 | + $this |
|
30 | + ->setName('update:check') |
|
31 | + ->setDescription('Check for server and app updates') |
|
32 | + ; |
|
33 | + } |
|
34 | 34 | |
35 | - protected function execute(InputInterface $input, OutputInterface $output): int { |
|
36 | - $updatesAvailableCount = 0; |
|
35 | + protected function execute(InputInterface $input, OutputInterface $output): int { |
|
36 | + $updatesAvailableCount = 0; |
|
37 | 37 | |
38 | - // Server |
|
39 | - $r = $this->updateChecker->getUpdateState(); |
|
40 | - if (isset($r['updateAvailable']) && $r['updateAvailable']) { |
|
41 | - $output->writeln($r['updateVersionString'] . ' is available. Get more information on how to update at ' . $r['updateLink'] . '.'); |
|
42 | - $updatesAvailableCount += 1; |
|
43 | - } |
|
38 | + // Server |
|
39 | + $r = $this->updateChecker->getUpdateState(); |
|
40 | + if (isset($r['updateAvailable']) && $r['updateAvailable']) { |
|
41 | + $output->writeln($r['updateVersionString'] . ' is available. Get more information on how to update at ' . $r['updateLink'] . '.'); |
|
42 | + $updatesAvailableCount += 1; |
|
43 | + } |
|
44 | 44 | |
45 | 45 | |
46 | - // Apps |
|
47 | - $apps = $this->appManager->getEnabledApps(); |
|
48 | - foreach ($apps as $app) { |
|
49 | - $update = $this->installer->isUpdateAvailable($app); |
|
50 | - if ($update !== false) { |
|
51 | - $output->writeln('Update for ' . $app . ' to version ' . $update . ' is available.'); |
|
52 | - $updatesAvailableCount += 1; |
|
53 | - } |
|
54 | - } |
|
46 | + // Apps |
|
47 | + $apps = $this->appManager->getEnabledApps(); |
|
48 | + foreach ($apps as $app) { |
|
49 | + $update = $this->installer->isUpdateAvailable($app); |
|
50 | + if ($update !== false) { |
|
51 | + $output->writeln('Update for ' . $app . ' to version ' . $update . ' is available.'); |
|
52 | + $updatesAvailableCount += 1; |
|
53 | + } |
|
54 | + } |
|
55 | 55 | |
56 | - // Report summary |
|
57 | - if ($updatesAvailableCount === 0) { |
|
58 | - $output->writeln('<info>Everything up to date</info>'); |
|
59 | - } elseif ($updatesAvailableCount === 1) { |
|
60 | - $output->writeln('<comment>1 update available</comment>'); |
|
61 | - } else { |
|
62 | - $output->writeln('<comment>' . $updatesAvailableCount . ' updates available</comment>'); |
|
63 | - } |
|
56 | + // Report summary |
|
57 | + if ($updatesAvailableCount === 0) { |
|
58 | + $output->writeln('<info>Everything up to date</info>'); |
|
59 | + } elseif ($updatesAvailableCount === 1) { |
|
60 | + $output->writeln('<comment>1 update available</comment>'); |
|
61 | + } else { |
|
62 | + $output->writeln('<comment>' . $updatesAvailableCount . ' updates available</comment>'); |
|
63 | + } |
|
64 | 64 | |
65 | - return 0; |
|
66 | - } |
|
65 | + return 0; |
|
66 | + } |
|
67 | 67 | } |
@@ -26,172 +26,172 @@ |
||
26 | 26 | */ |
27 | 27 | class APIController extends OCSController { |
28 | 28 | |
29 | - protected ?string $language = null; |
|
30 | - |
|
31 | - /** |
|
32 | - * List of apps that were in the appstore but are now shipped and don't have |
|
33 | - * a compatible update available. |
|
34 | - * |
|
35 | - * @var array<string, int> |
|
36 | - */ |
|
37 | - protected array $appsShippedInFutureVersion = [ |
|
38 | - 'bruteforcesettings' => 25, |
|
39 | - 'suspicious_login' => 25, |
|
40 | - 'twofactor_totp' => 25, |
|
41 | - 'files_downloadlimit' => 29, |
|
42 | - 'twofactor_nextcloud_notification' => 30, |
|
43 | - 'app_api' => 30, |
|
44 | - ]; |
|
45 | - |
|
46 | - public function __construct( |
|
47 | - string $appName, |
|
48 | - IRequest $request, |
|
49 | - protected IConfig $config, |
|
50 | - protected IAppManager $appManager, |
|
51 | - protected AppFetcher $appFetcher, |
|
52 | - protected IFactory $l10nFactory, |
|
53 | - protected IUserSession $userSession, |
|
54 | - protected Manager $manager, |
|
55 | - ) { |
|
56 | - parent::__construct($appName, $request); |
|
57 | - } |
|
58 | - |
|
59 | - /** |
|
60 | - * List available updates for apps |
|
61 | - * |
|
62 | - * @param string $newVersion Server version to check updates for |
|
63 | - * |
|
64 | - * @return DataResponse<Http::STATUS_OK, array{missing: list<UpdateNotificationApp>, available: list<UpdateNotificationApp>}, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array{appstore_disabled: bool, already_on_latest?: bool}, array{}> |
|
65 | - * |
|
66 | - * 200: Apps returned |
|
67 | - * 404: New versions not found |
|
68 | - */ |
|
69 | - public function getAppList(string $newVersion): DataResponse { |
|
70 | - if (!$this->config->getSystemValue('appstoreenabled', true)) { |
|
71 | - return new DataResponse([ |
|
72 | - 'appstore_disabled' => true, |
|
73 | - ], Http::STATUS_NOT_FOUND); |
|
74 | - } |
|
75 | - |
|
76 | - // Get list of installed custom apps |
|
77 | - $installedApps = $this->appManager->getEnabledApps(); |
|
78 | - $installedApps = array_filter($installedApps, function ($app) { |
|
79 | - try { |
|
80 | - $this->appManager->getAppPath($app); |
|
81 | - } catch (AppPathNotFoundException $e) { |
|
82 | - return false; |
|
83 | - } |
|
84 | - return !$this->appManager->isShipped($app) && !isset($this->appsShippedInFutureVersion[$app]); |
|
85 | - }); |
|
86 | - |
|
87 | - if (empty($installedApps)) { |
|
88 | - return new DataResponse([ |
|
89 | - 'missing' => [], |
|
90 | - 'available' => [], |
|
91 | - ]); |
|
92 | - } |
|
93 | - |
|
94 | - $this->appFetcher->setVersion($newVersion, 'future-apps.json', false); |
|
95 | - |
|
96 | - // Apps available on the app store for that version |
|
97 | - $availableApps = array_map(static function (array $app): string { |
|
98 | - return $app['id']; |
|
99 | - }, $this->appFetcher->get()); |
|
100 | - |
|
101 | - if (empty($availableApps)) { |
|
102 | - return new DataResponse([ |
|
103 | - 'appstore_disabled' => false, |
|
104 | - 'already_on_latest' => false, |
|
105 | - ], Http::STATUS_NOT_FOUND); |
|
106 | - } |
|
107 | - |
|
108 | - // Ignore apps that are deployed from git |
|
109 | - $installedApps = array_filter($installedApps, function (string $appId) { |
|
110 | - try { |
|
111 | - return !file_exists($this->appManager->getAppPath($appId) . '/.git'); |
|
112 | - } catch (AppPathNotFoundException $e) { |
|
113 | - return true; |
|
114 | - } |
|
115 | - }); |
|
116 | - |
|
117 | - $missing = array_diff($installedApps, $availableApps); |
|
118 | - $missing = array_map([$this, 'getAppDetails'], $missing); |
|
119 | - sort($missing); |
|
120 | - |
|
121 | - $available = array_intersect($installedApps, $availableApps); |
|
122 | - $available = array_map([$this, 'getAppDetails'], $available); |
|
123 | - sort($available); |
|
124 | - |
|
125 | - return new DataResponse([ |
|
126 | - 'missing' => $missing, |
|
127 | - 'available' => $available, |
|
128 | - ]); |
|
129 | - } |
|
130 | - |
|
131 | - /** |
|
132 | - * Get translated app name |
|
133 | - * |
|
134 | - * @param string $appId |
|
135 | - * @return UpdateNotificationApp |
|
136 | - */ |
|
137 | - protected function getAppDetails(string $appId): array { |
|
138 | - $app = $this->appManager->getAppInfo($appId, false, $this->language); |
|
139 | - $name = $app['name'] ?? $appId; |
|
140 | - return [ |
|
141 | - 'appId' => $appId, |
|
142 | - 'appName' => $name, |
|
143 | - ]; |
|
144 | - } |
|
145 | - |
|
146 | - protected function getLanguage(): string { |
|
147 | - if ($this->language === null) { |
|
148 | - $this->language = $this->l10nFactory->getUserLanguage($this->userSession->getUser()); |
|
149 | - } |
|
150 | - return $this->language; |
|
151 | - } |
|
152 | - |
|
153 | - /** |
|
154 | - * Get changelog entry for an app |
|
155 | - * |
|
156 | - * @param string $appId App to search changelog entry for |
|
157 | - * @param string|null $version The version to search the changelog entry for (defaults to the latest installed) |
|
158 | - * |
|
159 | - * @return DataResponse<Http::STATUS_OK, array{appName: string, content: string, version: string}, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array{}, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{}, array{}> |
|
160 | - * |
|
161 | - * 200: Changelog entry returned |
|
162 | - * 400: The `version` parameter is not a valid version format |
|
163 | - * 404: No changelog found |
|
164 | - */ |
|
165 | - public function getAppChangelogEntry(string $appId, ?string $version = null): DataResponse { |
|
166 | - $version = $version ?? $this->appManager->getAppVersion($appId); |
|
167 | - // handle pre-release versions |
|
168 | - $matches = []; |
|
169 | - $result = preg_match('/^(\d+\.\d+(\.\d+)?)/', $version, $matches); |
|
170 | - if ($result === false || $result === 0) { |
|
171 | - return new DataResponse([], Http::STATUS_BAD_REQUEST); |
|
172 | - } |
|
173 | - $shortVersion = $matches[0]; |
|
174 | - |
|
175 | - $changes = $this->manager->getChangelog($appId, $shortVersion); |
|
176 | - |
|
177 | - if ($changes === null) { |
|
178 | - return new DataResponse([], Http::STATUS_NOT_FOUND); |
|
179 | - } |
|
180 | - |
|
181 | - // Remove version headline |
|
182 | - /** @var string[] */ |
|
183 | - $changes = explode("\n", $changes, 2); |
|
184 | - $changes = trim(end($changes)); |
|
185 | - |
|
186 | - // Get app info for localized app name |
|
187 | - $info = $this->appManager->getAppInfo($appId) ?? []; |
|
188 | - /** @var string */ |
|
189 | - $appName = $info['name'] ?? $appId; |
|
190 | - |
|
191 | - return new DataResponse([ |
|
192 | - 'appName' => $appName, |
|
193 | - 'content' => $changes, |
|
194 | - 'version' => $version, |
|
195 | - ]); |
|
196 | - } |
|
29 | + protected ?string $language = null; |
|
30 | + |
|
31 | + /** |
|
32 | + * List of apps that were in the appstore but are now shipped and don't have |
|
33 | + * a compatible update available. |
|
34 | + * |
|
35 | + * @var array<string, int> |
|
36 | + */ |
|
37 | + protected array $appsShippedInFutureVersion = [ |
|
38 | + 'bruteforcesettings' => 25, |
|
39 | + 'suspicious_login' => 25, |
|
40 | + 'twofactor_totp' => 25, |
|
41 | + 'files_downloadlimit' => 29, |
|
42 | + 'twofactor_nextcloud_notification' => 30, |
|
43 | + 'app_api' => 30, |
|
44 | + ]; |
|
45 | + |
|
46 | + public function __construct( |
|
47 | + string $appName, |
|
48 | + IRequest $request, |
|
49 | + protected IConfig $config, |
|
50 | + protected IAppManager $appManager, |
|
51 | + protected AppFetcher $appFetcher, |
|
52 | + protected IFactory $l10nFactory, |
|
53 | + protected IUserSession $userSession, |
|
54 | + protected Manager $manager, |
|
55 | + ) { |
|
56 | + parent::__construct($appName, $request); |
|
57 | + } |
|
58 | + |
|
59 | + /** |
|
60 | + * List available updates for apps |
|
61 | + * |
|
62 | + * @param string $newVersion Server version to check updates for |
|
63 | + * |
|
64 | + * @return DataResponse<Http::STATUS_OK, array{missing: list<UpdateNotificationApp>, available: list<UpdateNotificationApp>}, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array{appstore_disabled: bool, already_on_latest?: bool}, array{}> |
|
65 | + * |
|
66 | + * 200: Apps returned |
|
67 | + * 404: New versions not found |
|
68 | + */ |
|
69 | + public function getAppList(string $newVersion): DataResponse { |
|
70 | + if (!$this->config->getSystemValue('appstoreenabled', true)) { |
|
71 | + return new DataResponse([ |
|
72 | + 'appstore_disabled' => true, |
|
73 | + ], Http::STATUS_NOT_FOUND); |
|
74 | + } |
|
75 | + |
|
76 | + // Get list of installed custom apps |
|
77 | + $installedApps = $this->appManager->getEnabledApps(); |
|
78 | + $installedApps = array_filter($installedApps, function ($app) { |
|
79 | + try { |
|
80 | + $this->appManager->getAppPath($app); |
|
81 | + } catch (AppPathNotFoundException $e) { |
|
82 | + return false; |
|
83 | + } |
|
84 | + return !$this->appManager->isShipped($app) && !isset($this->appsShippedInFutureVersion[$app]); |
|
85 | + }); |
|
86 | + |
|
87 | + if (empty($installedApps)) { |
|
88 | + return new DataResponse([ |
|
89 | + 'missing' => [], |
|
90 | + 'available' => [], |
|
91 | + ]); |
|
92 | + } |
|
93 | + |
|
94 | + $this->appFetcher->setVersion($newVersion, 'future-apps.json', false); |
|
95 | + |
|
96 | + // Apps available on the app store for that version |
|
97 | + $availableApps = array_map(static function (array $app): string { |
|
98 | + return $app['id']; |
|
99 | + }, $this->appFetcher->get()); |
|
100 | + |
|
101 | + if (empty($availableApps)) { |
|
102 | + return new DataResponse([ |
|
103 | + 'appstore_disabled' => false, |
|
104 | + 'already_on_latest' => false, |
|
105 | + ], Http::STATUS_NOT_FOUND); |
|
106 | + } |
|
107 | + |
|
108 | + // Ignore apps that are deployed from git |
|
109 | + $installedApps = array_filter($installedApps, function (string $appId) { |
|
110 | + try { |
|
111 | + return !file_exists($this->appManager->getAppPath($appId) . '/.git'); |
|
112 | + } catch (AppPathNotFoundException $e) { |
|
113 | + return true; |
|
114 | + } |
|
115 | + }); |
|
116 | + |
|
117 | + $missing = array_diff($installedApps, $availableApps); |
|
118 | + $missing = array_map([$this, 'getAppDetails'], $missing); |
|
119 | + sort($missing); |
|
120 | + |
|
121 | + $available = array_intersect($installedApps, $availableApps); |
|
122 | + $available = array_map([$this, 'getAppDetails'], $available); |
|
123 | + sort($available); |
|
124 | + |
|
125 | + return new DataResponse([ |
|
126 | + 'missing' => $missing, |
|
127 | + 'available' => $available, |
|
128 | + ]); |
|
129 | + } |
|
130 | + |
|
131 | + /** |
|
132 | + * Get translated app name |
|
133 | + * |
|
134 | + * @param string $appId |
|
135 | + * @return UpdateNotificationApp |
|
136 | + */ |
|
137 | + protected function getAppDetails(string $appId): array { |
|
138 | + $app = $this->appManager->getAppInfo($appId, false, $this->language); |
|
139 | + $name = $app['name'] ?? $appId; |
|
140 | + return [ |
|
141 | + 'appId' => $appId, |
|
142 | + 'appName' => $name, |
|
143 | + ]; |
|
144 | + } |
|
145 | + |
|
146 | + protected function getLanguage(): string { |
|
147 | + if ($this->language === null) { |
|
148 | + $this->language = $this->l10nFactory->getUserLanguage($this->userSession->getUser()); |
|
149 | + } |
|
150 | + return $this->language; |
|
151 | + } |
|
152 | + |
|
153 | + /** |
|
154 | + * Get changelog entry for an app |
|
155 | + * |
|
156 | + * @param string $appId App to search changelog entry for |
|
157 | + * @param string|null $version The version to search the changelog entry for (defaults to the latest installed) |
|
158 | + * |
|
159 | + * @return DataResponse<Http::STATUS_OK, array{appName: string, content: string, version: string}, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array{}, array{}>|DataResponse<Http::STATUS_BAD_REQUEST, array{}, array{}> |
|
160 | + * |
|
161 | + * 200: Changelog entry returned |
|
162 | + * 400: The `version` parameter is not a valid version format |
|
163 | + * 404: No changelog found |
|
164 | + */ |
|
165 | + public function getAppChangelogEntry(string $appId, ?string $version = null): DataResponse { |
|
166 | + $version = $version ?? $this->appManager->getAppVersion($appId); |
|
167 | + // handle pre-release versions |
|
168 | + $matches = []; |
|
169 | + $result = preg_match('/^(\d+\.\d+(\.\d+)?)/', $version, $matches); |
|
170 | + if ($result === false || $result === 0) { |
|
171 | + return new DataResponse([], Http::STATUS_BAD_REQUEST); |
|
172 | + } |
|
173 | + $shortVersion = $matches[0]; |
|
174 | + |
|
175 | + $changes = $this->manager->getChangelog($appId, $shortVersion); |
|
176 | + |
|
177 | + if ($changes === null) { |
|
178 | + return new DataResponse([], Http::STATUS_NOT_FOUND); |
|
179 | + } |
|
180 | + |
|
181 | + // Remove version headline |
|
182 | + /** @var string[] */ |
|
183 | + $changes = explode("\n", $changes, 2); |
|
184 | + $changes = trim(end($changes)); |
|
185 | + |
|
186 | + // Get app info for localized app name |
|
187 | + $info = $this->appManager->getAppInfo($appId) ?? []; |
|
188 | + /** @var string */ |
|
189 | + $appName = $info['name'] ?? $appId; |
|
190 | + |
|
191 | + return new DataResponse([ |
|
192 | + 'appName' => $appName, |
|
193 | + 'content' => $changes, |
|
194 | + 'version' => $version, |
|
195 | + ]); |
|
196 | + } |
|
197 | 197 | } |
@@ -75,7 +75,7 @@ discard block |
||
75 | 75 | |
76 | 76 | // Get list of installed custom apps |
77 | 77 | $installedApps = $this->appManager->getEnabledApps(); |
78 | - $installedApps = array_filter($installedApps, function ($app) { |
|
78 | + $installedApps = array_filter($installedApps, function($app) { |
|
79 | 79 | try { |
80 | 80 | $this->appManager->getAppPath($app); |
81 | 81 | } catch (AppPathNotFoundException $e) { |
@@ -94,7 +94,7 @@ discard block |
||
94 | 94 | $this->appFetcher->setVersion($newVersion, 'future-apps.json', false); |
95 | 95 | |
96 | 96 | // Apps available on the app store for that version |
97 | - $availableApps = array_map(static function (array $app): string { |
|
97 | + $availableApps = array_map(static function(array $app): string { |
|
98 | 98 | return $app['id']; |
99 | 99 | }, $this->appFetcher->get()); |
100 | 100 | |
@@ -106,9 +106,9 @@ discard block |
||
106 | 106 | } |
107 | 107 | |
108 | 108 | // Ignore apps that are deployed from git |
109 | - $installedApps = array_filter($installedApps, function (string $appId) { |
|
109 | + $installedApps = array_filter($installedApps, function(string $appId) { |
|
110 | 110 | try { |
111 | - return !file_exists($this->appManager->getAppPath($appId) . '/.git'); |
|
111 | + return !file_exists($this->appManager->getAppPath($appId).'/.git'); |
|
112 | 112 | } catch (AppPathNotFoundException $e) { |
113 | 113 | return true; |
114 | 114 | } |
@@ -24,49 +24,49 @@ |
||
24 | 24 | |
25 | 25 | class AdminController extends Controller { |
26 | 26 | |
27 | - public function __construct( |
|
28 | - string $appName, |
|
29 | - IRequest $request, |
|
30 | - private IJobList $jobList, |
|
31 | - private ISecureRandom $secureRandom, |
|
32 | - private IConfig $config, |
|
33 | - private IAppConfig $appConfig, |
|
34 | - private ITimeFactory $timeFactory, |
|
35 | - private IL10N $l10n, |
|
36 | - ) { |
|
37 | - parent::__construct($appName, $request); |
|
38 | - } |
|
27 | + public function __construct( |
|
28 | + string $appName, |
|
29 | + IRequest $request, |
|
30 | + private IJobList $jobList, |
|
31 | + private ISecureRandom $secureRandom, |
|
32 | + private IConfig $config, |
|
33 | + private IAppConfig $appConfig, |
|
34 | + private ITimeFactory $timeFactory, |
|
35 | + private IL10N $l10n, |
|
36 | + ) { |
|
37 | + parent::__construct($appName, $request); |
|
38 | + } |
|
39 | 39 | |
40 | - private function isUpdaterEnabled(): bool { |
|
41 | - return !$this->config->getSystemValueBool('upgrade.disable-web'); |
|
42 | - } |
|
40 | + private function isUpdaterEnabled(): bool { |
|
41 | + return !$this->config->getSystemValueBool('upgrade.disable-web'); |
|
42 | + } |
|
43 | 43 | |
44 | - /** |
|
45 | - * @param string $channel |
|
46 | - * @return DataResponse |
|
47 | - */ |
|
48 | - public function setChannel(string $channel): DataResponse { |
|
49 | - Util::setChannel($channel); |
|
50 | - $this->appConfig->setValueInt('core', 'lastupdatedat', 0); |
|
51 | - return new DataResponse(['status' => 'success', 'data' => ['message' => $this->l10n->t('Channel updated')]]); |
|
52 | - } |
|
44 | + /** |
|
45 | + * @param string $channel |
|
46 | + * @return DataResponse |
|
47 | + */ |
|
48 | + public function setChannel(string $channel): DataResponse { |
|
49 | + Util::setChannel($channel); |
|
50 | + $this->appConfig->setValueInt('core', 'lastupdatedat', 0); |
|
51 | + return new DataResponse(['status' => 'success', 'data' => ['message' => $this->l10n->t('Channel updated')]]); |
|
52 | + } |
|
53 | 53 | |
54 | - /** |
|
55 | - * @return DataResponse |
|
56 | - */ |
|
57 | - public function createCredentials(): DataResponse { |
|
58 | - if (!$this->isUpdaterEnabled()) { |
|
59 | - return new DataResponse(['status' => 'error', 'message' => $this->l10n->t('Web updater is disabled')], Http::STATUS_FORBIDDEN); |
|
60 | - } |
|
54 | + /** |
|
55 | + * @return DataResponse |
|
56 | + */ |
|
57 | + public function createCredentials(): DataResponse { |
|
58 | + if (!$this->isUpdaterEnabled()) { |
|
59 | + return new DataResponse(['status' => 'error', 'message' => $this->l10n->t('Web updater is disabled')], Http::STATUS_FORBIDDEN); |
|
60 | + } |
|
61 | 61 | |
62 | - // Create a new job and store the creation date |
|
63 | - $this->jobList->add(ResetToken::class); |
|
64 | - $this->appConfig->setValueInt('core', 'updater.secret.created', $this->timeFactory->getTime()); |
|
62 | + // Create a new job and store the creation date |
|
63 | + $this->jobList->add(ResetToken::class); |
|
64 | + $this->appConfig->setValueInt('core', 'updater.secret.created', $this->timeFactory->getTime()); |
|
65 | 65 | |
66 | - // Create a new token |
|
67 | - $newToken = $this->secureRandom->generate(64); |
|
68 | - $this->config->setSystemValue('updater.secret', password_hash($newToken, PASSWORD_DEFAULT)); |
|
66 | + // Create a new token |
|
67 | + $newToken = $this->secureRandom->generate(64); |
|
68 | + $this->config->setSystemValue('updater.secret', password_hash($newToken, PASSWORD_DEFAULT)); |
|
69 | 69 | |
70 | - return new DataResponse($newToken); |
|
71 | - } |
|
70 | + return new DataResponse($newToken); |
|
71 | + } |
|
72 | 72 | } |
@@ -24,128 +24,128 @@ |
||
24 | 24 | use Psr\Log\LoggerInterface; |
25 | 25 | |
26 | 26 | class Admin implements ISettings { |
27 | - public function __construct( |
|
28 | - private IConfig $config, |
|
29 | - private IAppConfig $appConfig, |
|
30 | - private UpdateChecker $updateChecker, |
|
31 | - private IGroupManager $groupManager, |
|
32 | - private IDateTimeFormatter $dateTimeFormatter, |
|
33 | - private IFactory $l10nFactory, |
|
34 | - private IRegistry $subscriptionRegistry, |
|
35 | - private IUserManager $userManager, |
|
36 | - private LoggerInterface $logger, |
|
37 | - private IInitialState $initialState, |
|
38 | - private ServerVersion $serverVersion, |
|
39 | - ) { |
|
40 | - } |
|
41 | - |
|
42 | - public function getForm(): TemplateResponse { |
|
43 | - $lastUpdateCheckTimestamp = $this->appConfig->getValueInt('core', 'lastupdatedat'); |
|
44 | - $lastUpdateCheck = $this->dateTimeFormatter->formatDateTime($lastUpdateCheckTimestamp); |
|
45 | - |
|
46 | - $channels = [ |
|
47 | - 'daily', |
|
48 | - 'beta', |
|
49 | - 'stable', |
|
50 | - 'production', |
|
51 | - ]; |
|
52 | - $currentChannel = $this->serverVersion->getChannel(); |
|
53 | - if ($currentChannel === 'git') { |
|
54 | - $channels[] = 'git'; |
|
55 | - } |
|
56 | - |
|
57 | - $updateState = $this->updateChecker->getUpdateState(); |
|
58 | - |
|
59 | - $notifyGroups = $this->appConfig->getValueArray(Application::APP_NAME, 'notify_groups', ['admin']); |
|
60 | - |
|
61 | - $defaultUpdateServerURL = 'https://updates.nextcloud.com/updater_server/'; |
|
62 | - $updateServerURL = $this->config->getSystemValue('updater.server.url', $defaultUpdateServerURL); |
|
63 | - $defaultCustomerUpdateServerURLPrefix = 'https://updates.nextcloud.com/customers/'; |
|
64 | - |
|
65 | - $isDefaultUpdateServerURL = $updateServerURL === $defaultUpdateServerURL |
|
66 | - || strpos($updateServerURL, $defaultCustomerUpdateServerURLPrefix) === 0; |
|
67 | - |
|
68 | - $hasValidSubscription = $this->subscriptionRegistry->delegateHasValidSubscription(); |
|
69 | - |
|
70 | - $params = [ |
|
71 | - 'isNewVersionAvailable' => !empty($updateState['updateAvailable']), |
|
72 | - 'isUpdateChecked' => $lastUpdateCheckTimestamp > 0, |
|
73 | - 'lastChecked' => $lastUpdateCheck, |
|
74 | - 'currentChannel' => $currentChannel, |
|
75 | - 'channels' => $channels, |
|
76 | - 'newVersion' => empty($updateState['updateVersion']) ? '' : $updateState['updateVersion'], |
|
77 | - 'newVersionString' => empty($updateState['updateVersionString']) ? '' : $updateState['updateVersionString'], |
|
78 | - 'downloadLink' => empty($updateState['downloadLink']) ? '' : $updateState['downloadLink'], |
|
79 | - 'changes' => $this->filterChanges($updateState['changes'] ?? []), |
|
80 | - 'webUpdaterEnabled' => !$this->config->getSystemValue('upgrade.disable-web', false), |
|
81 | - 'isWebUpdaterRecommended' => $this->isWebUpdaterRecommended(), |
|
82 | - 'updaterEnabled' => empty($updateState['updaterEnabled']) ? false : $updateState['updaterEnabled'], |
|
83 | - 'versionIsEol' => empty($updateState['versionIsEol']) ? false : $updateState['versionIsEol'], |
|
84 | - 'isDefaultUpdateServerURL' => $isDefaultUpdateServerURL, |
|
85 | - 'updateServerURL' => $updateServerURL, |
|
86 | - 'notifyGroups' => $this->getSelectedGroups($notifyGroups), |
|
87 | - 'hasValidSubscription' => $hasValidSubscription, |
|
88 | - ]; |
|
89 | - $this->initialState->provideInitialState('data', $params); |
|
90 | - |
|
91 | - return new TemplateResponse('updatenotification', 'admin', [], ''); |
|
92 | - } |
|
93 | - |
|
94 | - protected function filterChanges(array $changes): array { |
|
95 | - $filtered = []; |
|
96 | - if (isset($changes['changelogURL'])) { |
|
97 | - $filtered['changelogURL'] = $changes['changelogURL']; |
|
98 | - } |
|
99 | - if (!isset($changes['whatsNew'])) { |
|
100 | - return $filtered; |
|
101 | - } |
|
102 | - |
|
103 | - $iterator = $this->l10nFactory->getLanguageIterator(); |
|
104 | - do { |
|
105 | - $lang = $iterator->current(); |
|
106 | - if (isset($changes['whatsNew'][$lang])) { |
|
107 | - $filtered['whatsNew'] = $changes['whatsNew'][$lang]; |
|
108 | - return $filtered; |
|
109 | - } |
|
110 | - $iterator->next(); |
|
111 | - } while ($lang !== 'en' && $iterator->valid()); |
|
112 | - |
|
113 | - return $filtered; |
|
114 | - } |
|
115 | - |
|
116 | - /** |
|
117 | - * @param string[] $groupIds |
|
118 | - * @return list<array{id: string, displayname: string}> |
|
119 | - */ |
|
120 | - protected function getSelectedGroups(array $groupIds): array { |
|
121 | - $result = []; |
|
122 | - foreach ($groupIds as $groupId) { |
|
123 | - $group = $this->groupManager->get($groupId); |
|
124 | - |
|
125 | - if ($group === null) { |
|
126 | - continue; |
|
127 | - } |
|
128 | - |
|
129 | - $result[] = ['id' => $group->getGID(), 'displayname' => $group->getDisplayName()]; |
|
130 | - } |
|
131 | - |
|
132 | - return $result; |
|
133 | - } |
|
134 | - |
|
135 | - public function getSection(): ?string { |
|
136 | - if (!$this->config->getSystemValueBool('updatechecker', true)) { |
|
137 | - // update checker is disabled so we do not show the section at all |
|
138 | - return null; |
|
139 | - } |
|
140 | - |
|
141 | - return 'overview'; |
|
142 | - } |
|
143 | - |
|
144 | - public function getPriority(): int { |
|
145 | - return 11; |
|
146 | - } |
|
147 | - |
|
148 | - private function isWebUpdaterRecommended(): bool { |
|
149 | - return (int)$this->userManager->countUsersTotal(100) < 100; |
|
150 | - } |
|
27 | + public function __construct( |
|
28 | + private IConfig $config, |
|
29 | + private IAppConfig $appConfig, |
|
30 | + private UpdateChecker $updateChecker, |
|
31 | + private IGroupManager $groupManager, |
|
32 | + private IDateTimeFormatter $dateTimeFormatter, |
|
33 | + private IFactory $l10nFactory, |
|
34 | + private IRegistry $subscriptionRegistry, |
|
35 | + private IUserManager $userManager, |
|
36 | + private LoggerInterface $logger, |
|
37 | + private IInitialState $initialState, |
|
38 | + private ServerVersion $serverVersion, |
|
39 | + ) { |
|
40 | + } |
|
41 | + |
|
42 | + public function getForm(): TemplateResponse { |
|
43 | + $lastUpdateCheckTimestamp = $this->appConfig->getValueInt('core', 'lastupdatedat'); |
|
44 | + $lastUpdateCheck = $this->dateTimeFormatter->formatDateTime($lastUpdateCheckTimestamp); |
|
45 | + |
|
46 | + $channels = [ |
|
47 | + 'daily', |
|
48 | + 'beta', |
|
49 | + 'stable', |
|
50 | + 'production', |
|
51 | + ]; |
|
52 | + $currentChannel = $this->serverVersion->getChannel(); |
|
53 | + if ($currentChannel === 'git') { |
|
54 | + $channels[] = 'git'; |
|
55 | + } |
|
56 | + |
|
57 | + $updateState = $this->updateChecker->getUpdateState(); |
|
58 | + |
|
59 | + $notifyGroups = $this->appConfig->getValueArray(Application::APP_NAME, 'notify_groups', ['admin']); |
|
60 | + |
|
61 | + $defaultUpdateServerURL = 'https://updates.nextcloud.com/updater_server/'; |
|
62 | + $updateServerURL = $this->config->getSystemValue('updater.server.url', $defaultUpdateServerURL); |
|
63 | + $defaultCustomerUpdateServerURLPrefix = 'https://updates.nextcloud.com/customers/'; |
|
64 | + |
|
65 | + $isDefaultUpdateServerURL = $updateServerURL === $defaultUpdateServerURL |
|
66 | + || strpos($updateServerURL, $defaultCustomerUpdateServerURLPrefix) === 0; |
|
67 | + |
|
68 | + $hasValidSubscription = $this->subscriptionRegistry->delegateHasValidSubscription(); |
|
69 | + |
|
70 | + $params = [ |
|
71 | + 'isNewVersionAvailable' => !empty($updateState['updateAvailable']), |
|
72 | + 'isUpdateChecked' => $lastUpdateCheckTimestamp > 0, |
|
73 | + 'lastChecked' => $lastUpdateCheck, |
|
74 | + 'currentChannel' => $currentChannel, |
|
75 | + 'channels' => $channels, |
|
76 | + 'newVersion' => empty($updateState['updateVersion']) ? '' : $updateState['updateVersion'], |
|
77 | + 'newVersionString' => empty($updateState['updateVersionString']) ? '' : $updateState['updateVersionString'], |
|
78 | + 'downloadLink' => empty($updateState['downloadLink']) ? '' : $updateState['downloadLink'], |
|
79 | + 'changes' => $this->filterChanges($updateState['changes'] ?? []), |
|
80 | + 'webUpdaterEnabled' => !$this->config->getSystemValue('upgrade.disable-web', false), |
|
81 | + 'isWebUpdaterRecommended' => $this->isWebUpdaterRecommended(), |
|
82 | + 'updaterEnabled' => empty($updateState['updaterEnabled']) ? false : $updateState['updaterEnabled'], |
|
83 | + 'versionIsEol' => empty($updateState['versionIsEol']) ? false : $updateState['versionIsEol'], |
|
84 | + 'isDefaultUpdateServerURL' => $isDefaultUpdateServerURL, |
|
85 | + 'updateServerURL' => $updateServerURL, |
|
86 | + 'notifyGroups' => $this->getSelectedGroups($notifyGroups), |
|
87 | + 'hasValidSubscription' => $hasValidSubscription, |
|
88 | + ]; |
|
89 | + $this->initialState->provideInitialState('data', $params); |
|
90 | + |
|
91 | + return new TemplateResponse('updatenotification', 'admin', [], ''); |
|
92 | + } |
|
93 | + |
|
94 | + protected function filterChanges(array $changes): array { |
|
95 | + $filtered = []; |
|
96 | + if (isset($changes['changelogURL'])) { |
|
97 | + $filtered['changelogURL'] = $changes['changelogURL']; |
|
98 | + } |
|
99 | + if (!isset($changes['whatsNew'])) { |
|
100 | + return $filtered; |
|
101 | + } |
|
102 | + |
|
103 | + $iterator = $this->l10nFactory->getLanguageIterator(); |
|
104 | + do { |
|
105 | + $lang = $iterator->current(); |
|
106 | + if (isset($changes['whatsNew'][$lang])) { |
|
107 | + $filtered['whatsNew'] = $changes['whatsNew'][$lang]; |
|
108 | + return $filtered; |
|
109 | + } |
|
110 | + $iterator->next(); |
|
111 | + } while ($lang !== 'en' && $iterator->valid()); |
|
112 | + |
|
113 | + return $filtered; |
|
114 | + } |
|
115 | + |
|
116 | + /** |
|
117 | + * @param string[] $groupIds |
|
118 | + * @return list<array{id: string, displayname: string}> |
|
119 | + */ |
|
120 | + protected function getSelectedGroups(array $groupIds): array { |
|
121 | + $result = []; |
|
122 | + foreach ($groupIds as $groupId) { |
|
123 | + $group = $this->groupManager->get($groupId); |
|
124 | + |
|
125 | + if ($group === null) { |
|
126 | + continue; |
|
127 | + } |
|
128 | + |
|
129 | + $result[] = ['id' => $group->getGID(), 'displayname' => $group->getDisplayName()]; |
|
130 | + } |
|
131 | + |
|
132 | + return $result; |
|
133 | + } |
|
134 | + |
|
135 | + public function getSection(): ?string { |
|
136 | + if (!$this->config->getSystemValueBool('updatechecker', true)) { |
|
137 | + // update checker is disabled so we do not show the section at all |
|
138 | + return null; |
|
139 | + } |
|
140 | + |
|
141 | + return 'overview'; |
|
142 | + } |
|
143 | + |
|
144 | + public function getPriority(): int { |
|
145 | + return 11; |
|
146 | + } |
|
147 | + |
|
148 | + private function isWebUpdaterRecommended(): bool { |
|
149 | + return (int)$this->userManager->countUsersTotal(100) < 100; |
|
150 | + } |
|
151 | 151 | } |
@@ -16,99 +16,99 @@ |
||
16 | 16 | |
17 | 17 | class Manager { |
18 | 18 | |
19 | - private ?IUser $currentUser; |
|
19 | + private ?IUser $currentUser; |
|
20 | 20 | |
21 | - public function __construct( |
|
22 | - IUserSession $currentSession, |
|
23 | - private IAppManager $appManager, |
|
24 | - private IFactory $l10NFactory, |
|
25 | - private LoggerInterface $logger, |
|
26 | - ) { |
|
27 | - $this->currentUser = $currentSession->getUser(); |
|
28 | - } |
|
21 | + public function __construct( |
|
22 | + IUserSession $currentSession, |
|
23 | + private IAppManager $appManager, |
|
24 | + private IFactory $l10NFactory, |
|
25 | + private LoggerInterface $logger, |
|
26 | + ) { |
|
27 | + $this->currentUser = $currentSession->getUser(); |
|
28 | + } |
|
29 | 29 | |
30 | - /** |
|
31 | - * Get the changelog entry for the given appId |
|
32 | - * @param string $appId The app for which to query the entry |
|
33 | - * @param string $version The version for which to query the changelog entry |
|
34 | - * @param ?string $languageCode The language in which to query the changelog (defaults to current user language and fallsback to English) |
|
35 | - * @return string|null Either the changelog entry or null if no changelog is found |
|
36 | - */ |
|
37 | - public function getChangelog(string $appId, string $version, ?string $languageCode = null): ?string { |
|
38 | - if ($languageCode === null) { |
|
39 | - $languageCode = $this->l10NFactory->getUserLanguage($this->currentUser); |
|
40 | - } |
|
30 | + /** |
|
31 | + * Get the changelog entry for the given appId |
|
32 | + * @param string $appId The app for which to query the entry |
|
33 | + * @param string $version The version for which to query the changelog entry |
|
34 | + * @param ?string $languageCode The language in which to query the changelog (defaults to current user language and fallsback to English) |
|
35 | + * @return string|null Either the changelog entry or null if no changelog is found |
|
36 | + */ |
|
37 | + public function getChangelog(string $appId, string $version, ?string $languageCode = null): ?string { |
|
38 | + if ($languageCode === null) { |
|
39 | + $languageCode = $this->l10NFactory->getUserLanguage($this->currentUser); |
|
40 | + } |
|
41 | 41 | |
42 | - $path = $this->getChangelogFile($appId, $languageCode); |
|
43 | - if ($path === null) { |
|
44 | - $this->logger->debug('No changelog file found for app ' . $appId . ' and language code ' . $languageCode); |
|
45 | - return null; |
|
46 | - } |
|
42 | + $path = $this->getChangelogFile($appId, $languageCode); |
|
43 | + if ($path === null) { |
|
44 | + $this->logger->debug('No changelog file found for app ' . $appId . ' and language code ' . $languageCode); |
|
45 | + return null; |
|
46 | + } |
|
47 | 47 | |
48 | - $changes = $this->retrieveChangelogEntry($path, $version); |
|
49 | - return $changes; |
|
50 | - } |
|
48 | + $changes = $this->retrieveChangelogEntry($path, $version); |
|
49 | + return $changes; |
|
50 | + } |
|
51 | 51 | |
52 | - /** |
|
53 | - * Get the changelog file in the requested language or fallback to English |
|
54 | - * @param string $appId The app to load the changelog for |
|
55 | - * @param string $languageCode The language code to search |
|
56 | - * @return string|null Either the file path or null if not found |
|
57 | - */ |
|
58 | - public function getChangelogFile(string $appId, string $languageCode): ?string { |
|
59 | - try { |
|
60 | - $appPath = $this->appManager->getAppPath($appId); |
|
61 | - $files = ["CHANGELOG.$languageCode.md", 'CHANGELOG.en.md']; |
|
62 | - foreach ($files as $file) { |
|
63 | - $path = $appPath . '/' . $file; |
|
64 | - if (is_file($path)) { |
|
65 | - return $path; |
|
66 | - } |
|
67 | - } |
|
68 | - } catch (\Throwable $e) { |
|
69 | - // ignore and return null below |
|
70 | - } |
|
71 | - return null; |
|
72 | - } |
|
52 | + /** |
|
53 | + * Get the changelog file in the requested language or fallback to English |
|
54 | + * @param string $appId The app to load the changelog for |
|
55 | + * @param string $languageCode The language code to search |
|
56 | + * @return string|null Either the file path or null if not found |
|
57 | + */ |
|
58 | + public function getChangelogFile(string $appId, string $languageCode): ?string { |
|
59 | + try { |
|
60 | + $appPath = $this->appManager->getAppPath($appId); |
|
61 | + $files = ["CHANGELOG.$languageCode.md", 'CHANGELOG.en.md']; |
|
62 | + foreach ($files as $file) { |
|
63 | + $path = $appPath . '/' . $file; |
|
64 | + if (is_file($path)) { |
|
65 | + return $path; |
|
66 | + } |
|
67 | + } |
|
68 | + } catch (\Throwable $e) { |
|
69 | + // ignore and return null below |
|
70 | + } |
|
71 | + return null; |
|
72 | + } |
|
73 | 73 | |
74 | - /** |
|
75 | - * Retrieve a log entry from the changelog |
|
76 | - * @param string $path The path to the changelog file |
|
77 | - * @param string $version The version to query (make sure to only pass in "{major}.{minor}(.{patch}" format) |
|
78 | - */ |
|
79 | - protected function retrieveChangelogEntry(string $path, string $version): ?string { |
|
80 | - $matches = []; |
|
81 | - $content = file_get_contents($path); |
|
82 | - if ($content === false) { |
|
83 | - $this->logger->debug('Could not open changelog file', ['file-path' => $path]); |
|
84 | - return null; |
|
85 | - } |
|
74 | + /** |
|
75 | + * Retrieve a log entry from the changelog |
|
76 | + * @param string $path The path to the changelog file |
|
77 | + * @param string $version The version to query (make sure to only pass in "{major}.{minor}(.{patch}" format) |
|
78 | + */ |
|
79 | + protected function retrieveChangelogEntry(string $path, string $version): ?string { |
|
80 | + $matches = []; |
|
81 | + $content = file_get_contents($path); |
|
82 | + if ($content === false) { |
|
83 | + $this->logger->debug('Could not open changelog file', ['file-path' => $path]); |
|
84 | + return null; |
|
85 | + } |
|
86 | 86 | |
87 | - $result = preg_match_all('/^## (?:\[)?(?:v)?(\d+\.\d+(\.\d+)?)/m', $content, $matches, PREG_OFFSET_CAPTURE); |
|
88 | - if ($result === false || $result === 0) { |
|
89 | - $this->logger->debug('No entries in changelog found', ['file_path' => $path]); |
|
90 | - return null; |
|
91 | - } |
|
87 | + $result = preg_match_all('/^## (?:\[)?(?:v)?(\d+\.\d+(\.\d+)?)/m', $content, $matches, PREG_OFFSET_CAPTURE); |
|
88 | + if ($result === false || $result === 0) { |
|
89 | + $this->logger->debug('No entries in changelog found', ['file_path' => $path]); |
|
90 | + return null; |
|
91 | + } |
|
92 | 92 | |
93 | - // Get the key of the match that equals the requested version |
|
94 | - $index = array_key_first( |
|
95 | - // Get the array containing the match that equals the requested version, keys are preserved so: [1 => '1.2.4'] |
|
96 | - array_filter( |
|
97 | - // This is the array of the versions found, like ['1.2.3', '1.2.4'] |
|
98 | - $matches[1], |
|
99 | - // Callback to filter only version that matches the requested version |
|
100 | - fn (array $match) => version_compare($match[0], $version, '=='), |
|
101 | - ) |
|
102 | - ); |
|
93 | + // Get the key of the match that equals the requested version |
|
94 | + $index = array_key_first( |
|
95 | + // Get the array containing the match that equals the requested version, keys are preserved so: [1 => '1.2.4'] |
|
96 | + array_filter( |
|
97 | + // This is the array of the versions found, like ['1.2.3', '1.2.4'] |
|
98 | + $matches[1], |
|
99 | + // Callback to filter only version that matches the requested version |
|
100 | + fn (array $match) => version_compare($match[0], $version, '=='), |
|
101 | + ) |
|
102 | + ); |
|
103 | 103 | |
104 | - if ($index === null) { |
|
105 | - $this->logger->debug('No changelog entry for version ' . $version . ' found', ['file_path' => $path]); |
|
106 | - return null; |
|
107 | - } |
|
104 | + if ($index === null) { |
|
105 | + $this->logger->debug('No changelog entry for version ' . $version . ' found', ['file_path' => $path]); |
|
106 | + return null; |
|
107 | + } |
|
108 | 108 | |
109 | - $offsetChangelogEntry = $matches[0][$index][1]; |
|
110 | - // Length of the changelog entry (offset of next match - own offset) or null if the whole rest should be considered |
|
111 | - $lengthChangelogEntry = $index < ($result - 1) ? ($matches[0][$index + 1][1] - $offsetChangelogEntry) : null; |
|
112 | - return substr($content, $offsetChangelogEntry, $lengthChangelogEntry); |
|
113 | - } |
|
109 | + $offsetChangelogEntry = $matches[0][$index][1]; |
|
110 | + // Length of the changelog entry (offset of next match - own offset) or null if the whole rest should be considered |
|
111 | + $lengthChangelogEntry = $index < ($result - 1) ? ($matches[0][$index + 1][1] - $offsetChangelogEntry) : null; |
|
112 | + return substr($content, $offsetChangelogEntry, $lengthChangelogEntry); |
|
113 | + } |
|
114 | 114 | } |
@@ -24,142 +24,142 @@ |
||
24 | 24 | use OCP\ServerVersion; |
25 | 25 | |
26 | 26 | class Notifier implements INotifier { |
27 | - /** @var string[] */ |
|
28 | - protected $appVersions; |
|
29 | - |
|
30 | - /** |
|
31 | - * Notifier constructor. |
|
32 | - */ |
|
33 | - public function __construct( |
|
34 | - protected IURLGenerator $url, |
|
35 | - protected IAppConfig $appConfig, |
|
36 | - protected IManager $notificationManager, |
|
37 | - protected IFactory $l10NFactory, |
|
38 | - protected IUserSession $userSession, |
|
39 | - protected IGroupManager $groupManager, |
|
40 | - protected IAppManager $appManager, |
|
41 | - protected ServerVersion $serverVersion, |
|
42 | - ) { |
|
43 | - $this->appVersions = $this->appManager->getAppInstalledVersions(); |
|
44 | - } |
|
45 | - |
|
46 | - /** |
|
47 | - * Identifier of the notifier, only use [a-z0-9_] |
|
48 | - * |
|
49 | - * @return string |
|
50 | - * @since 17.0.0 |
|
51 | - */ |
|
52 | - public function getID(): string { |
|
53 | - return Application::APP_NAME; |
|
54 | - } |
|
55 | - |
|
56 | - /** |
|
57 | - * Human readable name describing the notifier |
|
58 | - * |
|
59 | - * @return string |
|
60 | - * @since 17.0.0 |
|
61 | - */ |
|
62 | - public function getName(): string { |
|
63 | - return $this->l10NFactory->get(Application::APP_NAME)->t('Update notifications'); |
|
64 | - } |
|
65 | - |
|
66 | - /** |
|
67 | - * @param INotification $notification |
|
68 | - * @param string $languageCode The code of the language that should be used to prepare the notification |
|
69 | - * @return INotification |
|
70 | - * @throws UnknownNotificationException When the notification was not prepared by a notifier |
|
71 | - * @throws AlreadyProcessedException When the notification is not needed anymore and should be deleted |
|
72 | - * @since 9.0.0 |
|
73 | - */ |
|
74 | - public function prepare(INotification $notification, string $languageCode): INotification { |
|
75 | - if ($notification->getApp() !== Application::APP_NAME) { |
|
76 | - throw new UnknownNotificationException('Unknown app id'); |
|
77 | - } |
|
78 | - |
|
79 | - if ($notification->getSubject() !== 'update_available' && $notification->getSubject() !== 'connection_error') { |
|
80 | - throw new UnknownNotificationException('Unknown subject'); |
|
81 | - } |
|
82 | - |
|
83 | - $l = $this->l10NFactory->get(Application::APP_NAME, $languageCode); |
|
84 | - if ($notification->getSubject() === 'connection_error') { |
|
85 | - $errors = $this->appConfig->getAppValueInt('update_check_errors', 0); |
|
86 | - if ($errors === 0) { |
|
87 | - throw new AlreadyProcessedException(); |
|
88 | - } |
|
89 | - |
|
90 | - $notification->setParsedSubject($l->t('The update server could not be reached since %d days to check for new updates.', [$errors])) |
|
91 | - ->setParsedMessage($l->t('Please check the Nextcloud and server log files for errors.')); |
|
92 | - } else { |
|
93 | - if ($notification->getObjectType() === 'core') { |
|
94 | - $this->updateAlreadyInstalledCheck($notification, $this->getCoreVersions()); |
|
95 | - |
|
96 | - $parameters = $notification->getSubjectParameters(); |
|
97 | - $notification->setRichSubject($l->t('Update to {serverAndVersion} is available.'), [ |
|
98 | - 'serverAndVersion' => [ |
|
99 | - 'type' => 'highlight', |
|
100 | - 'id' => $notification->getObjectType(), |
|
101 | - 'name' => $parameters['version'], |
|
102 | - ] |
|
103 | - ]); |
|
104 | - |
|
105 | - if ($this->isAdmin()) { |
|
106 | - $notification->setLink($this->url->linkToRouteAbsolute('settings.AdminSettings.index', ['section' => 'overview']) . '#version'); |
|
107 | - } |
|
108 | - } else { |
|
109 | - $appInfo = $this->getAppInfo($notification->getObjectType(), $languageCode); |
|
110 | - $appName = ($appInfo === null) ? $notification->getObjectType() : $appInfo['name']; |
|
111 | - |
|
112 | - if (isset($this->appVersions[$notification->getObjectType()])) { |
|
113 | - $this->updateAlreadyInstalledCheck($notification, $this->appVersions[$notification->getObjectType()]); |
|
114 | - } |
|
115 | - |
|
116 | - $notification->setRichSubject($l->t('Update for {app} to version %s is available.', [$notification->getObjectId()]), [ |
|
117 | - 'app' => [ |
|
118 | - 'type' => 'app', |
|
119 | - 'id' => $notification->getObjectType(), |
|
120 | - 'name' => $appName, |
|
121 | - ] |
|
122 | - ]); |
|
123 | - |
|
124 | - if ($this->isAdmin()) { |
|
125 | - $notification->setLink($this->url->linkToRouteAbsolute('settings.AppSettings.viewApps', ['category' => 'updates']) . '#app-' . $notification->getObjectType()); |
|
126 | - } |
|
127 | - } |
|
128 | - } |
|
129 | - |
|
130 | - $notification->setIcon($this->url->getAbsoluteURL($this->url->imagePath(Application::APP_NAME, 'notification.svg'))); |
|
131 | - |
|
132 | - return $notification; |
|
133 | - } |
|
134 | - |
|
135 | - /** |
|
136 | - * Remove the notification and prevent rendering, when the update is installed |
|
137 | - * |
|
138 | - * @param INotification $notification |
|
139 | - * @param string $installedVersion |
|
140 | - * @throws AlreadyProcessedException When the update is already installed |
|
141 | - */ |
|
142 | - protected function updateAlreadyInstalledCheck(INotification $notification, $installedVersion): void { |
|
143 | - if (version_compare($notification->getObjectId(), $installedVersion, '<=')) { |
|
144 | - throw new AlreadyProcessedException(); |
|
145 | - } |
|
146 | - } |
|
147 | - |
|
148 | - protected function isAdmin(): bool { |
|
149 | - $user = $this->userSession->getUser(); |
|
150 | - |
|
151 | - if ($user instanceof IUser) { |
|
152 | - return $this->groupManager->isAdmin($user->getUID()); |
|
153 | - } |
|
154 | - |
|
155 | - return false; |
|
156 | - } |
|
157 | - |
|
158 | - protected function getCoreVersions(): string { |
|
159 | - return implode('.', $this->serverVersion->getVersion()); |
|
160 | - } |
|
161 | - |
|
162 | - protected function getAppInfo(string $appId, ?string $languageCode): ?array { |
|
163 | - return $this->appManager->getAppInfo($appId, false, $languageCode); |
|
164 | - } |
|
27 | + /** @var string[] */ |
|
28 | + protected $appVersions; |
|
29 | + |
|
30 | + /** |
|
31 | + * Notifier constructor. |
|
32 | + */ |
|
33 | + public function __construct( |
|
34 | + protected IURLGenerator $url, |
|
35 | + protected IAppConfig $appConfig, |
|
36 | + protected IManager $notificationManager, |
|
37 | + protected IFactory $l10NFactory, |
|
38 | + protected IUserSession $userSession, |
|
39 | + protected IGroupManager $groupManager, |
|
40 | + protected IAppManager $appManager, |
|
41 | + protected ServerVersion $serverVersion, |
|
42 | + ) { |
|
43 | + $this->appVersions = $this->appManager->getAppInstalledVersions(); |
|
44 | + } |
|
45 | + |
|
46 | + /** |
|
47 | + * Identifier of the notifier, only use [a-z0-9_] |
|
48 | + * |
|
49 | + * @return string |
|
50 | + * @since 17.0.0 |
|
51 | + */ |
|
52 | + public function getID(): string { |
|
53 | + return Application::APP_NAME; |
|
54 | + } |
|
55 | + |
|
56 | + /** |
|
57 | + * Human readable name describing the notifier |
|
58 | + * |
|
59 | + * @return string |
|
60 | + * @since 17.0.0 |
|
61 | + */ |
|
62 | + public function getName(): string { |
|
63 | + return $this->l10NFactory->get(Application::APP_NAME)->t('Update notifications'); |
|
64 | + } |
|
65 | + |
|
66 | + /** |
|
67 | + * @param INotification $notification |
|
68 | + * @param string $languageCode The code of the language that should be used to prepare the notification |
|
69 | + * @return INotification |
|
70 | + * @throws UnknownNotificationException When the notification was not prepared by a notifier |
|
71 | + * @throws AlreadyProcessedException When the notification is not needed anymore and should be deleted |
|
72 | + * @since 9.0.0 |
|
73 | + */ |
|
74 | + public function prepare(INotification $notification, string $languageCode): INotification { |
|
75 | + if ($notification->getApp() !== Application::APP_NAME) { |
|
76 | + throw new UnknownNotificationException('Unknown app id'); |
|
77 | + } |
|
78 | + |
|
79 | + if ($notification->getSubject() !== 'update_available' && $notification->getSubject() !== 'connection_error') { |
|
80 | + throw new UnknownNotificationException('Unknown subject'); |
|
81 | + } |
|
82 | + |
|
83 | + $l = $this->l10NFactory->get(Application::APP_NAME, $languageCode); |
|
84 | + if ($notification->getSubject() === 'connection_error') { |
|
85 | + $errors = $this->appConfig->getAppValueInt('update_check_errors', 0); |
|
86 | + if ($errors === 0) { |
|
87 | + throw new AlreadyProcessedException(); |
|
88 | + } |
|
89 | + |
|
90 | + $notification->setParsedSubject($l->t('The update server could not be reached since %d days to check for new updates.', [$errors])) |
|
91 | + ->setParsedMessage($l->t('Please check the Nextcloud and server log files for errors.')); |
|
92 | + } else { |
|
93 | + if ($notification->getObjectType() === 'core') { |
|
94 | + $this->updateAlreadyInstalledCheck($notification, $this->getCoreVersions()); |
|
95 | + |
|
96 | + $parameters = $notification->getSubjectParameters(); |
|
97 | + $notification->setRichSubject($l->t('Update to {serverAndVersion} is available.'), [ |
|
98 | + 'serverAndVersion' => [ |
|
99 | + 'type' => 'highlight', |
|
100 | + 'id' => $notification->getObjectType(), |
|
101 | + 'name' => $parameters['version'], |
|
102 | + ] |
|
103 | + ]); |
|
104 | + |
|
105 | + if ($this->isAdmin()) { |
|
106 | + $notification->setLink($this->url->linkToRouteAbsolute('settings.AdminSettings.index', ['section' => 'overview']) . '#version'); |
|
107 | + } |
|
108 | + } else { |
|
109 | + $appInfo = $this->getAppInfo($notification->getObjectType(), $languageCode); |
|
110 | + $appName = ($appInfo === null) ? $notification->getObjectType() : $appInfo['name']; |
|
111 | + |
|
112 | + if (isset($this->appVersions[$notification->getObjectType()])) { |
|
113 | + $this->updateAlreadyInstalledCheck($notification, $this->appVersions[$notification->getObjectType()]); |
|
114 | + } |
|
115 | + |
|
116 | + $notification->setRichSubject($l->t('Update for {app} to version %s is available.', [$notification->getObjectId()]), [ |
|
117 | + 'app' => [ |
|
118 | + 'type' => 'app', |
|
119 | + 'id' => $notification->getObjectType(), |
|
120 | + 'name' => $appName, |
|
121 | + ] |
|
122 | + ]); |
|
123 | + |
|
124 | + if ($this->isAdmin()) { |
|
125 | + $notification->setLink($this->url->linkToRouteAbsolute('settings.AppSettings.viewApps', ['category' => 'updates']) . '#app-' . $notification->getObjectType()); |
|
126 | + } |
|
127 | + } |
|
128 | + } |
|
129 | + |
|
130 | + $notification->setIcon($this->url->getAbsoluteURL($this->url->imagePath(Application::APP_NAME, 'notification.svg'))); |
|
131 | + |
|
132 | + return $notification; |
|
133 | + } |
|
134 | + |
|
135 | + /** |
|
136 | + * Remove the notification and prevent rendering, when the update is installed |
|
137 | + * |
|
138 | + * @param INotification $notification |
|
139 | + * @param string $installedVersion |
|
140 | + * @throws AlreadyProcessedException When the update is already installed |
|
141 | + */ |
|
142 | + protected function updateAlreadyInstalledCheck(INotification $notification, $installedVersion): void { |
|
143 | + if (version_compare($notification->getObjectId(), $installedVersion, '<=')) { |
|
144 | + throw new AlreadyProcessedException(); |
|
145 | + } |
|
146 | + } |
|
147 | + |
|
148 | + protected function isAdmin(): bool { |
|
149 | + $user = $this->userSession->getUser(); |
|
150 | + |
|
151 | + if ($user instanceof IUser) { |
|
152 | + return $this->groupManager->isAdmin($user->getUID()); |
|
153 | + } |
|
154 | + |
|
155 | + return false; |
|
156 | + } |
|
157 | + |
|
158 | + protected function getCoreVersions(): string { |
|
159 | + return implode('.', $this->serverVersion->getVersion()); |
|
160 | + } |
|
161 | + |
|
162 | + protected function getAppInfo(string $appId, ?string $languageCode): ?array { |
|
163 | + return $this->appManager->getAppInfo($appId, false, $languageCode); |
|
164 | + } |
|
165 | 165 | } |
@@ -103,7 +103,7 @@ discard block |
||
103 | 103 | ]); |
104 | 104 | |
105 | 105 | if ($this->isAdmin()) { |
106 | - $notification->setLink($this->url->linkToRouteAbsolute('settings.AdminSettings.index', ['section' => 'overview']) . '#version'); |
|
106 | + $notification->setLink($this->url->linkToRouteAbsolute('settings.AdminSettings.index', ['section' => 'overview']).'#version'); |
|
107 | 107 | } |
108 | 108 | } else { |
109 | 109 | $appInfo = $this->getAppInfo($notification->getObjectType(), $languageCode); |
@@ -122,7 +122,7 @@ discard block |
||
122 | 122 | ]); |
123 | 123 | |
124 | 124 | if ($this->isAdmin()) { |
125 | - $notification->setLink($this->url->linkToRouteAbsolute('settings.AppSettings.viewApps', ['category' => 'updates']) . '#app-' . $notification->getObjectType()); |
|
125 | + $notification->setLink($this->url->linkToRouteAbsolute('settings.AppSettings.viewApps', ['category' => 'updates']).'#app-'.$notification->getObjectType()); |
|
126 | 126 | } |
127 | 127 | } |
128 | 128 | } |
@@ -23,222 +23,222 @@ |
||
23 | 23 | |
24 | 24 | class UpdateAvailableNotifications extends TimedJob { |
25 | 25 | |
26 | - /** |
|
27 | - * Numbers of failed updater connection to report error as notification. |
|
28 | - * @var list<int> |
|
29 | - */ |
|
30 | - protected const CONNECTION_NOTIFICATIONS = [3, 7, 14, 30]; |
|
31 | - |
|
32 | - /** @var ?string[] */ |
|
33 | - protected $users = null; |
|
34 | - |
|
35 | - public function __construct( |
|
36 | - ITimeFactory $timeFactory, |
|
37 | - protected ServerVersion $serverVersion, |
|
38 | - protected IConfig $config, |
|
39 | - protected IAppConfig $appConfig, |
|
40 | - protected IManager $notificationManager, |
|
41 | - protected IGroupManager $groupManager, |
|
42 | - protected IAppManager $appManager, |
|
43 | - protected Installer $installer, |
|
44 | - protected VersionCheck $versionCheck, |
|
45 | - ) { |
|
46 | - parent::__construct($timeFactory); |
|
47 | - // Run once a day |
|
48 | - $this->setInterval(60 * 60 * 24); |
|
49 | - $this->setTimeSensitivity(self::TIME_INSENSITIVE); |
|
50 | - } |
|
51 | - |
|
52 | - protected function run($argument) { |
|
53 | - // Do not check for updates if not connected to the internet |
|
54 | - if (!$this->config->getSystemValueBool('has_internet_connection', true)) { |
|
55 | - return; |
|
56 | - } |
|
57 | - |
|
58 | - if (\OC::$CLI && !$this->config->getSystemValueBool('debug', false)) { |
|
59 | - try { |
|
60 | - // Jitter the pinging of the updater server and the appstore a bit. |
|
61 | - // Otherwise all Nextcloud installations are pinging the servers |
|
62 | - // in one of 288 |
|
63 | - sleep(random_int(1, 180)); |
|
64 | - } catch (\Exception $e) { |
|
65 | - } |
|
66 | - } |
|
67 | - |
|
68 | - $this->checkCoreUpdate(); |
|
69 | - $this->checkAppUpdates(); |
|
70 | - } |
|
71 | - |
|
72 | - /** |
|
73 | - * Check for Nextcloud server update |
|
74 | - */ |
|
75 | - protected function checkCoreUpdate(): void { |
|
76 | - if (!$this->config->getSystemValueBool('updatechecker', true)) { |
|
77 | - // update checker is disabled so no core update check! |
|
78 | - return; |
|
79 | - } |
|
80 | - |
|
81 | - if (\in_array($this->serverVersion->getChannel(), ['daily', 'git'], true)) { |
|
82 | - // "These aren't the update channels you're looking for." - Ben Obi-Wan Kenobi |
|
83 | - return; |
|
84 | - } |
|
85 | - |
|
86 | - $status = $this->versionCheck->check(); |
|
87 | - if ($status === false) { |
|
88 | - $errors = 1 + $this->appConfig->getAppValueInt('update_check_errors', 0); |
|
89 | - $this->appConfig->setAppValueInt('update_check_errors', $errors); |
|
90 | - |
|
91 | - if (\in_array($errors, self::CONNECTION_NOTIFICATIONS, true)) { |
|
92 | - $this->sendErrorNotifications($errors); |
|
93 | - } |
|
94 | - } elseif (\is_array($status)) { |
|
95 | - $this->appConfig->setAppValueInt('update_check_errors', 0); |
|
96 | - $this->clearErrorNotifications(); |
|
97 | - |
|
98 | - if (isset($status['version'])) { |
|
99 | - $this->createNotifications('core', $status['version'], $status['versionstring']); |
|
100 | - } |
|
101 | - } |
|
102 | - } |
|
103 | - |
|
104 | - /** |
|
105 | - * Send a message to the admin when the update server could not be reached |
|
106 | - * @param int $numDays |
|
107 | - */ |
|
108 | - protected function sendErrorNotifications($numDays): void { |
|
109 | - $this->clearErrorNotifications(); |
|
110 | - |
|
111 | - $notification = $this->notificationManager->createNotification(); |
|
112 | - try { |
|
113 | - $notification->setApp(Application::APP_NAME) |
|
114 | - ->setDateTime(new \DateTime()) |
|
115 | - ->setObject(Application::APP_NAME, 'error') |
|
116 | - ->setSubject('connection_error', ['days' => $numDays]); |
|
117 | - |
|
118 | - foreach ($this->getUsersToNotify() as $uid) { |
|
119 | - $notification->setUser($uid); |
|
120 | - $this->notificationManager->notify($notification); |
|
121 | - } |
|
122 | - } catch (\InvalidArgumentException $e) { |
|
123 | - return; |
|
124 | - } |
|
125 | - } |
|
126 | - |
|
127 | - /** |
|
128 | - * Remove error notifications again |
|
129 | - */ |
|
130 | - protected function clearErrorNotifications(): void { |
|
131 | - $notification = $this->notificationManager->createNotification(); |
|
132 | - try { |
|
133 | - $notification->setApp(Application::APP_NAME) |
|
134 | - ->setSubject('connection_error') |
|
135 | - ->setObject(Application::APP_NAME, 'error'); |
|
136 | - } catch (\InvalidArgumentException $e) { |
|
137 | - return; |
|
138 | - } |
|
139 | - $this->notificationManager->markProcessed($notification); |
|
140 | - } |
|
141 | - |
|
142 | - /** |
|
143 | - * Check all installed apps for updates |
|
144 | - */ |
|
145 | - protected function checkAppUpdates(): void { |
|
146 | - $apps = $this->appManager->getEnabledApps(); |
|
147 | - foreach ($apps as $app) { |
|
148 | - $update = $this->isUpdateAvailable($app); |
|
149 | - if ($update !== false) { |
|
150 | - $this->createNotifications($app, $update); |
|
151 | - } |
|
152 | - } |
|
153 | - } |
|
154 | - |
|
155 | - /** |
|
156 | - * Create notifications for this app version |
|
157 | - * |
|
158 | - * @param string $app |
|
159 | - * @param string $version |
|
160 | - * @param string $visibleVersion |
|
161 | - */ |
|
162 | - protected function createNotifications($app, $version, $visibleVersion = ''): void { |
|
163 | - $lastNotification = $this->appConfig->getAppValueString($app, ''); |
|
164 | - if ($lastNotification === $version) { |
|
165 | - // We already notified about this update |
|
166 | - return; |
|
167 | - } |
|
168 | - |
|
169 | - if ($lastNotification !== '') { |
|
170 | - // Delete old updates |
|
171 | - $this->deleteOutdatedNotifications($app, $lastNotification); |
|
172 | - } |
|
173 | - |
|
174 | - $notification = $this->notificationManager->createNotification(); |
|
175 | - try { |
|
176 | - $notification->setApp(Application::APP_NAME) |
|
177 | - ->setDateTime(new \DateTime()) |
|
178 | - ->setObject($app, $version); |
|
179 | - |
|
180 | - if ($visibleVersion !== '') { |
|
181 | - $notification->setSubject('update_available', ['version' => $visibleVersion]); |
|
182 | - } else { |
|
183 | - $notification->setSubject('update_available'); |
|
184 | - } |
|
185 | - |
|
186 | - foreach ($this->getUsersToNotify() as $uid) { |
|
187 | - $notification->setUser($uid); |
|
188 | - $this->notificationManager->notify($notification); |
|
189 | - } |
|
190 | - } catch (\InvalidArgumentException $e) { |
|
191 | - return; |
|
192 | - } |
|
193 | - |
|
194 | - $this->appConfig->setAppValueString($app, $version); |
|
195 | - } |
|
196 | - |
|
197 | - /** |
|
198 | - * @return string[] |
|
199 | - */ |
|
200 | - protected function getUsersToNotify(): array { |
|
201 | - if ($this->users !== null) { |
|
202 | - return $this->users; |
|
203 | - } |
|
204 | - |
|
205 | - $notifyGroups = $this->appConfig->getAppValueArray('notify_groups', ['admin']); |
|
206 | - $this->users = []; |
|
207 | - foreach ($notifyGroups as $group) { |
|
208 | - $groupToNotify = $this->groupManager->get($group); |
|
209 | - if ($groupToNotify instanceof IGroup) { |
|
210 | - foreach ($groupToNotify->getUsers() as $user) { |
|
211 | - $this->users[] = $user->getUID(); |
|
212 | - } |
|
213 | - } |
|
214 | - } |
|
215 | - |
|
216 | - $this->users = array_values(array_unique($this->users)); |
|
217 | - return $this->users; |
|
218 | - } |
|
219 | - |
|
220 | - /** |
|
221 | - * Delete notifications for old updates |
|
222 | - * |
|
223 | - * @param string $app |
|
224 | - * @param string $version |
|
225 | - */ |
|
226 | - protected function deleteOutdatedNotifications($app, $version): void { |
|
227 | - $notification = $this->notificationManager->createNotification(); |
|
228 | - try { |
|
229 | - $notification->setApp(Application::APP_NAME) |
|
230 | - ->setObject($app, $version); |
|
231 | - } catch (\InvalidArgumentException) { |
|
232 | - return; |
|
233 | - } |
|
234 | - $this->notificationManager->markProcessed($notification); |
|
235 | - } |
|
236 | - |
|
237 | - /** |
|
238 | - * @param string $app |
|
239 | - * @return string|false |
|
240 | - */ |
|
241 | - protected function isUpdateAvailable($app) { |
|
242 | - return $this->installer->isUpdateAvailable($app); |
|
243 | - } |
|
26 | + /** |
|
27 | + * Numbers of failed updater connection to report error as notification. |
|
28 | + * @var list<int> |
|
29 | + */ |
|
30 | + protected const CONNECTION_NOTIFICATIONS = [3, 7, 14, 30]; |
|
31 | + |
|
32 | + /** @var ?string[] */ |
|
33 | + protected $users = null; |
|
34 | + |
|
35 | + public function __construct( |
|
36 | + ITimeFactory $timeFactory, |
|
37 | + protected ServerVersion $serverVersion, |
|
38 | + protected IConfig $config, |
|
39 | + protected IAppConfig $appConfig, |
|
40 | + protected IManager $notificationManager, |
|
41 | + protected IGroupManager $groupManager, |
|
42 | + protected IAppManager $appManager, |
|
43 | + protected Installer $installer, |
|
44 | + protected VersionCheck $versionCheck, |
|
45 | + ) { |
|
46 | + parent::__construct($timeFactory); |
|
47 | + // Run once a day |
|
48 | + $this->setInterval(60 * 60 * 24); |
|
49 | + $this->setTimeSensitivity(self::TIME_INSENSITIVE); |
|
50 | + } |
|
51 | + |
|
52 | + protected function run($argument) { |
|
53 | + // Do not check for updates if not connected to the internet |
|
54 | + if (!$this->config->getSystemValueBool('has_internet_connection', true)) { |
|
55 | + return; |
|
56 | + } |
|
57 | + |
|
58 | + if (\OC::$CLI && !$this->config->getSystemValueBool('debug', false)) { |
|
59 | + try { |
|
60 | + // Jitter the pinging of the updater server and the appstore a bit. |
|
61 | + // Otherwise all Nextcloud installations are pinging the servers |
|
62 | + // in one of 288 |
|
63 | + sleep(random_int(1, 180)); |
|
64 | + } catch (\Exception $e) { |
|
65 | + } |
|
66 | + } |
|
67 | + |
|
68 | + $this->checkCoreUpdate(); |
|
69 | + $this->checkAppUpdates(); |
|
70 | + } |
|
71 | + |
|
72 | + /** |
|
73 | + * Check for Nextcloud server update |
|
74 | + */ |
|
75 | + protected function checkCoreUpdate(): void { |
|
76 | + if (!$this->config->getSystemValueBool('updatechecker', true)) { |
|
77 | + // update checker is disabled so no core update check! |
|
78 | + return; |
|
79 | + } |
|
80 | + |
|
81 | + if (\in_array($this->serverVersion->getChannel(), ['daily', 'git'], true)) { |
|
82 | + // "These aren't the update channels you're looking for." - Ben Obi-Wan Kenobi |
|
83 | + return; |
|
84 | + } |
|
85 | + |
|
86 | + $status = $this->versionCheck->check(); |
|
87 | + if ($status === false) { |
|
88 | + $errors = 1 + $this->appConfig->getAppValueInt('update_check_errors', 0); |
|
89 | + $this->appConfig->setAppValueInt('update_check_errors', $errors); |
|
90 | + |
|
91 | + if (\in_array($errors, self::CONNECTION_NOTIFICATIONS, true)) { |
|
92 | + $this->sendErrorNotifications($errors); |
|
93 | + } |
|
94 | + } elseif (\is_array($status)) { |
|
95 | + $this->appConfig->setAppValueInt('update_check_errors', 0); |
|
96 | + $this->clearErrorNotifications(); |
|
97 | + |
|
98 | + if (isset($status['version'])) { |
|
99 | + $this->createNotifications('core', $status['version'], $status['versionstring']); |
|
100 | + } |
|
101 | + } |
|
102 | + } |
|
103 | + |
|
104 | + /** |
|
105 | + * Send a message to the admin when the update server could not be reached |
|
106 | + * @param int $numDays |
|
107 | + */ |
|
108 | + protected function sendErrorNotifications($numDays): void { |
|
109 | + $this->clearErrorNotifications(); |
|
110 | + |
|
111 | + $notification = $this->notificationManager->createNotification(); |
|
112 | + try { |
|
113 | + $notification->setApp(Application::APP_NAME) |
|
114 | + ->setDateTime(new \DateTime()) |
|
115 | + ->setObject(Application::APP_NAME, 'error') |
|
116 | + ->setSubject('connection_error', ['days' => $numDays]); |
|
117 | + |
|
118 | + foreach ($this->getUsersToNotify() as $uid) { |
|
119 | + $notification->setUser($uid); |
|
120 | + $this->notificationManager->notify($notification); |
|
121 | + } |
|
122 | + } catch (\InvalidArgumentException $e) { |
|
123 | + return; |
|
124 | + } |
|
125 | + } |
|
126 | + |
|
127 | + /** |
|
128 | + * Remove error notifications again |
|
129 | + */ |
|
130 | + protected function clearErrorNotifications(): void { |
|
131 | + $notification = $this->notificationManager->createNotification(); |
|
132 | + try { |
|
133 | + $notification->setApp(Application::APP_NAME) |
|
134 | + ->setSubject('connection_error') |
|
135 | + ->setObject(Application::APP_NAME, 'error'); |
|
136 | + } catch (\InvalidArgumentException $e) { |
|
137 | + return; |
|
138 | + } |
|
139 | + $this->notificationManager->markProcessed($notification); |
|
140 | + } |
|
141 | + |
|
142 | + /** |
|
143 | + * Check all installed apps for updates |
|
144 | + */ |
|
145 | + protected function checkAppUpdates(): void { |
|
146 | + $apps = $this->appManager->getEnabledApps(); |
|
147 | + foreach ($apps as $app) { |
|
148 | + $update = $this->isUpdateAvailable($app); |
|
149 | + if ($update !== false) { |
|
150 | + $this->createNotifications($app, $update); |
|
151 | + } |
|
152 | + } |
|
153 | + } |
|
154 | + |
|
155 | + /** |
|
156 | + * Create notifications for this app version |
|
157 | + * |
|
158 | + * @param string $app |
|
159 | + * @param string $version |
|
160 | + * @param string $visibleVersion |
|
161 | + */ |
|
162 | + protected function createNotifications($app, $version, $visibleVersion = ''): void { |
|
163 | + $lastNotification = $this->appConfig->getAppValueString($app, ''); |
|
164 | + if ($lastNotification === $version) { |
|
165 | + // We already notified about this update |
|
166 | + return; |
|
167 | + } |
|
168 | + |
|
169 | + if ($lastNotification !== '') { |
|
170 | + // Delete old updates |
|
171 | + $this->deleteOutdatedNotifications($app, $lastNotification); |
|
172 | + } |
|
173 | + |
|
174 | + $notification = $this->notificationManager->createNotification(); |
|
175 | + try { |
|
176 | + $notification->setApp(Application::APP_NAME) |
|
177 | + ->setDateTime(new \DateTime()) |
|
178 | + ->setObject($app, $version); |
|
179 | + |
|
180 | + if ($visibleVersion !== '') { |
|
181 | + $notification->setSubject('update_available', ['version' => $visibleVersion]); |
|
182 | + } else { |
|
183 | + $notification->setSubject('update_available'); |
|
184 | + } |
|
185 | + |
|
186 | + foreach ($this->getUsersToNotify() as $uid) { |
|
187 | + $notification->setUser($uid); |
|
188 | + $this->notificationManager->notify($notification); |
|
189 | + } |
|
190 | + } catch (\InvalidArgumentException $e) { |
|
191 | + return; |
|
192 | + } |
|
193 | + |
|
194 | + $this->appConfig->setAppValueString($app, $version); |
|
195 | + } |
|
196 | + |
|
197 | + /** |
|
198 | + * @return string[] |
|
199 | + */ |
|
200 | + protected function getUsersToNotify(): array { |
|
201 | + if ($this->users !== null) { |
|
202 | + return $this->users; |
|
203 | + } |
|
204 | + |
|
205 | + $notifyGroups = $this->appConfig->getAppValueArray('notify_groups', ['admin']); |
|
206 | + $this->users = []; |
|
207 | + foreach ($notifyGroups as $group) { |
|
208 | + $groupToNotify = $this->groupManager->get($group); |
|
209 | + if ($groupToNotify instanceof IGroup) { |
|
210 | + foreach ($groupToNotify->getUsers() as $user) { |
|
211 | + $this->users[] = $user->getUID(); |
|
212 | + } |
|
213 | + } |
|
214 | + } |
|
215 | + |
|
216 | + $this->users = array_values(array_unique($this->users)); |
|
217 | + return $this->users; |
|
218 | + } |
|
219 | + |
|
220 | + /** |
|
221 | + * Delete notifications for old updates |
|
222 | + * |
|
223 | + * @param string $app |
|
224 | + * @param string $version |
|
225 | + */ |
|
226 | + protected function deleteOutdatedNotifications($app, $version): void { |
|
227 | + $notification = $this->notificationManager->createNotification(); |
|
228 | + try { |
|
229 | + $notification->setApp(Application::APP_NAME) |
|
230 | + ->setObject($app, $version); |
|
231 | + } catch (\InvalidArgumentException) { |
|
232 | + return; |
|
233 | + } |
|
234 | + $this->notificationManager->markProcessed($notification); |
|
235 | + } |
|
236 | + |
|
237 | + /** |
|
238 | + * @param string $app |
|
239 | + * @return string|false |
|
240 | + */ |
|
241 | + protected function isUpdateAvailable($app) { |
|
242 | + return $this->installer->isUpdateAvailable($app); |
|
243 | + } |
|
244 | 244 | } |
@@ -6,43 +6,43 @@ |
||
6 | 6 | |
7 | 7 | class ComposerStaticInitUpdateNotification |
8 | 8 | { |
9 | - public static $prefixLengthsPsr4 = array ( |
|
9 | + public static $prefixLengthsPsr4 = array( |
|
10 | 10 | 'O' => |
11 | - array ( |
|
11 | + array( |
|
12 | 12 | 'OCA\\UpdateNotification\\' => 23, |
13 | 13 | ), |
14 | 14 | ); |
15 | 15 | |
16 | - public static $prefixDirsPsr4 = array ( |
|
16 | + public static $prefixDirsPsr4 = array( |
|
17 | 17 | 'OCA\\UpdateNotification\\' => |
18 | - array ( |
|
19 | - 0 => __DIR__ . '/..' . '/../lib', |
|
18 | + array( |
|
19 | + 0 => __DIR__.'/..'.'/../lib', |
|
20 | 20 | ), |
21 | 21 | ); |
22 | 22 | |
23 | - public static $classMap = array ( |
|
24 | - 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', |
|
25 | - 'OCA\\UpdateNotification\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php', |
|
26 | - 'OCA\\UpdateNotification\\BackgroundJob\\AppUpdatedNotifications' => __DIR__ . '/..' . '/../lib/BackgroundJob/AppUpdatedNotifications.php', |
|
27 | - 'OCA\\UpdateNotification\\BackgroundJob\\ResetToken' => __DIR__ . '/..' . '/../lib/BackgroundJob/ResetToken.php', |
|
28 | - 'OCA\\UpdateNotification\\BackgroundJob\\UpdateAvailableNotifications' => __DIR__ . '/..' . '/../lib/BackgroundJob/UpdateAvailableNotifications.php', |
|
29 | - 'OCA\\UpdateNotification\\Command\\Check' => __DIR__ . '/..' . '/../lib/Command/Check.php', |
|
30 | - 'OCA\\UpdateNotification\\Controller\\APIController' => __DIR__ . '/..' . '/../lib/Controller/APIController.php', |
|
31 | - 'OCA\\UpdateNotification\\Controller\\AdminController' => __DIR__ . '/..' . '/../lib/Controller/AdminController.php', |
|
32 | - 'OCA\\UpdateNotification\\Controller\\ChangelogController' => __DIR__ . '/..' . '/../lib/Controller/ChangelogController.php', |
|
33 | - 'OCA\\UpdateNotification\\Listener\\AppUpdateEventListener' => __DIR__ . '/..' . '/../lib/Listener/AppUpdateEventListener.php', |
|
34 | - 'OCA\\UpdateNotification\\Listener\\BeforeTemplateRenderedEventListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeTemplateRenderedEventListener.php', |
|
35 | - 'OCA\\UpdateNotification\\Manager' => __DIR__ . '/..' . '/../lib/Manager.php', |
|
36 | - 'OCA\\UpdateNotification\\Notification\\AppUpdateNotifier' => __DIR__ . '/..' . '/../lib/Notification/AppUpdateNotifier.php', |
|
37 | - 'OCA\\UpdateNotification\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php', |
|
38 | - 'OCA\\UpdateNotification\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php', |
|
39 | - 'OCA\\UpdateNotification\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php', |
|
40 | - 'OCA\\UpdateNotification\\UpdateChecker' => __DIR__ . '/..' . '/../lib/UpdateChecker.php', |
|
23 | + public static $classMap = array( |
|
24 | + 'Composer\\InstalledVersions' => __DIR__.'/..'.'/composer/InstalledVersions.php', |
|
25 | + 'OCA\\UpdateNotification\\AppInfo\\Application' => __DIR__.'/..'.'/../lib/AppInfo/Application.php', |
|
26 | + 'OCA\\UpdateNotification\\BackgroundJob\\AppUpdatedNotifications' => __DIR__.'/..'.'/../lib/BackgroundJob/AppUpdatedNotifications.php', |
|
27 | + 'OCA\\UpdateNotification\\BackgroundJob\\ResetToken' => __DIR__.'/..'.'/../lib/BackgroundJob/ResetToken.php', |
|
28 | + 'OCA\\UpdateNotification\\BackgroundJob\\UpdateAvailableNotifications' => __DIR__.'/..'.'/../lib/BackgroundJob/UpdateAvailableNotifications.php', |
|
29 | + 'OCA\\UpdateNotification\\Command\\Check' => __DIR__.'/..'.'/../lib/Command/Check.php', |
|
30 | + 'OCA\\UpdateNotification\\Controller\\APIController' => __DIR__.'/..'.'/../lib/Controller/APIController.php', |
|
31 | + 'OCA\\UpdateNotification\\Controller\\AdminController' => __DIR__.'/..'.'/../lib/Controller/AdminController.php', |
|
32 | + 'OCA\\UpdateNotification\\Controller\\ChangelogController' => __DIR__.'/..'.'/../lib/Controller/ChangelogController.php', |
|
33 | + 'OCA\\UpdateNotification\\Listener\\AppUpdateEventListener' => __DIR__.'/..'.'/../lib/Listener/AppUpdateEventListener.php', |
|
34 | + 'OCA\\UpdateNotification\\Listener\\BeforeTemplateRenderedEventListener' => __DIR__.'/..'.'/../lib/Listener/BeforeTemplateRenderedEventListener.php', |
|
35 | + 'OCA\\UpdateNotification\\Manager' => __DIR__.'/..'.'/../lib/Manager.php', |
|
36 | + 'OCA\\UpdateNotification\\Notification\\AppUpdateNotifier' => __DIR__.'/..'.'/../lib/Notification/AppUpdateNotifier.php', |
|
37 | + 'OCA\\UpdateNotification\\Notification\\Notifier' => __DIR__.'/..'.'/../lib/Notification/Notifier.php', |
|
38 | + 'OCA\\UpdateNotification\\ResponseDefinitions' => __DIR__.'/..'.'/../lib/ResponseDefinitions.php', |
|
39 | + 'OCA\\UpdateNotification\\Settings\\Admin' => __DIR__.'/..'.'/../lib/Settings/Admin.php', |
|
40 | + 'OCA\\UpdateNotification\\UpdateChecker' => __DIR__.'/..'.'/../lib/UpdateChecker.php', |
|
41 | 41 | ); |
42 | 42 | |
43 | 43 | public static function getInitializer(ClassLoader $loader) |
44 | 44 | { |
45 | - return \Closure::bind(function () use ($loader) { |
|
45 | + return \Closure::bind(function() use ($loader) { |
|
46 | 46 | $loader->prefixLengthsPsr4 = ComposerStaticInitUpdateNotification::$prefixLengthsPsr4; |
47 | 47 | $loader->prefixDirsPsr4 = ComposerStaticInitUpdateNotification::$prefixDirsPsr4; |
48 | 48 | $loader->classMap = ComposerStaticInitUpdateNotification::$classMap; |