123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597 |
- <?php
- /**
- * Notifier
- *
- * @package Notifier
- */
- elgg_register_event_handler('init', 'system', 'notifier_init');
- /**
- * Initialize the plugin
- *
- * @return void
- */
- function notifier_init () {
- notifier_set_view_listener();
- // Add hidden popup module to topbar
- elgg_extend_view('page/elements/topbar', 'notifier/popup');
- elgg_require_js('notifier/notifier');
- // Must always have lightbox loaded because views needing it come via AJAX
- elgg_load_js('lightbox');
- elgg_load_css('lightbox');
- elgg_register_page_handler('notifier', 'notifier_page_handler');
- // Add css
- elgg_extend_view('css/elgg', 'notifier/css');
- elgg_register_notification_method('notifier');
- elgg_register_plugin_hook_handler('send', 'notification:notifier', 'notifier_notification_send');
- elgg_register_plugin_hook_handler('route', 'friendsof', 'notifier_read_friends_notification');
- elgg_register_event_handler('create', 'relationship', 'notifier_relationship_notifications');
- elgg_register_event_handler('delete', 'relationship', 'notifier_read_group_invitation_notification');
- // Hook handler for cron that removes old messages
- elgg_register_plugin_hook_handler('cron', 'daily', 'notifier_cron');
- elgg_register_plugin_hook_handler('register', 'menu:topbar', 'notifier_topbar_menu_setup');
- elgg_register_event_handler('create', 'user', 'notifier_enable_for_new_user');
- elgg_register_event_handler('join', 'group', 'notifier_enable_for_new_group_member');
- $action_path = elgg_get_plugins_path() . 'notifier/actions/notifier/';
- elgg_register_action('notifier/dismiss', $action_path . 'dismiss.php');
- elgg_register_action('notifier/clear', $action_path . 'clear.php');
- elgg_register_action('notifier/delete', $action_path . 'delete.php');
- }
- /**
- * Add notifier icon to topbar menu
- *
- * The menu item opens a popup module defined in view notifier/popup
- *
- * @param string $hook Hook name
- * @param string $type Hook type
- * @param ElggMenuItem[] $return Array of menu items
- * @param array $params Hook parameters
- * @return ElggMenuItem[] $return
- */
- function notifier_topbar_menu_setup ($hook, $type, $return, $params) {
- if (elgg_is_logged_in()) {
- // Get amount of unread notifications
- $count = (int)notifier_count_unread();
- $text = elgg_view_icon('attention');
- $tooltip = elgg_echo("notifier:unreadcount", array($count));
- if ($count > 0) {
- if ($count > 99) {
- // Don't allow the counter to grow endlessly
- $count = '99+';
- }
- $hidden = '';
- } else {
- $hidden = 'class="hidden"';
- }
- $text .= "<span id=\"notifier-new\" $hidden>$count</span>";
- $item = ElggMenuItem::factory(array(
- 'name' => 'notifier',
- 'href' => '#notifier-popup',
- 'text' => $text,
- 'priority' => 600,
- 'title' => $tooltip,
- 'rel' => 'popup',
- 'id' => 'notifier-popup-link'
- ));
- $return[] = $item;
- }
- return $return;
- }
- /**
- * Dispatches notifier pages
- *
- * URLs take the form of
- * All notifications: notifier/all
- * Subjects of a notification: notifier/subjects/<notification guid>
- *
- * @param array $page Array of URL segments
- * @return bool Was the page handled successfully
- */
- function notifier_page_handler ($page) {
- gatekeeper();
- if (empty($page[0])) {
- $page[0] = 'all';
- }
- $path = elgg_get_plugins_path() . 'notifier/pages/notifier/';
- switch ($page[0]) {
- case 'popup':
- include_once($path . 'popup.php');
- break;
- case 'subjects':
- set_input('guid', $page[1]);
- include_once($path . 'subjects.php');
- break;
- case 'all':
- default:
- include_once($path . 'list.php');
- break;
- }
- return true;
- }
- /**
- * Create a notification
- *
- * @param string $hook Hook name
- * @param string $type Hook type
- * @param bool $result Has the notification been sent
- * @param array $params Hook parameters
- * @return bool Was the notification handled successfully
- */
- function notifier_notification_send($hook, $type, $result, $params) {
- $notification = $params['notification'];
- /* @var Elgg_Notifications_Notification $notification */
- $event = $params['event'];
- /* @var Elgg_Notifications_Event $event */
- if (!$event) {
- // Plugin is calling notify_user() so stop here and let
- // the NotificationService handle the notification later.
- return false;
- }
- $ia = elgg_set_ignore_access(true);
- $action = $event->getAction();
- $object = $event->getObject();
- $string = "river:{$action}:{$object->getType()}:{$object->getSubtype()}";
- $recipient = $notification->getRecipient();
- $actor = $event->getActor();
- switch ($object->getType()) {
- case 'annotation':
- // Get the entity that was annotated
- $entity = $object->getEntity();
- break;
- case 'relationship':
- $entity = get_entity($object->guid_two);
- break;
- default:
- if ($object instanceof ElggComment) {
- // Use comment's container as notification target
- $entity = $object->getContainerEntity();
- // Check the action because this isn't necessarily a new comment,
- // but e.g. someone being mentioned in a comment
- if ($action == 'create') {
- $string = "river:comment:{$entity->getType()}:{$entity->getSubtype()}";
- }
- // TODO How about discussion replies?
- } else {
- // This covers all other entities
- $entity = $object;
- }
- }
- if ($object->getType() == 'annotation' || $object->getType() == 'relationship' || ($object instanceof ElggComment && $action == 'create')) {
- // Check if similar notification already exists
- $existing = notifier_get_similar($event->getDescription(), $entity, $recipient);
- if ($existing) {
- // Update the existing notification
- $existing->setSubject($actor);
- $existing->markUnread();
- // time_created must be used because time_updated gets updated
- // automatically and it won't therefore match the time_created
- // of the object triggering the notification
- $existing->time_created = $object->time_created;
- return $existing->save();
- }
- }
- // If the river string is not available, fall back to summary or subject
- if ($string == elgg_echo($string)) {
- if ($notification->summary) {
- $string = $notification->summary;
- } else {
- $string = $notification->subject;
- }
- }
- $note = new ElggNotification();
- $note->title = $string;
- $note->owner_guid = $recipient->getGUID();
- $note->container_guid = $recipient->getGUID();
- $note->event = $event->getDescription();
- // The notification may be being created later than the event took
- // place, so use the original time_created instead of time()
- $note->time_created = $object->time_created;
- if ($note->save()) {
- $note->setSubject($actor);
- $note->setTarget($entity);
- }
- elgg_set_ignore_access($ia);
- if ($note) {
- return true;
- }
- }
- /**
- * Get the count of all unread notifications
- *
- * @return integer
- */
- function notifier_count_unread () {
- return notifier_get_unread(array('count' => true));
- }
- /**
- * Get all unread messages for logged in users
- *
- * @param array $options Options passed to elgg_get_entities_from_metadata
- * @return ElggNotification[]
- */
- function notifier_get_unread ($options = array()) {
- $defaults = array(
- 'type' => 'object',
- 'subtype' => 'notification',
- 'limit' => false,
- 'owner_guid' => elgg_get_logged_in_user_guid(),
- 'metadata_name_value_pairs' => array(
- 'name' => 'status',
- 'value' => 'unread'
- )
- );
- $options = array_merge($defaults, $options);
- return elgg_get_entities_from_metadata($options);
- }
- /**
- * Remove over week old notifications that have been read
- *
- * @param string $hook Hook name
- * @param string $type Hook type
- * @param string $return Old stdOut contents
- * @param array $params Array containing the time when cron was triggered
- * @return void
- */
- function notifier_cron ($hook, $type, $return, $params) {
- // One week ago
- $time = time() - 60 * 60 * 24 * 7;
- $options = array(
- 'type' => 'object',
- 'subtype' => 'notification',
- 'wheres' => array("e.time_created < $time"),
- 'metadata_name_value_pairs' => array(
- 'name' => 'status',
- 'value' => 'read'
- ),
- 'limit' => false
- );
- $ia = elgg_set_ignore_access(true);
- $notifications = elgg_get_entities_from_metadata($options);
- $options['count'] = true;
- $count = elgg_get_entities_from_metadata($options);
- foreach ($notifications as $notification) {
- $notification->delete();
- }
- echo "<p>Removed $count notifications.</p>";
- elgg_set_ignore_access($ia);
- }
- /**
- * Add view listener to views that may be the targets of notifications
- *
- * @return void
- */
- function notifier_set_view_listener () {
- $dbprefix = elgg_get_config('dbprefix');
- $types = get_data("SELECT * FROM {$dbprefix}entity_subtypes");
- // These subtypes do not have notifications so they can be skipped
- $skip = array(
- 'plugin',
- 'widget',
- 'admin_notice',
- 'notification',
- 'messages',
- 'reported_content',
- 'site_notification'
- );
- foreach ($types as $type) {
- if (in_array($type->subtype, $skip)) {
- continue;
- }
- elgg_extend_view("object/{$type->subtype}", 'notifier/view_listener');
- }
- // Some manual additions
- elgg_extend_view('profile/wrapper', 'notifier/view_listener');
- }
- /**
- * Enable notifier by default for new users according to plugin settings.
- *
- * We do this using 'create, user' event instead of 'register, user' plugin
- * hook so that it affects also users created by an admin.
- *
- * @param string $event 'create'
- * @param string $type 'user'
- * @param ElggUser $user The user that was created
- * @return boolean
- */
- function notifier_enable_for_new_user ($event, $type, $user) {
- $personal = (boolean) elgg_get_plugin_setting('enable_personal', 'notifier');
- $collections = (boolean) elgg_get_plugin_setting('enable_collections', 'notifier');
- if ($personal) {
- $prefix = "notification:method:notifier";
- $user->$prefix = true;
- }
- if ($collections) {
- /**
- * This function is triggered before invite code is checked so it's
- * enough just to add the setting. Notifications plugin will take care
- * of adding the 'notifynotifier' relationship in case user was invited.
- */
- $user->collections_notifications_preferences_notifier = '-1';
- }
- $user->save();
- return true;
- }
- /**
- * Enable notifier as notification method when joining a group.
- *
- * @param string $event 'join'
- * @param string $type 'group'
- * @param array $params Array containing ElggUser and ElggGroup
- */
- function notifier_enable_for_new_group_member ($event, $type, $params) {
- $group = $params['group'];
- $user = $params['user'];
- $enabled = (boolean) elgg_get_plugin_setting('enable_groups', 'notifier');
- if ($enabled) {
- if (elgg_instanceof($group, 'group') && elgg_instanceof($user, 'user')) {
- add_entity_relationship($user->guid, 'notifynotifier', $group->guid);
- }
- }
- }
- /**
- * Get existing notifications that match the given parameters.
- *
- * This can be used when we want to update an old notification.
- * E.g. "A likes X" and "B likes X" become "A and B like X".
- *
- * @param string $event_name String like "action:type:subtype"
- * @param ElggEntity $entity Entity being notified about
- * @param ElggUser $recipient User being notified
- * @return ElggNotification|null
- */
- function notifier_get_similar($event_name, $entity, $recipient) {
- $db_prefix = elgg_get_config('dbprefix');
- $ia = elgg_set_ignore_access(true);
- $object_relationship = ElggNotification::HAS_OBJECT;
- // Notification (guid_one) has relationship 'hasObject' to target (guid_two)
- $options = array(
- 'type' => 'object',
- 'subtype' => 'notification',
- 'owner_guid' => $recipient->guid,
- 'metadata_name_value_pairs' => array(
- 'name' => 'event',
- 'value' => $event_name,
- ),
- 'joins' => array(
- "JOIN {$db_prefix}entity_relationships er ON e.guid = er.guid_one", // Object relationship
- ),
- 'wheres' => array(
- "er.guid_two = {$entity->guid}",
- "er.relationship = '$object_relationship'",
- ),
- );
- $notification = elgg_get_entities_from_metadata($options);
- if ($notification) {
- $notification = $notification[0];
- }
- elgg_set_ignore_access($ia);
- return $notification;
- }
- /**
- * Mark unread friend notifications as read.
- *
- * This hook is triggered when user goes to the "friendsof/<username>" page.
- *
- * @param string $hook Hook name
- * @param string $type Hook type
- * @param array $return Array containing 'identifier' and 'segments'
- * @param array $params This is empty
- * @return void
- */
- function notifier_read_friends_notification ($hook, $type, $return, $params) {
- // Get unread notifications that match the friending event
- $options = array(
- 'metadata_name_value_pairs' => array(
- 'name' => 'event',
- 'value' => 'create:relationship:friend',
- )
- );
- $notifications = notifier_get_unread($options);
- foreach ($notifications as $note) {
- $note->markRead();
- }
- }
- /**
- * Create notifications about new relationships
- *
- * The Elgg 1.9 notifications system does not yet process relationships
- * so we create the notifications manually on the 'create', 'relationship'
- * event instead.
- *
- * @param string $event 'create'
- * @param string $type 'relationship'
- * @param ElggRelationship $object The created relationships
- * @return boolean Always returns true
- */
- function notifier_relationship_notifications ($event, $type, $object) {
- $guid_one = $object->guid_one;
- $guid_two = $object->guid_two;
- $relationship = $object->relationship;
- switch ($relationship) {
- case 'friend':
- // Notification about a new friend
- $actor = get_user($guid_one);
- $recipient = get_user($guid_two);
- $target = $recipient;
- $string = 'friend:notifications:summary';
- break;
- case 'invited':
- // Notification about a group membership invitation
- $actor = elgg_get_logged_in_user_entity(); // User who invited
- $target = get_entity($guid_one); // The group
- $recipient = get_user($guid_two); // The invited user
- $string = 'groups:notifications:invitation';
- break;
- case 'membership_request':
- // Notification about a group membership invitation
- $actor = get_user($guid_one); // User who requested
- $target = get_entity($guid_two); // The group
- $recipient = get_user($target->owner_guid); // The group_owner
- $string = 'groups:notifications:membership_request';
- break;
- default;
- return true;
- }
- if (!$actor) {
- return true;
- }
- if (!$recipient) {
- return true;
- }
- if (!$target) {
- return true;
- }
- $ia = elgg_set_ignore_access(true);
- $event_name = "create:relationship:{$relationship}";
- $note = notifier_get_similar($event_name, $target, $recipient);
- if (!$note) {
- $note = new ElggNotification();
- $note->title = $string;
- $note->owner_guid = $recipient->guid;
- $note->container_guid = $recipient->guid;
- $note->event = $event_name;
- $note->save();
- $note->setTarget($target);
- } else {
- // Mark the existing notification as unread
- $note->markUnread();
- }
- $note->setSubject($actor);
- elgg_set_ignore_access($ia);
- // Returning false would delete the relationship
- return true;
- }
- /**
- * Delete notification about a group invitation when user accepts/deletes it
- *
- * @param string $event 'delete'
- * @param string $type 'relationship'
- * @param ElggRelationship $object The relationship being deleted
- * @return boolean
- */
- function notifier_read_group_invitation_notification($event, $type, $object) {
- $relationship = $object->relationship;
- // Proceed only if the relationship is an invitation
- if ($relationship != 'invited' && $relationship != 'membership_request') {
- return true;
- }
- // The group may be hidden, so ignore access
- $ia = elgg_set_ignore_access(true);
- if ($relationship === 'invited') {
- $group = get_entity($object->guid_one);
- } else {
- $group = get_entity($object->guid_two);
- }
- elgg_set_ignore_access($ia);
- // Proceed only if the invitation is for a group
- if (!$group instanceof ElggGroup) {
- return true;
- }
- $dbprefix = elgg_get_config('dbprefix');
- $has_object = ElggNotification::HAS_OBJECT;
- $options = array(
- 'joins' => array("JOIN {$dbprefix}entity_relationships er ON e.guid = er.guid_one"),
- 'wheres' => array("er.relationship = '{$has_object}' AND er.guid_two = {$group->guid}"),
- );
- // Get unread notifications
- $notifications = notifier_get_unread($options);
- foreach ($notifications as $note) {
- if ($note->event === "create:relationship:$relationship") {
- $note->markRead();
- }
- }
- // Returning true means that the relationship deletion can now proceed
- return true;
- }
|