PageRenderTime 116ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/engine/lib/notification.php

https://github.com/fragilbert/Elgg
PHP | 549 lines | 289 code | 74 blank | 186 comment | 65 complexity | 14bee62aff9e3b54ac51374756f9b0c4 MD5 | raw file
Possible License(s): MIT, BSD-3-Clause, LGPL-2.1, GPL-2.0
  1. <?php
  2. /**
  3. * Notifications
  4. * This file contains classes and functions which allow plugins to register and send notifications.
  5. *
  6. * There are notification methods which are provided out of the box
  7. * (see notification_init() ). Each method is identified by a string, e.g. "email".
  8. *
  9. * To register an event use register_notification_handler() and pass the method name and a
  10. * handler function.
  11. *
  12. * To send a notification call notify() passing it the method you wish to use combined with a
  13. * number of method specific addressing parameters.
  14. *
  15. * Catch NotificationException to trap errors.
  16. *
  17. * @package Elgg.Core
  18. * @subpackage Notifications
  19. */
  20. /** Notification handlers */
  21. global $NOTIFICATION_HANDLERS;
  22. $NOTIFICATION_HANDLERS = array();
  23. /**
  24. * This function registers a handler for a given notification type (eg "email")
  25. *
  26. * @param string $method The method
  27. * @param string $handler The handler function, in the format
  28. * "handler(ElggEntity $from, ElggUser $to, $subject,
  29. * $message, array $params = NULL)". This function should
  30. * return false on failure, and true/a tracking message ID on success.
  31. * @param array $params An associated array of other parameters for this handler
  32. * defining some properties eg. supported msg length or rich text support.
  33. *
  34. * @return bool
  35. */
  36. function register_notification_handler($method, $handler, $params = NULL) {
  37. global $NOTIFICATION_HANDLERS;
  38. if (is_callable($handler, true)) {
  39. $NOTIFICATION_HANDLERS[$method] = new stdClass;
  40. $NOTIFICATION_HANDLERS[$method]->handler = $handler;
  41. if ($params) {
  42. foreach ($params as $k => $v) {
  43. $NOTIFICATION_HANDLERS[$method]->$k = $v;
  44. }
  45. }
  46. return true;
  47. }
  48. return false;
  49. }
  50. /**
  51. * This function unregisters a handler for a given notification type (eg "email")
  52. *
  53. * @param string $method The method
  54. *
  55. * @return void
  56. * @since 1.7.1
  57. */
  58. function unregister_notification_handler($method) {
  59. global $NOTIFICATION_HANDLERS;
  60. if (isset($NOTIFICATION_HANDLERS[$method])) {
  61. unset($NOTIFICATION_HANDLERS[$method]);
  62. }
  63. }
  64. /**
  65. * Notify a user via their preferences.
  66. *
  67. * @param mixed $to Either a guid or an array of guid's to notify.
  68. * @param int $from GUID of the sender, which may be a user, site or object.
  69. * @param string $subject Message subject.
  70. * @param string $message Message body.
  71. * @param array $params Misc additional parameters specific to various methods.
  72. * @param mixed $methods_override A string, or an array of strings specifying the delivery
  73. * methods to use - or leave blank for delivery using the
  74. * user's chosen delivery methods.
  75. *
  76. * @return array Compound array of each delivery user/delivery method's success or failure.
  77. * @throws NotificationException
  78. */
  79. function notify_user($to, $from, $subject, $message, array $params = NULL, $methods_override = "") {
  80. global $NOTIFICATION_HANDLERS;
  81. // Sanitise
  82. if (!is_array($to)) {
  83. $to = array((int)$to);
  84. }
  85. $from = (int)$from;
  86. //$subject = sanitise_string($subject);
  87. // Get notification methods
  88. if (($methods_override) && (!is_array($methods_override))) {
  89. $methods_override = array($methods_override);
  90. }
  91. $result = array();
  92. foreach ($to as $guid) {
  93. // Results for a user are...
  94. $result[$guid] = array();
  95. if ($guid) { // Is the guid > 0?
  96. // Are we overriding delivery?
  97. $methods = $methods_override;
  98. if (!$methods) {
  99. $tmp = (array)get_user_notification_settings($guid);
  100. $methods = array();
  101. foreach ($tmp as $k => $v) {
  102. // Add method if method is turned on for user!
  103. if ($v) {
  104. $methods[] = $k;
  105. }
  106. }
  107. }
  108. if ($methods) {
  109. // Deliver
  110. foreach ($methods as $method) {
  111. if (!isset($NOTIFICATION_HANDLERS[$method])) {
  112. continue;
  113. }
  114. // Extract method details from list
  115. $details = $NOTIFICATION_HANDLERS[$method];
  116. $handler = $details->handler;
  117. /* @var callable $handler */
  118. if ((!$NOTIFICATION_HANDLERS[$method]) || (!$handler) || (!is_callable($handler))) {
  119. error_log(elgg_echo('NotificationException:NoHandlerFound', array($method)));
  120. }
  121. elgg_log("Sending message to $guid using $method");
  122. // Trigger handler and retrieve result.
  123. try {
  124. $result[$guid][$method] = call_user_func($handler,
  125. $from ? get_entity($from) : NULL, // From entity
  126. get_entity($guid), // To entity
  127. $subject, // The subject
  128. $message, // Message
  129. $params // Params
  130. );
  131. } catch (Exception $e) {
  132. error_log($e->getMessage());
  133. }
  134. }
  135. }
  136. }
  137. }
  138. return $result;
  139. }
  140. /**
  141. * Get the notification settings for a given user.
  142. *
  143. * @param int $user_guid The user id
  144. *
  145. * @return stdClass
  146. */
  147. function get_user_notification_settings($user_guid = 0) {
  148. $user_guid = (int)$user_guid;
  149. if ($user_guid == 0) {
  150. $user_guid = elgg_get_logged_in_user_guid();
  151. }
  152. // @todo: there should be a better way now that metadata is cached. E.g. just query for MD names, then
  153. // query user object directly
  154. $all_metadata = elgg_get_metadata(array(
  155. 'guid' => $user_guid,
  156. 'limit' => 0
  157. ));
  158. if ($all_metadata) {
  159. $prefix = "notification:method:";
  160. $return = new stdClass;
  161. foreach ($all_metadata as $meta) {
  162. $name = substr($meta->name, strlen($prefix));
  163. $value = $meta->value;
  164. if (strpos($meta->name, $prefix) === 0) {
  165. $return->$name = $value;
  166. }
  167. }
  168. return $return;
  169. }
  170. return false;
  171. }
  172. /**
  173. * Set a user notification pref.
  174. *
  175. * @param int $user_guid The user id.
  176. * @param string $method The delivery method (eg. email)
  177. * @param bool $value On(true) or off(false).
  178. *
  179. * @return bool
  180. */
  181. function set_user_notification_setting($user_guid, $method, $value) {
  182. $user_guid = (int)$user_guid;
  183. $method = sanitise_string($method);
  184. $user = get_entity($user_guid);
  185. if (!$user) {
  186. $user = elgg_get_logged_in_user_entity();
  187. }
  188. if (($user) && ($user instanceof ElggUser)) {
  189. $prefix = "notification:method:$method";
  190. $user->$prefix = $value;
  191. $user->save();
  192. return true;
  193. }
  194. return false;
  195. }
  196. /**
  197. * Send a notification via email.
  198. *
  199. * @param ElggEntity $from The from user/site/object
  200. * @param ElggUser $to To which user?
  201. * @param string $subject The subject of the message.
  202. * @param string $message The message body
  203. * @param array $params Optional parameters (none taken in this instance)
  204. *
  205. * @return bool
  206. * @throws NotificationException
  207. * @access private
  208. */
  209. function email_notify_handler(ElggEntity $from, ElggUser $to, $subject, $message,
  210. array $params = NULL) {
  211. global $CONFIG;
  212. if (!$from) {
  213. $msg = elgg_echo('NotificationException:MissingParameter', array('from'));
  214. throw new NotificationException($msg);
  215. }
  216. if (!$to) {
  217. $msg = elgg_echo('NotificationException:MissingParameter', array('to'));
  218. throw new NotificationException($msg);
  219. }
  220. if ($to->email == "") {
  221. $msg = elgg_echo('NotificationException:NoEmailAddress', array($to->guid));
  222. throw new NotificationException($msg);
  223. }
  224. // To
  225. $to = $to->email;
  226. // From
  227. $site = elgg_get_site_entity();
  228. // If there's an email address, use it - but only if its not from a user.
  229. if (!($from instanceof ElggUser) && $from->email) {
  230. $from = $from->email;
  231. } else if ($site && $site->email) {
  232. // Use email address of current site if we cannot use sender's email
  233. $from = $site->email;
  234. } else {
  235. // If all else fails, use the domain of the site.
  236. $from = 'noreply@' . get_site_domain($CONFIG->site_guid);
  237. }
  238. return elgg_send_email($from, $to, $subject, $message);
  239. }
  240. /**
  241. * Send an email to any email address
  242. *
  243. * @param string $from Email address or string: "name <email>"
  244. * @param string $to Email address or string: "name <email>"
  245. * @param string $subject The subject of the message
  246. * @param string $body The message body
  247. * @param array $params Optional parameters (none used in this function)
  248. *
  249. * @return bool
  250. * @throws NotificationException
  251. * @since 1.7.2
  252. */
  253. function elgg_send_email($from, $to, $subject, $body, array $params = NULL) {
  254. global $CONFIG;
  255. if (!$from) {
  256. $msg = elgg_echo('NotificationException:MissingParameter', array('from'));
  257. throw new NotificationException($msg);
  258. }
  259. if (!$to) {
  260. $msg = elgg_echo('NotificationException:MissingParameter', array('to'));
  261. throw new NotificationException($msg);
  262. }
  263. $header_fields = array(
  264. "Content-Type" => "text/plain; charset=UTF-8; format=flowed",
  265. "MIME-Version" => "1.0",
  266. "Content-Transfer-Encoding" => "8bit",
  267. );
  268. // return TRUE/FALSE to stop elgg_send_email() from sending
  269. $mail_params = array(
  270. 'to' => $to,
  271. 'from' => $from,
  272. 'subject' => $subject,
  273. 'body' => $body,
  274. 'headers' => $header_fields,
  275. 'params' => $params,
  276. );
  277. // $mail_params is passed as both params and return value. The former is for backwards
  278. // compatibility. The latter is so handlers can now alter the contents/headers of
  279. // the email by returning the array
  280. $result = elgg_trigger_plugin_hook('email', 'system', $mail_params, $mail_params);
  281. if (is_array($result)) {
  282. foreach (array('to', 'from', 'subject', 'body', 'headers') as $key) {
  283. if (isset($result[$key])) {
  284. ${$key} = $result[$key];
  285. }
  286. }
  287. } elseif ($result !== NULL) {
  288. return $result;
  289. }
  290. $header_eol = "\r\n";
  291. if (isset($CONFIG->broken_mta) && $CONFIG->broken_mta) {
  292. // Allow non-RFC 2822 mail headers to support some broken MTAs
  293. $header_eol = "\n";
  294. }
  295. // Windows is somewhat broken, so we use just address for to and from
  296. if (strtolower(substr(PHP_OS, 0, 3)) == 'win') {
  297. // strip name from to and from
  298. if (strpos($to, '<')) {
  299. preg_match('/<(.*)>/', $to, $matches);
  300. $to = $matches[1];
  301. }
  302. if (strpos($from, '<')) {
  303. preg_match('/<(.*)>/', $from, $matches);
  304. $from = $matches[1];
  305. }
  306. }
  307. $headers = "From: $from{$header_eol}";
  308. foreach ($header_fields as $header_field => $header_value) {
  309. $headers .= "$header_field: $header_value{$header_eol}";
  310. }
  311. // Sanitise subject by stripping line endings
  312. $subject = preg_replace("/(\r\n|\r|\n)/", " ", $subject);
  313. // this is because Elgg encodes everything and matches what is done with body
  314. $subject = html_entity_decode($subject, ENT_COMPAT, 'UTF-8'); // Decode any html entities
  315. if (is_callable('mb_encode_mimeheader')) {
  316. $subject = mb_encode_mimeheader($subject, "UTF-8", "B");
  317. }
  318. // Format message
  319. $body = html_entity_decode($body, ENT_COMPAT, 'UTF-8'); // Decode any html entities
  320. $body = elgg_strip_tags($body); // Strip tags from message
  321. $body = preg_replace("/(\r\n|\r)/", "\n", $body); // Convert to unix line endings in body
  322. $body = preg_replace("/^From/", ">From", $body); // Change lines starting with From to >From
  323. $body = wordwrap($body);
  324. return mail($to, $subject, $body, $headers);
  325. }
  326. /**
  327. * Correctly initialise notifications and register the email handler.
  328. *
  329. * @return void
  330. * @access private
  331. */
  332. function notification_init() {
  333. // Register a notification handler for the default email method
  334. register_notification_handler("email", "email_notify_handler");
  335. // Add settings view to user settings & register action
  336. elgg_extend_view('forms/account/settings', 'core/settings/account/notifications');
  337. elgg_register_plugin_hook_handler('usersettings:save', 'user', 'notification_user_settings_save');
  338. }
  339. /**
  340. * Includes the action to save user notifications
  341. *
  342. * @return void
  343. * @todo why can't this call action(...)?
  344. * @access private
  345. */
  346. function notification_user_settings_save() {
  347. global $CONFIG;
  348. //@todo Wha??
  349. include($CONFIG->path . "actions/notifications/settings/usersettings/save.php");
  350. }
  351. /**
  352. * Register an entity type and subtype to be eligible for notifications
  353. *
  354. * @param string $entity_type The type of entity
  355. * @param string $object_subtype Its subtype
  356. * @param string $language_name Its localized notification string (eg "New blog post")
  357. *
  358. * @return void
  359. */
  360. function register_notification_object($entity_type, $object_subtype, $language_name) {
  361. global $CONFIG;
  362. if ($entity_type == '') {
  363. $entity_type = '__BLANK__';
  364. }
  365. if ($object_subtype == '') {
  366. $object_subtype = '__BLANK__';
  367. }
  368. if (!isset($CONFIG->register_objects)) {
  369. $CONFIG->register_objects = array();
  370. }
  371. if (!isset($CONFIG->register_objects[$entity_type])) {
  372. $CONFIG->register_objects[$entity_type] = array();
  373. }
  374. $CONFIG->register_objects[$entity_type][$object_subtype] = $language_name;
  375. }
  376. /**
  377. * Establish a 'notify' relationship between the user and a content author
  378. *
  379. * @param int $user_guid The GUID of the user who wants to follow a user's content
  380. * @param int $author_guid The GUID of the user whose content the user wants to follow
  381. *
  382. * @return bool Depending on success
  383. */
  384. function register_notification_interest($user_guid, $author_guid) {
  385. return add_entity_relationship($user_guid, 'notify', $author_guid);
  386. }
  387. /**
  388. * Remove a 'notify' relationship between the user and a content author
  389. *
  390. * @param int $user_guid The GUID of the user who is following a user's content
  391. * @param int $author_guid The GUID of the user whose content the user wants to unfollow
  392. *
  393. * @return bool Depending on success
  394. */
  395. function remove_notification_interest($user_guid, $author_guid) {
  396. return remove_entity_relationship($user_guid, 'notify', $author_guid);
  397. }
  398. /**
  399. * Automatically triggered notification on 'create' events that looks at registered
  400. * objects and attempts to send notifications to anybody who's interested
  401. *
  402. * @see register_notification_object
  403. *
  404. * @param string $event create
  405. * @param string $object_type mixed
  406. * @param mixed $object The object created
  407. *
  408. * @return bool
  409. * @access private
  410. */
  411. function object_notifications($event, $object_type, $object) {
  412. // We only want to trigger notification events for ElggEntities
  413. if ($object instanceof ElggEntity) {
  414. /* @var ElggEntity $object */
  415. // Get config data
  416. global $CONFIG, $SESSION, $NOTIFICATION_HANDLERS;
  417. $hookresult = elgg_trigger_plugin_hook('object:notifications', $object_type, array(
  418. 'event' => $event,
  419. 'object_type' => $object_type,
  420. 'object' => $object,
  421. ), false);
  422. if ($hookresult === true) {
  423. return true;
  424. }
  425. // Have we registered notifications for this type of entity?
  426. $object_type = $object->getType();
  427. if (empty($object_type)) {
  428. $object_type = '__BLANK__';
  429. }
  430. $object_subtype = $object->getSubtype();
  431. if (empty($object_subtype)) {
  432. $object_subtype = '__BLANK__';
  433. }
  434. if (isset($CONFIG->register_objects[$object_type][$object_subtype])) {
  435. $subject = $CONFIG->register_objects[$object_type][$object_subtype];
  436. $string = $subject . ": " . $object->getURL();
  437. // Get users interested in content from this person and notify them
  438. // (Person defined by container_guid so we can also subscribe to groups if we want)
  439. foreach ($NOTIFICATION_HANDLERS as $method => $foo) {
  440. $interested_users = elgg_get_entities_from_relationship(array(
  441. 'site_guids' => ELGG_ENTITIES_ANY_VALUE,
  442. 'relationship' => 'notify' . $method,
  443. 'relationship_guid' => $object->container_guid,
  444. 'inverse_relationship' => TRUE,
  445. 'type' => 'user',
  446. 'limit' => false
  447. ));
  448. /* @var ElggUser[] $interested_users */
  449. if ($interested_users && is_array($interested_users)) {
  450. foreach ($interested_users as $user) {
  451. if ($user instanceof ElggUser && !$user->isBanned()) {
  452. if (($user->guid != $SESSION['user']->guid) && has_access_to_entity($object, $user)
  453. && $object->access_id != ACCESS_PRIVATE) {
  454. $body = elgg_trigger_plugin_hook('notify:entity:message', $object->getType(), array(
  455. 'entity' => $object,
  456. 'to_entity' => $user,
  457. 'method' => $method), $string);
  458. if (empty($body) && $body !== false) {
  459. $body = $string;
  460. }
  461. if ($body !== false) {
  462. notify_user($user->guid, $object->container_guid, $subject, $body,
  463. null, array($method));
  464. }
  465. }
  466. }
  467. }
  468. }
  469. }
  470. }
  471. }
  472. }
  473. // Register a startup event
  474. elgg_register_event_handler('init', 'system', 'notification_init', 0);
  475. elgg_register_event_handler('create', 'object', 'object_notifications');