Manager.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
  5. *
  6. * @author Björn Schießle <bjoern@schiessle.org>
  7. * @author Joas Schilling <coding@schilljs.com>
  8. * @author Thomas Müller <thomas.mueller@tmit.eu>
  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\Activity;
  26. use OCP\Activity\IConsumer;
  27. use OCP\Activity\IEvent;
  28. use OCP\Activity\IExtension;
  29. use OCP\Activity\IFilter;
  30. use OCP\Activity\IManager;
  31. use OCP\Activity\IProvider;
  32. use OCP\Activity\ISetting;
  33. use OCP\IConfig;
  34. use OCP\IRequest;
  35. use OCP\IUser;
  36. use OCP\IUserSession;
  37. use OCP\RichObjectStrings\IValidator;
  38. class Manager implements IManager {
  39. /** @var IRequest */
  40. protected $request;
  41. /** @var IUserSession */
  42. protected $session;
  43. /** @var IConfig */
  44. protected $config;
  45. /** @var IValidator */
  46. protected $validator;
  47. /** @var string */
  48. protected $formattingObjectType;
  49. /** @var int */
  50. protected $formattingObjectId;
  51. /** @var string */
  52. protected $currentUserId;
  53. /**
  54. * constructor of the controller
  55. *
  56. * @param IRequest $request
  57. * @param IUserSession $session
  58. * @param IConfig $config
  59. * @param IValidator $validator
  60. */
  61. public function __construct(IRequest $request,
  62. IUserSession $session,
  63. IConfig $config,
  64. IValidator $validator) {
  65. $this->request = $request;
  66. $this->session = $session;
  67. $this->config = $config;
  68. $this->validator = $validator;
  69. }
  70. /** @var \Closure[] */
  71. private $consumersClosures = array();
  72. /** @var IConsumer[] */
  73. private $consumers = array();
  74. /** @var \Closure[] */
  75. private $extensionsClosures = array();
  76. /** @var IExtension[] */
  77. private $extensions = array();
  78. /** @var array list of filters "name" => "is valid" */
  79. protected $validFilters = array(
  80. 'all' => true,
  81. 'by' => true,
  82. 'self' => true,
  83. );
  84. /** @var array list of type icons "type" => "css class" */
  85. protected $typeIcons = array();
  86. /** @var array list of special parameters "app" => ["text" => ["parameter" => "type"]] */
  87. protected $specialParameters = array();
  88. /**
  89. * @return \OCP\Activity\IConsumer[]
  90. */
  91. protected function getConsumers() {
  92. if (!empty($this->consumers)) {
  93. return $this->consumers;
  94. }
  95. $this->consumers = [];
  96. foreach($this->consumersClosures as $consumer) {
  97. $c = $consumer();
  98. if ($c instanceof IConsumer) {
  99. $this->consumers[] = $c;
  100. } else {
  101. throw new \InvalidArgumentException('The given consumer does not implement the \OCP\Activity\IConsumer interface');
  102. }
  103. }
  104. return $this->consumers;
  105. }
  106. /**
  107. * @return \OCP\Activity\IExtension[]
  108. */
  109. protected function getExtensions() {
  110. if (!empty($this->extensions)) {
  111. return $this->extensions;
  112. }
  113. $this->extensions = [];
  114. foreach($this->extensionsClosures as $extension) {
  115. $e = $extension();
  116. if ($e instanceof IExtension) {
  117. $this->extensions[] = $e;
  118. } else {
  119. throw new \InvalidArgumentException('The given extension does not implement the \OCP\Activity\IExtension interface');
  120. }
  121. }
  122. return $this->extensions;
  123. }
  124. /**
  125. * Generates a new IEvent object
  126. *
  127. * Make sure to call at least the following methods before sending it to the
  128. * app with via the publish() method:
  129. * - setApp()
  130. * - setType()
  131. * - setAffectedUser()
  132. * - setSubject()
  133. *
  134. * @return IEvent
  135. */
  136. public function generateEvent() {
  137. return new Event($this->validator);
  138. }
  139. /**
  140. * Publish an event to the activity consumers
  141. *
  142. * Make sure to call at least the following methods before sending an Event:
  143. * - setApp()
  144. * - setType()
  145. * - setAffectedUser()
  146. * - setSubject()
  147. *
  148. * @param IEvent $event
  149. * @throws \BadMethodCallException if required values have not been set
  150. */
  151. public function publish(IEvent $event) {
  152. if ($event->getAuthor() === '') {
  153. if ($this->session->getUser() instanceof IUser) {
  154. $event->setAuthor($this->session->getUser()->getUID());
  155. }
  156. }
  157. if (!$event->getTimestamp()) {
  158. $event->setTimestamp(time());
  159. }
  160. if (!$event->isValid()) {
  161. throw new \BadMethodCallException('The given event is invalid');
  162. }
  163. foreach ($this->getConsumers() as $c) {
  164. $c->receive($event);
  165. }
  166. }
  167. /**
  168. * @param string $app The app where this event is associated with
  169. * @param string $subject A short description of the event
  170. * @param array $subjectParams Array with parameters that are filled in the subject
  171. * @param string $message A longer description of the event
  172. * @param array $messageParams Array with parameters that are filled in the message
  173. * @param string $file The file including path where this event is associated with
  174. * @param string $link A link where this event is associated with
  175. * @param string $affectedUser Recipient of the activity
  176. * @param string $type Type of the notification
  177. * @param int $priority Priority of the notification
  178. */
  179. public function publishActivity($app, $subject, $subjectParams, $message, $messageParams, $file, $link, $affectedUser, $type, $priority) {
  180. $event = $this->generateEvent();
  181. $event->setApp($app)
  182. ->setType($type)
  183. ->setAffectedUser($affectedUser)
  184. ->setSubject($subject, $subjectParams)
  185. ->setMessage($message, $messageParams)
  186. ->setObject('', 0, $file)
  187. ->setLink($link);
  188. $this->publish($event);
  189. }
  190. /**
  191. * In order to improve lazy loading a closure can be registered which will be called in case
  192. * activity consumers are actually requested
  193. *
  194. * $callable has to return an instance of OCA\Activity\IConsumer
  195. *
  196. * @param \Closure $callable
  197. */
  198. public function registerConsumer(\Closure $callable) {
  199. array_push($this->consumersClosures, $callable);
  200. $this->consumers = [];
  201. }
  202. /**
  203. * In order to improve lazy loading a closure can be registered which will be called in case
  204. * activity consumers are actually requested
  205. *
  206. * $callable has to return an instance of OCA\Activity\IExtension
  207. *
  208. * @param \Closure $callable
  209. */
  210. public function registerExtension(\Closure $callable) {
  211. array_push($this->extensionsClosures, $callable);
  212. $this->extensions = [];
  213. }
  214. /** @var string[] */
  215. protected $filterClasses = [];
  216. /** @var IFilter[] */
  217. protected $filters = [];
  218. /** @var bool */
  219. protected $loadedLegacyFilters = false;
  220. /**
  221. * @param string $filter Class must implement OCA\Activity\IFilter
  222. * @return void
  223. */
  224. public function registerFilter($filter) {
  225. $this->filterClasses[$filter] = false;
  226. }
  227. /**
  228. * @return IFilter[]
  229. * @throws \InvalidArgumentException
  230. */
  231. public function getFilters() {
  232. if (!$this->loadedLegacyFilters) {
  233. $legacyFilters = $this->getNavigation();
  234. foreach ($legacyFilters['top'] as $filter => $data) {
  235. $this->filters[$filter] = new LegacyFilter(
  236. $this, $filter, $data['name'], true
  237. );
  238. }
  239. foreach ($legacyFilters['apps'] as $filter => $data) {
  240. $this->filters[$filter] = new LegacyFilter(
  241. $this, $filter, $data['name'], false
  242. );
  243. }
  244. $this->loadedLegacyFilters = true;
  245. }
  246. foreach ($this->filterClasses as $class => $false) {
  247. /** @var IFilter $filter */
  248. $filter = \OC::$server->query($class);
  249. if (!$filter instanceof IFilter) {
  250. throw new \InvalidArgumentException('Invalid activity filter registered');
  251. }
  252. $this->filters[$filter->getIdentifier()] = $filter;
  253. unset($this->filterClasses[$class]);
  254. }
  255. return $this->filters;
  256. }
  257. /**
  258. * @param string $id
  259. * @return IFilter
  260. * @throws \InvalidArgumentException when the filter was not found
  261. * @since 11.0.0
  262. */
  263. public function getFilterById($id) {
  264. $filters = $this->getFilters();
  265. if (isset($filters[$id])) {
  266. return $filters[$id];
  267. }
  268. throw new \InvalidArgumentException('Requested filter does not exist');
  269. }
  270. /** @var string[] */
  271. protected $providerClasses = [];
  272. /** @var IProvider[] */
  273. protected $providers = [];
  274. /**
  275. * @param string $provider Class must implement OCA\Activity\IProvider
  276. * @return void
  277. */
  278. public function registerProvider($provider) {
  279. $this->providerClasses[$provider] = false;
  280. }
  281. /**
  282. * @return IProvider[]
  283. * @throws \InvalidArgumentException
  284. */
  285. public function getProviders() {
  286. foreach ($this->providerClasses as $class => $false) {
  287. /** @var IProvider $provider */
  288. $provider = \OC::$server->query($class);
  289. if (!$provider instanceof IProvider) {
  290. throw new \InvalidArgumentException('Invalid activity provider registered');
  291. }
  292. $this->providers[] = $provider;
  293. unset($this->providerClasses[$class]);
  294. }
  295. return $this->providers;
  296. }
  297. /** @var string[] */
  298. protected $settingsClasses = [];
  299. /** @var ISetting[] */
  300. protected $settings = [];
  301. /** @var bool */
  302. protected $loadedLegacyTypes = false;
  303. /**
  304. * @param string $setting Class must implement OCA\Activity\ISetting
  305. * @return void
  306. */
  307. public function registerSetting($setting) {
  308. $this->settingsClasses[$setting] = false;
  309. }
  310. /**
  311. * @return ISetting[]
  312. * @throws \InvalidArgumentException
  313. */
  314. public function getSettings() {
  315. if (!$this->loadedLegacyTypes) {
  316. $l = \OC::$server->getL10N('core');
  317. $legacyTypes = $this->getNotificationTypes($l->getLanguageCode());
  318. $streamTypes = $this->getDefaultTypes(IExtension::METHOD_STREAM);
  319. $mailTypes = $this->getDefaultTypes(IExtension::METHOD_MAIL);
  320. foreach ($legacyTypes as $type => $data) {
  321. if (is_string($data)) {
  322. $desc = $data;
  323. $canChangeStream = true;
  324. $canChangeMail = true;
  325. } else {
  326. $desc = $data['desc'];
  327. $canChangeStream = in_array(IExtension::METHOD_STREAM, $data['methods']);
  328. $canChangeMail = in_array(IExtension::METHOD_MAIL, $data['methods']);
  329. }
  330. $this->settings[$type] = new LegacySetting(
  331. $type, $desc,
  332. $canChangeStream, in_array($type, $streamTypes),
  333. $canChangeMail, in_array($type, $mailTypes)
  334. );
  335. }
  336. $this->loadedLegacyTypes = true;
  337. }
  338. foreach ($this->settingsClasses as $class => $false) {
  339. /** @var ISetting $setting */
  340. $setting = \OC::$server->query($class);
  341. if (!$setting instanceof ISetting) {
  342. throw new \InvalidArgumentException('Invalid activity filter registered');
  343. }
  344. $this->settings[$setting->getIdentifier()] = $setting;
  345. unset($this->settingsClasses[$class]);
  346. }
  347. return $this->settings;
  348. }
  349. /**
  350. * @param string $id
  351. * @return ISetting
  352. * @throws \InvalidArgumentException when the setting was not found
  353. * @since 11.0.0
  354. */
  355. public function getSettingById($id) {
  356. $settings = $this->getSettings();
  357. if (isset($settings[$id])) {
  358. return $settings[$id];
  359. }
  360. throw new \InvalidArgumentException('Requested setting does not exist');
  361. }
  362. /**
  363. * @param string $type
  364. * @return string
  365. */
  366. public function getTypeIcon($type) {
  367. if (isset($this->typeIcons[$type])) {
  368. return $this->typeIcons[$type];
  369. }
  370. foreach ($this->getExtensions() as $c) {
  371. $icon = $c->getTypeIcon($type);
  372. if (is_string($icon)) {
  373. $this->typeIcons[$type] = $icon;
  374. return $icon;
  375. }
  376. }
  377. $this->typeIcons[$type] = '';
  378. return '';
  379. }
  380. /**
  381. * @param string $type
  382. * @param string $id
  383. */
  384. public function setFormattingObject($type, $id) {
  385. $this->formattingObjectType = $type;
  386. $this->formattingObjectId = (string) $id;
  387. }
  388. /**
  389. * @return bool
  390. */
  391. public function isFormattingFilteredObject() {
  392. return $this->formattingObjectType !== null && $this->formattingObjectId !== null
  393. && $this->formattingObjectType === $this->request->getParam('object_type')
  394. && $this->formattingObjectId === $this->request->getParam('object_id');
  395. }
  396. /**
  397. * @param string $app
  398. * @param string $text
  399. * @param array $params
  400. * @param boolean $stripPath
  401. * @param boolean $highlightParams
  402. * @param string $languageCode
  403. * @return string|false
  404. */
  405. public function translate($app, $text, $params, $stripPath, $highlightParams, $languageCode) {
  406. foreach ($this->getExtensions() as $c) {
  407. $translation = $c->translate($app, $text, $params, $stripPath, $highlightParams, $languageCode);
  408. if (is_string($translation)) {
  409. return $translation;
  410. }
  411. }
  412. return false;
  413. }
  414. /**
  415. * @param string $app
  416. * @param string $text
  417. * @return array|false
  418. */
  419. public function getSpecialParameterList($app, $text) {
  420. if (isset($this->specialParameters[$app][$text])) {
  421. return $this->specialParameters[$app][$text];
  422. }
  423. if (!isset($this->specialParameters[$app])) {
  424. $this->specialParameters[$app] = array();
  425. }
  426. foreach ($this->getExtensions() as $c) {
  427. $specialParameter = $c->getSpecialParameterList($app, $text);
  428. if (is_array($specialParameter)) {
  429. $this->specialParameters[$app][$text] = $specialParameter;
  430. return $specialParameter;
  431. }
  432. }
  433. $this->specialParameters[$app][$text] = false;
  434. return false;
  435. }
  436. /**
  437. * @param array $activity
  438. * @return integer|false
  439. */
  440. public function getGroupParameter($activity) {
  441. foreach ($this->getExtensions() as $c) {
  442. $parameter = $c->getGroupParameter($activity);
  443. if ($parameter !== false) {
  444. return $parameter;
  445. }
  446. }
  447. return false;
  448. }
  449. /**
  450. * Set the user we need to use
  451. *
  452. * @param string|null $currentUserId
  453. * @throws \UnexpectedValueException If the user is invalid
  454. */
  455. public function setCurrentUserId($currentUserId) {
  456. if (!is_string($currentUserId) && $currentUserId !== null) {
  457. throw new \UnexpectedValueException('The given current user is invalid');
  458. }
  459. $this->currentUserId = $currentUserId;
  460. }
  461. /**
  462. * Get the user we need to use
  463. *
  464. * Either the user is logged in, or we try to get it from the token
  465. *
  466. * @return string
  467. * @throws \UnexpectedValueException If the token is invalid, does not exist or is not unique
  468. */
  469. public function getCurrentUserId() {
  470. if ($this->currentUserId !== null) {
  471. return $this->currentUserId;
  472. } else if (!$this->session->isLoggedIn()) {
  473. return $this->getUserFromToken();
  474. } else {
  475. return $this->session->getUser()->getUID();
  476. }
  477. }
  478. /**
  479. * Get the user for the token
  480. *
  481. * @return string
  482. * @throws \UnexpectedValueException If the token is invalid, does not exist or is not unique
  483. */
  484. protected function getUserFromToken() {
  485. $token = (string) $this->request->getParam('token', '');
  486. if (strlen($token) !== 30) {
  487. throw new \UnexpectedValueException('The token is invalid');
  488. }
  489. $users = $this->config->getUsersForUserValue('activity', 'rsstoken', $token);
  490. if (sizeof($users) !== 1) {
  491. // No unique user found
  492. throw new \UnexpectedValueException('The token is invalid');
  493. }
  494. // Token found login as that user
  495. return array_shift($users);
  496. }
  497. /**
  498. * @return array
  499. * @deprecated 11.0.0 - Use getFilters() instead
  500. */
  501. public function getNavigation() {
  502. $entries = array(
  503. 'apps' => array(),
  504. 'top' => array(),
  505. );
  506. foreach ($this->getExtensions() as $c) {
  507. $additionalEntries = $c->getNavigation();
  508. if (is_array($additionalEntries)) {
  509. $entries['apps'] = array_merge($entries['apps'], $additionalEntries['apps']);
  510. $entries['top'] = array_merge($entries['top'], $additionalEntries['top']);
  511. }
  512. }
  513. return $entries;
  514. }
  515. /**
  516. * @param string $filterValue
  517. * @return boolean
  518. * @deprecated 11.0.0 - Use getFilterById() instead
  519. */
  520. public function isFilterValid($filterValue) {
  521. if (isset($this->validFilters[$filterValue])) {
  522. return $this->validFilters[$filterValue];
  523. }
  524. foreach ($this->getExtensions() as $c) {
  525. if ($c->isFilterValid($filterValue) === true) {
  526. $this->validFilters[$filterValue] = true;
  527. return true;
  528. }
  529. }
  530. $this->validFilters[$filterValue] = false;
  531. return false;
  532. }
  533. /**
  534. * @param array $types
  535. * @param string $filter
  536. * @return array
  537. * @deprecated 11.0.0 - Use getFilterById()->filterTypes() instead
  538. */
  539. public function filterNotificationTypes($types, $filter) {
  540. if (!$this->isFilterValid($filter)) {
  541. return $types;
  542. }
  543. foreach ($this->getExtensions() as $c) {
  544. $result = $c->filterNotificationTypes($types, $filter);
  545. if (is_array($result)) {
  546. $types = $result;
  547. }
  548. }
  549. return $types;
  550. }
  551. /**
  552. * @param string $filter
  553. * @return array
  554. * @deprecated 11.0.0 - Use getFilterById() instead
  555. */
  556. public function getQueryForFilter($filter) {
  557. if (!$this->isFilterValid($filter)) {
  558. return [null, null];
  559. }
  560. $conditions = array();
  561. $parameters = array();
  562. foreach ($this->getExtensions() as $c) {
  563. $result = $c->getQueryForFilter($filter);
  564. if (is_array($result)) {
  565. list($condition, $parameter) = $result;
  566. if ($condition && is_array($parameter)) {
  567. $conditions[] = $condition;
  568. $parameters = array_merge($parameters, $parameter);
  569. }
  570. }
  571. }
  572. if (empty($conditions)) {
  573. return array(null, null);
  574. }
  575. return array(' and ((' . implode(') or (', $conditions) . '))', $parameters);
  576. }
  577. /**
  578. * Will return additional notification types as specified by other apps
  579. *
  580. * @param string $languageCode
  581. * @return array
  582. * @deprecated 11.0.0 - Use getSettings() instead
  583. */
  584. public function getNotificationTypes($languageCode) {
  585. $notificationTypes = $sharingNotificationTypes = [];
  586. foreach ($this->getExtensions() as $c) {
  587. $result = $c->getNotificationTypes($languageCode);
  588. if (is_array($result)) {
  589. $notificationTypes = array_merge($notificationTypes, $result);
  590. }
  591. }
  592. return array_merge($sharingNotificationTypes, $notificationTypes);
  593. }
  594. /**
  595. * @param string $method
  596. * @return array
  597. * @deprecated 11.0.0 - Use getSettings()->isDefaulEnabled<method>() instead
  598. */
  599. public function getDefaultTypes($method) {
  600. $defaultTypes = array();
  601. foreach ($this->getExtensions() as $c) {
  602. $types = $c->getDefaultTypes($method);
  603. if (is_array($types)) {
  604. $defaultTypes = array_merge($types, $defaultTypes);
  605. }
  606. }
  607. return $defaultTypes;
  608. }
  609. }