You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Manager.php 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright (c) 2016, ownCloud, Inc.
  5. *
  6. * @author Joas Schilling <coding@schilljs.com>
  7. * @author Morris Jobke <hey@morrisjobke.de>
  8. * @author Roeland Jago Douma <roeland@famdouma.nl>
  9. *
  10. * @license AGPL-3.0
  11. *
  12. * This code is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU Affero General Public License, version 3,
  14. * as published by the Free Software Foundation.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU Affero General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU Affero General Public License, version 3,
  22. * along with this program. If not, see <http://www.gnu.org/licenses/>
  23. *
  24. */
  25. namespace OC\Notification;
  26. use OC\AppFramework\Bootstrap\Coordinator;
  27. use OCP\AppFramework\QueryException;
  28. use OCP\ILogger;
  29. use OCP\Notification\AlreadyProcessedException;
  30. use OCP\Notification\IApp;
  31. use OCP\Notification\IDeferrableApp;
  32. use OCP\Notification\IDismissableNotifier;
  33. use OCP\Notification\IManager;
  34. use OCP\Notification\INotification;
  35. use OCP\Notification\INotifier;
  36. use OCP\RichObjectStrings\IValidator;
  37. class Manager implements IManager {
  38. /** @var IValidator */
  39. protected $validator;
  40. /** @var ILogger */
  41. protected $logger;
  42. /** @var Coordinator */
  43. private $coordinator;
  44. /** @var IApp[] */
  45. protected $apps;
  46. /** @var string[] */
  47. protected $appClasses;
  48. /** @var INotifier[] */
  49. protected $notifiers;
  50. /** @var string[] */
  51. protected $notifierClasses;
  52. /** @var bool */
  53. protected $preparingPushNotification;
  54. /** @var bool */
  55. protected $deferPushing;
  56. /** @var bool */
  57. private $parsedRegistrationContext;
  58. public function __construct(IValidator $validator,
  59. ILogger $logger,
  60. Coordinator $coordinator) {
  61. $this->validator = $validator;
  62. $this->logger = $logger;
  63. $this->coordinator = $coordinator;
  64. $this->apps = [];
  65. $this->notifiers = [];
  66. $this->appClasses = [];
  67. $this->notifierClasses = [];
  68. $this->preparingPushNotification = false;
  69. $this->deferPushing = false;
  70. $this->parsedRegistrationContext = false;
  71. }
  72. /**
  73. * @param string $appClass The service must implement IApp, otherwise a
  74. * \InvalidArgumentException is thrown later
  75. * @since 17.0.0
  76. */
  77. public function registerApp(string $appClass): void {
  78. $this->appClasses[] = $appClass;
  79. }
  80. /**
  81. * @param \Closure $service The service must implement INotifier, otherwise a
  82. * \InvalidArgumentException is thrown later
  83. * @param \Closure $info An array with the keys 'id' and 'name' containing
  84. * the app id and the app name
  85. * @deprecated 17.0.0 use registerNotifierService instead.
  86. * @since 8.2.0 - Parameter $info was added in 9.0.0
  87. */
  88. public function registerNotifier(\Closure $service, \Closure $info) {
  89. $infoData = $info();
  90. $this->logger->logException(new \InvalidArgumentException(
  91. 'Notifier ' . $infoData['name'] . ' (id: ' . $infoData['id'] . ') is not considered because it is using the old way to register.'
  92. ));
  93. }
  94. /**
  95. * @param string $notifierService The service must implement INotifier, otherwise a
  96. * \InvalidArgumentException is thrown later
  97. * @since 17.0.0
  98. */
  99. public function registerNotifierService(string $notifierService): void {
  100. $this->notifierClasses[] = $notifierService;
  101. }
  102. /**
  103. * @return IApp[]
  104. */
  105. protected function getApps(): array {
  106. if (empty($this->appClasses)) {
  107. return $this->apps;
  108. }
  109. foreach ($this->appClasses as $appClass) {
  110. try {
  111. $app = \OC::$server->query($appClass);
  112. } catch (QueryException $e) {
  113. $this->logger->logException($e, [
  114. 'message' => 'Failed to load notification app class: ' . $appClass,
  115. 'app' => 'notifications',
  116. ]);
  117. continue;
  118. }
  119. if (!($app instanceof IApp)) {
  120. $this->logger->error('Notification app class ' . $appClass . ' is not implementing ' . IApp::class, [
  121. 'app' => 'notifications',
  122. ]);
  123. continue;
  124. }
  125. $this->apps[] = $app;
  126. }
  127. $this->appClasses = [];
  128. return $this->apps;
  129. }
  130. /**
  131. * @return INotifier[]
  132. */
  133. public function getNotifiers(): array {
  134. if (!$this->parsedRegistrationContext) {
  135. $notifierServices = $this->coordinator->getRegistrationContext()->getNotifierServices();
  136. foreach ($notifierServices as $notifierService) {
  137. try {
  138. $notifier = \OC::$server->query($notifierService->getService());
  139. } catch (QueryException $e) {
  140. $this->logger->logException($e, [
  141. 'message' => 'Failed to load notification notifier class: ' . $notifierService->getService(),
  142. 'app' => 'notifications',
  143. ]);
  144. continue;
  145. }
  146. if (!($notifier instanceof INotifier)) {
  147. $this->logger->error('Notification notifier class ' . $notifierService->getService() . ' is not implementing ' . INotifier::class, [
  148. 'app' => 'notifications',
  149. ]);
  150. continue;
  151. }
  152. $this->notifiers[] = $notifier;
  153. }
  154. $this->parsedRegistrationContext = true;
  155. }
  156. if (empty($this->notifierClasses)) {
  157. return $this->notifiers;
  158. }
  159. foreach ($this->notifierClasses as $notifierClass) {
  160. try {
  161. $notifier = \OC::$server->query($notifierClass);
  162. } catch (QueryException $e) {
  163. $this->logger->logException($e, [
  164. 'message' => 'Failed to load notification notifier class: ' . $notifierClass,
  165. 'app' => 'notifications',
  166. ]);
  167. continue;
  168. }
  169. if (!($notifier instanceof INotifier)) {
  170. $this->logger->error('Notification notifier class ' . $notifierClass . ' is not implementing ' . INotifier::class, [
  171. 'app' => 'notifications',
  172. ]);
  173. continue;
  174. }
  175. $this->notifiers[] = $notifier;
  176. }
  177. $this->notifierClasses = [];
  178. return $this->notifiers;
  179. }
  180. /**
  181. * @return INotification
  182. * @since 8.2.0
  183. */
  184. public function createNotification(): INotification {
  185. return new Notification($this->validator);
  186. }
  187. /**
  188. * @return bool
  189. * @since 8.2.0
  190. */
  191. public function hasNotifiers(): bool {
  192. return !empty($this->notifiers) || !empty($this->notifierClasses);
  193. }
  194. /**
  195. * @param bool $preparingPushNotification
  196. * @since 14.0.0
  197. */
  198. public function setPreparingPushNotification(bool $preparingPushNotification): void {
  199. $this->preparingPushNotification = $preparingPushNotification;
  200. }
  201. /**
  202. * @return bool
  203. * @since 14.0.0
  204. */
  205. public function isPreparingPushNotification(): bool {
  206. return $this->preparingPushNotification;
  207. }
  208. /**
  209. * The calling app should only "flush" when it got returned true on the defer call
  210. * @return bool
  211. * @since 20.0.0
  212. */
  213. public function defer(): bool {
  214. $alreadyDeferring = $this->deferPushing;
  215. $this->deferPushing = true;
  216. $apps = $this->getApps();
  217. foreach ($apps as $app) {
  218. if ($app instanceof IDeferrableApp) {
  219. $app->defer();
  220. }
  221. }
  222. return !$alreadyDeferring;
  223. }
  224. /**
  225. * @since 20.0.0
  226. */
  227. public function flush(): void {
  228. $apps = $this->getApps();
  229. foreach ($apps as $app) {
  230. if (!$app instanceof IDeferrableApp) {
  231. continue;
  232. }
  233. try {
  234. $app->flush();
  235. } catch (\InvalidArgumentException $e) {
  236. }
  237. }
  238. $this->deferPushing = false;
  239. }
  240. /**
  241. * @param INotification $notification
  242. * @throws \InvalidArgumentException When the notification is not valid
  243. * @since 8.2.0
  244. */
  245. public function notify(INotification $notification): void {
  246. if (!$notification->isValid()) {
  247. throw new \InvalidArgumentException('The given notification is invalid');
  248. }
  249. $apps = $this->getApps();
  250. foreach ($apps as $app) {
  251. try {
  252. $app->notify($notification);
  253. } catch (\InvalidArgumentException $e) {
  254. }
  255. }
  256. }
  257. /**
  258. * Identifier of the notifier, only use [a-z0-9_]
  259. *
  260. * @return string
  261. * @since 17.0.0
  262. */
  263. public function getID(): string {
  264. return 'core';
  265. }
  266. /**
  267. * Human readable name describing the notifier
  268. *
  269. * @return string
  270. * @since 17.0.0
  271. */
  272. public function getName(): string {
  273. return 'core';
  274. }
  275. /**
  276. * @param INotification $notification
  277. * @param string $languageCode The code of the language that should be used to prepare the notification
  278. * @return INotification
  279. * @throws \InvalidArgumentException When the notification was not prepared by a notifier
  280. * @throws AlreadyProcessedException When the notification is not needed anymore and should be deleted
  281. * @since 8.2.0
  282. */
  283. public function prepare(INotification $notification, string $languageCode): INotification {
  284. $notifiers = $this->getNotifiers();
  285. foreach ($notifiers as $notifier) {
  286. try {
  287. $notification = $notifier->prepare($notification, $languageCode);
  288. } catch (\InvalidArgumentException $e) {
  289. continue;
  290. } catch (AlreadyProcessedException $e) {
  291. $this->markProcessed($notification);
  292. throw new \InvalidArgumentException('The given notification has been processed');
  293. }
  294. if (!($notification instanceof INotification) || !$notification->isValidParsed()) {
  295. throw new \InvalidArgumentException('The given notification has not been handled');
  296. }
  297. }
  298. if (!($notification instanceof INotification) || !$notification->isValidParsed()) {
  299. throw new \InvalidArgumentException('The given notification has not been handled');
  300. }
  301. return $notification;
  302. }
  303. /**
  304. * @param INotification $notification
  305. */
  306. public function markProcessed(INotification $notification): void {
  307. $apps = $this->getApps();
  308. foreach ($apps as $app) {
  309. $app->markProcessed($notification);
  310. }
  311. }
  312. /**
  313. * @param INotification $notification
  314. * @return int
  315. */
  316. public function getCount(INotification $notification): int {
  317. $apps = $this->getApps();
  318. $count = 0;
  319. foreach ($apps as $app) {
  320. $count += $app->getCount($notification);
  321. }
  322. return $count;
  323. }
  324. public function dismissNotification(INotification $notification): void {
  325. $notifiers = $this->getNotifiers();
  326. foreach ($notifiers as $notifier) {
  327. if ($notifier instanceof IDismissableNotifier) {
  328. try {
  329. $notifier->dismissNotification($notification);
  330. } catch (\InvalidArgumentException $e) {
  331. continue;
  332. }
  333. }
  334. }
  335. }
  336. }