UpdateAvailableNotifications.php 6.1 KB

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