UpdateAvailableNotifications.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OCA\UpdateNotification\BackgroundJob;
  8. use OC\Installer;
  9. use OC\Updater\VersionCheck;
  10. use OCP\App\IAppManager;
  11. use OCP\AppFramework\Services\IAppConfig;
  12. use OCP\AppFramework\Utility\ITimeFactory;
  13. use OCP\BackgroundJob\TimedJob;
  14. use OCP\IConfig;
  15. use OCP\IGroup;
  16. use OCP\IGroupManager;
  17. use OCP\Notification\IManager;
  18. use OCP\ServerVersion;
  19. class UpdateAvailableNotifications extends TimedJob {
  20. protected $connectionNotifications = [3, 7, 14, 30];
  21. /** @var string[] */
  22. protected $users;
  23. public function __construct(
  24. ITimeFactory $timeFactory,
  25. protected ServerVersion $serverVersion,
  26. protected IConfig $config,
  27. protected IAppConfig $appConfig,
  28. protected IManager $notificationManager,
  29. protected IGroupManager $groupManager,
  30. protected IAppManager $appManager,
  31. protected Installer $installer,
  32. protected VersionCheck $versionCheck,
  33. ) {
  34. parent::__construct($timeFactory);
  35. // Run once a day
  36. $this->setInterval(60 * 60 * 24);
  37. $this->setTimeSensitivity(self::TIME_INSENSITIVE);
  38. }
  39. protected function run($argument) {
  40. // Do not check for updates if not connected to the internet
  41. if (!$this->config->getSystemValueBool('has_internet_connection', true)) {
  42. return;
  43. }
  44. if (\OC::$CLI && !$this->config->getSystemValueBool('debug', false)) {
  45. try {
  46. // Jitter the pinging of the updater server and the appstore a bit.
  47. // Otherwise all Nextcloud installations are pinging the servers
  48. // in one of 288
  49. sleep(random_int(1, 180));
  50. } catch (\Exception $e) {
  51. }
  52. }
  53. $this->checkCoreUpdate();
  54. $this->checkAppUpdates();
  55. }
  56. /**
  57. * Check for ownCloud update
  58. */
  59. protected function checkCoreUpdate() {
  60. if (\in_array($this->serverVersion->getChannel(), ['daily', 'git'], true)) {
  61. // "These aren't the update channels you're looking for." - Ben Obi-Wan Kenobi
  62. return;
  63. }
  64. $status = $this->versionCheck->check();
  65. if ($status === false) {
  66. $errors = 1 + $this->appConfig->getAppValueInt('update_check_errors', 0);
  67. $this->appConfig->setAppValueInt('update_check_errors', $errors);
  68. if (\in_array($errors, $this->connectionNotifications, true)) {
  69. $this->sendErrorNotifications($errors);
  70. }
  71. } elseif (\is_array($status)) {
  72. $this->appConfig->setAppValueInt('update_check_errors', 0);
  73. $this->clearErrorNotifications();
  74. if (isset($status['version'])) {
  75. $this->createNotifications('core', $status['version'], $status['versionstring']);
  76. }
  77. }
  78. }
  79. /**
  80. * Send a message to the admin when the update server could not be reached
  81. * @param int $numDays
  82. */
  83. protected function sendErrorNotifications($numDays) {
  84. $this->clearErrorNotifications();
  85. $notification = $this->notificationManager->createNotification();
  86. try {
  87. $notification->setApp('updatenotification')
  88. ->setDateTime(new \DateTime())
  89. ->setObject('updatenotification', 'error')
  90. ->setSubject('connection_error', ['days' => $numDays]);
  91. foreach ($this->getUsersToNotify() as $uid) {
  92. $notification->setUser($uid);
  93. $this->notificationManager->notify($notification);
  94. }
  95. } catch (\InvalidArgumentException $e) {
  96. return;
  97. }
  98. }
  99. /**
  100. * Remove error notifications again
  101. */
  102. protected function clearErrorNotifications() {
  103. $notification = $this->notificationManager->createNotification();
  104. try {
  105. $notification->setApp('updatenotification')
  106. ->setSubject('connection_error')
  107. ->setObject('updatenotification', 'error');
  108. } catch (\InvalidArgumentException $e) {
  109. return;
  110. }
  111. $this->notificationManager->markProcessed($notification);
  112. }
  113. /**
  114. * Check all installed apps for updates
  115. */
  116. protected function checkAppUpdates() {
  117. $apps = $this->appManager->getInstalledApps();
  118. foreach ($apps as $app) {
  119. $update = $this->isUpdateAvailable($app);
  120. if ($update !== false) {
  121. $this->createNotifications($app, $update);
  122. }
  123. }
  124. }
  125. /**
  126. * Create notifications for this app version
  127. *
  128. * @param string $app
  129. * @param string $version
  130. * @param string $visibleVersion
  131. */
  132. protected function createNotifications($app, $version, $visibleVersion = '') {
  133. $lastNotification = $this->appConfig->getAppValueString($app, '');
  134. if ($lastNotification === $version) {
  135. // We already notified about this update
  136. return;
  137. }
  138. if ($lastNotification !== '') {
  139. // Delete old updates
  140. $this->deleteOutdatedNotifications($app, $lastNotification);
  141. }
  142. $notification = $this->notificationManager->createNotification();
  143. try {
  144. $notification->setApp('updatenotification')
  145. ->setDateTime(new \DateTime())
  146. ->setObject($app, $version);
  147. if ($visibleVersion !== '') {
  148. $notification->setSubject('update_available', ['version' => $visibleVersion]);
  149. } else {
  150. $notification->setSubject('update_available');
  151. }
  152. foreach ($this->getUsersToNotify() as $uid) {
  153. $notification->setUser($uid);
  154. $this->notificationManager->notify($notification);
  155. }
  156. } catch (\InvalidArgumentException $e) {
  157. return;
  158. }
  159. $this->appConfig->setAppValueString($app, $version);
  160. }
  161. /**
  162. * @return string[]
  163. */
  164. protected function getUsersToNotify(): array {
  165. if ($this->users !== null) {
  166. return $this->users;
  167. }
  168. $notifyGroups = $this->appConfig->getAppValueArray('notify_groups', ['admin']);
  169. $this->users = [];
  170. foreach ($notifyGroups as $group) {
  171. $groupToNotify = $this->groupManager->get($group);
  172. if ($groupToNotify instanceof IGroup) {
  173. foreach ($groupToNotify->getUsers() as $user) {
  174. $this->users[] = $user->getUID();
  175. }
  176. }
  177. }
  178. $this->users = array_values(array_unique($this->users));
  179. return $this->users;
  180. }
  181. /**
  182. * Delete notifications for old updates
  183. *
  184. * @param string $app
  185. * @param string $version
  186. */
  187. protected function deleteOutdatedNotifications($app, $version) {
  188. $notification = $this->notificationManager->createNotification();
  189. try {
  190. $notification->setApp('updatenotification')
  191. ->setObject($app, $version);
  192. } catch (\InvalidArgumentException $e) {
  193. return;
  194. }
  195. $this->notificationManager->markProcessed($notification);
  196. }
  197. /**
  198. * @param string $app
  199. * @return string|false
  200. */
  201. protected function isUpdateAvailable($app) {
  202. return $this->installer->isUpdateAvailable($app);
  203. }
  204. }