NotificationsService.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. <?php
  2. namespace Elgg\Notifications;
  3. use ElggEntity;
  4. /**
  5. * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
  6. *
  7. * @access private
  8. *
  9. * @package Elgg.Core
  10. * @subpackage Notifications
  11. * @since 1.9.0
  12. */
  13. class NotificationsService {
  14. const QUEUE_NAME = 'notifications';
  15. /** @var \Elgg\Notifications\SubscriptionsService */
  16. protected $subscriptions;
  17. /** @var \Elgg\Queue\Queue */
  18. protected $queue;
  19. /** @var \Elgg\PluginHooksService */
  20. protected $hooks;
  21. /** @var \ElggSession */
  22. protected $session;
  23. /** @var array Registered notification events */
  24. protected $events = array();
  25. /** @var array Registered notification methods */
  26. protected $methods = array();
  27. /** @var array Deprecated notification handlers */
  28. protected $deprHandlers = array();
  29. /** @var array Deprecated message subjects */
  30. protected $deprSubjects = array();
  31. /**
  32. * Constructor
  33. *
  34. * @param \Elgg\Notifications\SubscriptionsService $subscriptions Subscription service
  35. * @param \Elgg\Queue\Queue $queue Queue
  36. * @param \Elgg\PluginHooksService $hooks Plugin hook service
  37. * @param \ElggSession $session Session service
  38. */
  39. public function __construct(\Elgg\Notifications\SubscriptionsService $subscriptions,
  40. \Elgg\Queue\Queue $queue, \Elgg\PluginHooksService $hooks, \ElggSession $session) {
  41. $this->subscriptions = $subscriptions;
  42. $this->queue = $queue;
  43. $this->hooks = $hooks;
  44. $this->session = $session;
  45. }
  46. /**
  47. * @see elgg_register_notification_event()
  48. * @access private
  49. */
  50. public function registerEvent($type, $subtype, array $actions = array()) {
  51. if (!isset($this->events[$type])) {
  52. $this->events[$type] = array();
  53. }
  54. if (!isset($this->events[$type][$subtype])) {
  55. $this->events[$type][$subtype] = array();
  56. }
  57. $action_list =& $this->events[$type][$subtype];
  58. if ($actions) {
  59. $action_list = array_unique(array_merge($action_list, $actions));
  60. } elseif (!in_array('create', $action_list)) {
  61. $action_list[] = 'create';
  62. }
  63. }
  64. /**
  65. * @see elgg_unregister_notification_event()
  66. * @access private
  67. */
  68. public function unregisterEvent($type, $subtype) {
  69. if (!isset($this->events[$type]) || !isset($this->events[$type][$subtype])) {
  70. return false;
  71. }
  72. unset($this->events[$type][$subtype]);
  73. return true;
  74. }
  75. /**
  76. * @access private
  77. */
  78. public function getEvents() {
  79. return $this->events;
  80. }
  81. /**
  82. * @see elgg_register_notification_method()
  83. * @access private
  84. */
  85. public function registerMethod($name) {
  86. $this->methods[$name] = $name;
  87. }
  88. /**
  89. * @see elgg_unregister_notification_method()
  90. * @access private
  91. */
  92. public function unregisterMethod($name) {
  93. if (isset($this->methods[$name])) {
  94. unset($this->methods[$name]);
  95. return true;
  96. }
  97. return false;
  98. }
  99. /**
  100. * @access private
  101. */
  102. public function getMethods() {
  103. return $this->methods;
  104. }
  105. /**
  106. * Add a notification event to the queue
  107. *
  108. * @param string $action Action name
  109. * @param string $type Type of the object of the action
  110. * @param \ElggData $object The object of the action
  111. * @return void
  112. * @access private
  113. */
  114. public function enqueueEvent($action, $type, $object) {
  115. if ($object instanceof \ElggData) {
  116. $object_type = $object->getType();
  117. $object_subtype = $object->getSubtype();
  118. $registered = false;
  119. if (isset($this->events[$object_type])
  120. && isset($this->events[$object_type][$object_subtype])
  121. && in_array($action, $this->events[$object_type][$object_subtype])) {
  122. $registered = true;
  123. }
  124. if ($registered) {
  125. $params = array(
  126. 'action' => $action,
  127. 'object' => $object,
  128. );
  129. $registered = $this->hooks->trigger('enqueue', 'notification', $params, $registered);
  130. }
  131. if ($registered) {
  132. $this->queue->enqueue(new \Elgg\Notifications\Event($object, $action));
  133. }
  134. }
  135. }
  136. /**
  137. * Pull notification events from queue until stop time is reached
  138. *
  139. * @param int $stopTime The Unix time to stop sending notifications
  140. * @return int The number of notification events handled
  141. * @access private
  142. */
  143. public function processQueue($stopTime) {
  144. $this->subscriptions->methods = $this->methods;
  145. $count = 0;
  146. // @todo grab mutex
  147. $ia = $this->session->setIgnoreAccess(true);
  148. while (time() < $stopTime) {
  149. // dequeue notification event
  150. $event = $this->queue->dequeue();
  151. if (!$event) {
  152. break;
  153. }
  154. // test for usage of the deprecated override hook
  155. if ($this->existsDeprecatedNotificationOverride($event)) {
  156. continue;
  157. }
  158. $subscriptions = $this->subscriptions->getSubscriptions($event);
  159. // return false to stop the default notification sender
  160. $params = array('event' => $event, 'subscriptions' => $subscriptions);
  161. if ($this->hooks->trigger('send:before', 'notifications', $params, true)) {
  162. $this->sendNotifications($event, $subscriptions);
  163. }
  164. $this->hooks->trigger('send:after', 'notifications', $params);
  165. $count++;
  166. }
  167. // release mutex
  168. $this->session->setIgnoreAccess($ia);
  169. return $count;
  170. }
  171. /**
  172. * Sends the notifications based on subscriptions
  173. *
  174. * @param \Elgg\Notifications\Event $event Notification event
  175. * @param array $subscriptions Subscriptions for this event
  176. * @return int The number of notifications handled
  177. * @access private
  178. */
  179. protected function sendNotifications($event, $subscriptions) {
  180. if (!$this->methods) {
  181. return 0;
  182. }
  183. $count = 0;
  184. foreach ($subscriptions as $guid => $methods) {
  185. foreach ($methods as $method) {
  186. if (in_array($method, $this->methods)) {
  187. if ($this->sendNotification($event, $guid, $method)) {
  188. $count++;
  189. }
  190. }
  191. }
  192. }
  193. return $count;
  194. }
  195. /**
  196. * Send a notification to a subscriber
  197. *
  198. * @param \Elgg\Notifications\Event $event The notification event
  199. * @param int $guid The guid of the subscriber
  200. * @param string $method The notification method
  201. * @return bool
  202. * @access private
  203. */
  204. protected function sendNotification(\Elgg\Notifications\Event $event, $guid, $method) {
  205. $recipient = get_user($guid);
  206. if (!$recipient || $recipient->isBanned()) {
  207. return false;
  208. }
  209. // don't notify the creator of the content
  210. if ($recipient->getGUID() == $event->getActorGUID()) {
  211. return false;
  212. }
  213. $actor = $event->getActor();
  214. $object = $event->getObject();
  215. if (!$actor || !$object) {
  216. return false;
  217. }
  218. if (($object instanceof ElggEntity) && !has_access_to_entity($object, $recipient)) {
  219. return false;
  220. }
  221. $language = $recipient->language;
  222. $params = array(
  223. 'event' => $event,
  224. 'method' => $method,
  225. 'recipient' => $recipient,
  226. 'language' => $language,
  227. 'object' => $object,
  228. );
  229. $subject = _elgg_services()->translator->translate('notification:subject', array($actor->name), $language);
  230. $body = _elgg_services()->translator->translate('notification:body', array($object->getURL()), $language);
  231. $notification = new \Elgg\Notifications\Notification($event->getActor(), $recipient, $language, $subject, $body, '', $params);
  232. $type = 'notification:' . $event->getDescription();
  233. if ($this->hooks->hasHandler('prepare', $type)) {
  234. $notification = $this->hooks->trigger('prepare', $type, $params, $notification);
  235. } else {
  236. // pre Elgg 1.9 notification message generation
  237. $notification = $this->getDeprecatedNotificationBody($notification, $event, $method);
  238. }
  239. if ($this->hooks->hasHandler('send', "notification:$method")) {
  240. // return true to indicate the notification has been sent
  241. $params = array(
  242. 'notification' => $notification,
  243. 'event' => $event,
  244. );
  245. return $this->hooks->trigger('send', "notification:$method", $params, false);
  246. } else {
  247. // pre Elgg 1.9 notification handler
  248. $userGuid = $notification->getRecipientGUID();
  249. $senderGuid = $notification->getSenderGUID();
  250. $subject = $notification->subject;
  251. $body = $notification->body;
  252. $params = $notification->params;
  253. return (bool)_elgg_notify_user($userGuid, $senderGuid, $subject, $body, $params, array($method));
  254. }
  255. }
  256. /**
  257. * Register a deprecated notification handler
  258. *
  259. * @param string $method Method name
  260. * @param string $handler Handler callback
  261. * @return void
  262. */
  263. public function registerDeprecatedHandler($method, $handler) {
  264. $this->deprHandlers[$method] = $handler;
  265. }
  266. /**
  267. * Get a deprecated notification handler callback
  268. *
  269. * @param string $method Method name
  270. * @return callback|null
  271. */
  272. public function getDeprecatedHandler($method) {
  273. if (isset($this->deprHandlers[$method])) {
  274. return $this->deprHandlers[$method];
  275. } else {
  276. return null;
  277. }
  278. }
  279. /**
  280. * Provides a way to incrementally wean Elgg's notifications code from the
  281. * global $NOTIFICATION_HANDLERS
  282. *
  283. * @return array
  284. */
  285. public function getMethodsAsDeprecatedGlobal() {
  286. $data = array();
  287. foreach ($this->methods as $method) {
  288. $data[$method] = 'empty';
  289. }
  290. return $data;
  291. }
  292. /**
  293. * Get the notification body using a pre-Elgg 1.9 plugin hook
  294. *
  295. * @param \Elgg\Notifications\Notification $notification Notification
  296. * @param \Elgg\Notifications\Event $event Event
  297. * @param string $method Method
  298. * @return \Elgg\Notifications\Notification
  299. */
  300. protected function getDeprecatedNotificationBody(\Elgg\Notifications\Notification $notification, \Elgg\Notifications\Event $event, $method) {
  301. $entity = $event->getObject();
  302. $params = array(
  303. 'entity' => $entity,
  304. 'to_entity' => $notification->getRecipient(),
  305. 'method' => $method,
  306. );
  307. $subject = $this->getDeprecatedNotificationSubject($entity->getType(), $entity->getSubtype());
  308. $string = $subject . ": " . $entity->getURL();
  309. $body = $this->hooks->trigger('notify:entity:message', $entity->getType(), $params, $string);
  310. if ($subject) {
  311. $notification->subject = $subject;
  312. $notification->body = $body;
  313. }
  314. return $notification;
  315. }
  316. /**
  317. * Set message subject for deprecated notification code
  318. *
  319. * @param string $type Entity type
  320. * @param string $subtype Entity subtype
  321. * @param string $subject Subject line
  322. * @return void
  323. */
  324. public function setDeprecatedNotificationSubject($type, $subtype, $subject) {
  325. if ($type == '') {
  326. $type = '__BLANK__';
  327. }
  328. if ($subtype == '') {
  329. $subtype = '__BLANK__';
  330. }
  331. if (!isset($this->deprSubjects[$type])) {
  332. $this->deprSubjects[$type] = array();
  333. }
  334. $this->deprSubjects[$type][$subtype] = $subject;
  335. }
  336. /**
  337. * Get the deprecated subject
  338. *
  339. * @param string $type Entity type
  340. * @param string $subtype Entity subtype
  341. * @return string
  342. */
  343. protected function getDeprecatedNotificationSubject($type, $subtype) {
  344. if ($type == '') {
  345. $type = '__BLANK__';
  346. }
  347. if ($subtype == '') {
  348. $subtype = '__BLANK__';
  349. }
  350. if (!isset($this->deprSubjects[$type])) {
  351. return '';
  352. }
  353. if (!isset($this->deprSubjects[$type][$subtype])) {
  354. return '';
  355. }
  356. return $this->deprSubjects[$type][$subtype];
  357. }
  358. /**
  359. * Is someone using the deprecated override
  360. *
  361. * @param \Elgg\Notifications\Event $event Event
  362. * @return boolean
  363. */
  364. protected function existsDeprecatedNotificationOverride(\Elgg\Notifications\Event $event) {
  365. $entity = $event->getObject();
  366. if (!elgg_instanceof($entity)) {
  367. return false;
  368. }
  369. $params = array(
  370. 'event' => $event->getAction(),
  371. 'object_type' => $entity->getType(),
  372. 'object' => $entity,
  373. );
  374. $hookresult = $this->hooks->trigger('object:notifications', $entity->getType(), $params, false);
  375. if ($hookresult === true) {
  376. elgg_deprecated_notice("Using the plugin hook 'object:notifications' has been deprecated by the hook 'send:before', 'notifications'", 1.9);
  377. return true;
  378. } else {
  379. return false;
  380. }
  381. }
  382. }