<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[BuddyPress][11888] trunk: Move notification-grouping logic to MySQL.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://buddypress.trac.wordpress.org/changeset/11888">11888</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://buddypress.trac.wordpress.org/changeset/11888","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>boonebgorges</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2018-03-05 12:57:24 +0000 (Mon, 05 Mar 2018)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Move notification-grouping logic to MySQL.

The notification-grouping logic required to show the collapsed Notifications
toolbar - "You have 3 pending friend requests", etc - is currently done in
PHP, in a manner that requires all notifications to be loaded into memory
and processed. On sites where users have large numbers of notifications, this
operation requires extensive resources. By leaving the grouping to MySQL,
we can speed up processing in almost all situations.

This changeset also adds specific caching for grouped notifications.

Props m_uysl.
See <a href="http://buddypress.trac.wordpress.org/ticket/7130">#7130</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcbpnotificationsbpnotificationscachephp">trunk/src/bp-notifications/bp-notifications-cache.php</a></li>
<li><a href="#trunksrcbpnotificationsbpnotificationsfunctionsphp">trunk/src/bp-notifications/bp-notifications-functions.php</a></li>
<li><a href="#trunksrcbpnotificationsclassesclassbpnotificationsnotificationphp">trunk/src/bp-notifications/classes/class-bp-notifications-notification.php</a></li>
<li><a href="#trunktestsphpunittestcasesnotificationsfunctionsphp">trunk/tests/phpunit/testcases/notifications/functions.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcbpnotificationsbpnotificationscachephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/bp-notifications/bp-notifications-cache.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/bp-notifications/bp-notifications-cache.php     2018-03-05 11:10:35 UTC (rev 11887)
+++ trunk/src/bp-notifications/bp-notifications-cache.php       2018-03-05 12:57:24 UTC (rev 11888)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -44,6 +44,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> function bp_notifications_clear_all_for_user_cache( $user_id = 0 ) {
</span><span class="cx" style="display: block; padding: 0 10px">        wp_cache_delete( 'all_for_user_' . $user_id, 'bp_notifications' );
</span><span class="cx" style="display: block; padding: 0 10px">        wp_cache_delete( $user_id, 'bp_notifications_unread_count' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        wp_cache_delete( $user_id, 'bp_notifications_grouped_notifications' );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span></span></pre></div>
<a id="trunksrcbpnotificationsbpnotificationsfunctionsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/bp-notifications/bp-notifications-functions.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/bp-notifications/bp-notifications-functions.php 2018-03-05 11:10:35 UTC (rev 11887)
+++ trunk/src/bp-notifications/bp-notifications-functions.php   2018-03-05 12:57:24 UTC (rev 11888)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -163,6 +163,32 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Get a user's unread notifications, grouped by component and action.
+ *
+ * This function returns a list of notifications collapsed by component + action.
+ * See BP_Notifications_Notification::get_grouped_notifications_for_user() for
+ * more details.
+ *
+ * @since 3.0.0
+ *
+ * @param int $user_id ID of the user whose notifications are being fetched.
+ * @return array $notifications
+ */
+function bp_notifications_get_grouped_notifications_for_user( $user_id = 0 ) {
+       if ( empty( $user_id ) ) {
+               $user_id = ( bp_displayed_user_id() ) ? bp_displayed_user_id() : bp_loggedin_user_id();
+       }
+
+       $notifications = wp_cache_get( $user_id, 'bp_notifications_grouped_notifications' );
+       if ( false === $notifications ) {
+               $notifications = BP_Notifications_Notification::get_grouped_notifications_for_user( $user_id );
+               wp_cache_set( $user_id, $notifications, 'bp_notifications_grouped_notifications' );
+       }
+
+       return $notifications;
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Get notifications for a specific user.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 1.9.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -173,160 +199,119 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @return mixed Object or array on success, false on failure.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function bp_notifications_get_notifications_for_user( $user_id, $format = 'string' ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-       // Setup local variables.
</del><span class="cx" style="display: block; padding: 0 10px">         $bp = buddypress();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        // Get notifications (out of the cache, or query if necessary).
-       $notifications         = bp_notifications_get_all_notifications_for_user( $user_id );
-       $grouped_notifications = array(); // Notification groups.
-       $renderable            = array(); // Renderable notifications.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $notifications = bp_notifications_get_grouped_notifications_for_user( $user_id );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        // Group notifications by component and component_action and provide totals.
-       for ( $i = 0, $count = count( $notifications ); $i < $count; ++$i ) {
-               $notification = $notifications[$i];
-               $grouped_notifications[$notification->component_name][$notification->component_action][] = $notification;
-       }
-
-       // Bail if no notification groups.
-       if ( empty( $grouped_notifications ) ) {
-               return false;
-       }
-
</del><span class="cx" style="display: block; padding: 0 10px">         // Calculate a renderable output for each notification type.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        foreach ( $grouped_notifications as $component_name => $action_arrays ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ foreach ( $notifications as $notification_item ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $component_name = $notification_item->component_name;
</ins><span class="cx" style="display: block; padding: 0 10px">                 // We prefer that extended profile component-related notifications use
</span><span class="cx" style="display: block; padding: 0 10px">                // the component_name of 'xprofile'. However, the extended profile child
</span><span class="cx" style="display: block; padding: 0 10px">                // object in the $bp object is keyed as 'profile', which is where we need
</span><span class="cx" style="display: block; padding: 0 10px">                // to look for the registered notification callback.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( 'xprofile' == $component_name ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( 'xprofile' == $notification_item->component_name ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         $component_name = 'profile';
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // Skip if group is empty.
-               if ( empty( $action_arrays ) ) {
-                       continue;
-               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Callback function exists.
+               if ( isset( $bp->{$component_name}->notification_callback ) && is_callable( $bp->{$component_name}->notification_callback ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // Loop through each actionable item and try to map it to a component.
-               foreach ( (array) $action_arrays as $component_action_name => $component_action_items ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // Function should return an object.
+                       if ( 'object' === $format ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        // Get the number of actionable items.
-                       $action_item_count = count( $component_action_items );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         // Retrieve the content of the notification using the callback.
+                               $content = call_user_func( $bp->{$component_name}->notification_callback, $notification_item->component_action, $notification_item->item_id, $notification_item->secondary_item_id, $notification_item->total_count, 'array', $notification_item->id );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        // Skip if the count is less than 1.
-                       if ( $action_item_count < 1 ) {
-                               continue;
-                       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         // Create the object to be returned.
+                               $notification_object = $notification_item;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        // Callback function exists.
-                       if ( isset( $bp->{$component_name}->notification_callback ) && is_callable( $bp->{$component_name}->notification_callback ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         // Minimal backpat with non-compatible notification
+                               // callback functions.
+                               if ( is_string( $content ) ) {
+                                       $notification_object->content = $content;
+                                       $notification_object->href    = bp_loggedin_user_domain();
+                               } else {
+                                       $notification_object->content = $content['text'];
+                                       $notification_object->href    = $content['link'];
+                               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                // Function should return an object.
-                               if ( 'object' === $format ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         $renderable[] = $notification_object;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        // Retrieve the content of the notification using the callback.
-                                       $content = call_user_func(
-                                               $bp->{$component_name}->notification_callback,
-                                               $component_action_name,
-                                               $component_action_items[0]->item_id,
-                                               $component_action_items[0]->secondary_item_id,
-                                               $action_item_count,
-                                               'array',
-                                               $component_action_items[0]->id
-                                       );
-
-                                       // Create the object to be returned.
-                                       $notification_object = $component_action_items[0];
-
-                                       // Minimal backpat with non-compatible notification
-                                       // callback functions.
-                                       if ( is_string( $content ) ) {
-                                               $notification_object->content = $content;
-                                               $notification_object->href    = bp_loggedin_user_domain();
-                                       } else {
-                                               $notification_object->content = $content['text'];
-                                               $notification_object->href    = $content['link'];
-                                       }
-
-                                       $renderable[] = $notification_object;
-
</del><span class="cx" style="display: block; padding: 0 10px">                                 // Return an array of content strings.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                } else {
-                                       $content      = call_user_func( $bp->{$component_name}->notification_callback, $component_action_name, $component_action_items[0]->item_id, $component_action_items[0]->secondary_item_id, $action_item_count, 'string', $component_action_items[0]->id );
-                                       $renderable[] = $content;
-                               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 } else {
+                               $content      = call_user_func( $bp->{$component_name}->notification_callback, $notification_item->component_action, $notification_item->item_id, $notification_item->secondary_item_id, $notification_item->total_count, 'string', $notification_item->id );
+                               $renderable[] = $content;
+                       }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // @deprecated format_notification_function - 1.5
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        } elseif ( isset( $bp->{$component_name}->format_notification_function ) && function_exists( $bp->{$component_name}->format_notification_function ) ) {
-                               $renderable[] = call_user_func( $bp->{$component_name}->format_notification_function, $component_action_name, $component_action_items[0]->item_id, $component_action_items[0]->secondary_item_id, $action_item_count );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         } elseif ( isset( $bp->{$component_name}->format_notification_function ) && function_exists( $bp->{$component_name}->format_notification_function ) ) {
+                       $renderable[] = call_user_func( $bp->{$component_name}->notification_callback, $notification_item->component_action, $notification_item->item_id, $notification_item->secondary_item_id, $notification_item->total_count );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // Allow non BuddyPress components to hook in.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        } else {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         } else {
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                // The array to reference with apply_filters_ref_array().
-                               $ref_array = array(
-                                       $component_action_name,
-                                       $component_action_items[0]->item_id,
-                                       $component_action_items[0]->secondary_item_id,
-                                       $action_item_count,
-                                       $format,
-                                       $component_action_name, // Duplicated so plugins can check the canonical action name.
-                                       $component_name,
-                                       $component_action_items[0]->id
-                               );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // The array to reference with apply_filters_ref_array().
+                       $ref_array = array(
+                               $notification_item->component_action,
+                               $notification_item->item_id,
+                               $notification_item->secondary_item_id,
+                               $notification_item->total_count,
+                               $format,
+                               $notification_item->component_action, // Duplicated so plugins can check the canonical action name.
+                               $component_name,
+                               $notification_item->id,
+                       );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                // Function should return an object.
-                               if ( 'object' === $format ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // Function should return an object.
+                       if ( 'object' === $format ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        /**
-                                        * Filters the notification content for notifications created by plugins.
-                                        *
-                                        * If your plugin extends the {@link BP_Component} class, you should use the
-                                        * 'notification_callback' parameter in your extended
-                                        * {@link BP_Component::setup_globals()} method instead.
-                                        *
-                                        * @since 1.9.0
-                                        * @since 2.6.0 Added $component_action_name, $component_name, $id as parameters.
-                                        *
-                                        * @param string $content               Component action. Deprecated. Do not do checks against this! Use
-                                        *                                      the 6th parameter instead - $component_action_name.
-                                        * @param int    $item_id               Notification item ID.
-                                        * @param int    $secondary_item_id     Notification secondary item ID.
-                                        * @param int    $action_item_count     Number of notifications with the same action.
-                                        * @param string $format                Format of return. Either 'string' or 'object'.
-                                        * @param string $component_action_name Canonical notification action.
-                                        * @param string $component_name        Notification component ID.
-                                        * @param int    $id                    Notification ID.
-                                        *
-                                        * @return string|array If $format is 'string', return a string of the notification content.
-                                        *                      If $format is 'object', return an array formatted like:
-                                        *                      array( 'text' => 'CONTENT', 'link' => 'LINK' )
-                                        */
-                                       $content = apply_filters_ref_array( 'bp_notifications_get_notifications_for_user', $ref_array );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         /**
+                                * Filters the notification content for notifications created by plugins.
+                                * If your plugin extends the {@link BP_Component} class, you should use the
+                                * 'notification_callback' parameter in your extended
+                                * {@link BP_Component::setup_globals()} method instead.
+                                *
+                                * @since 1.9.0
+                                * @since 2.6.0 Added $component_action_name, $component_name, $id as parameters.
+                                *
+                                * @param string $content               Component action. Deprecated. Do not do checks against this! Use
+                                *                                      the 6th parameter instead - $component_action_name.
+                                * @param int    $item_id               Notification item ID.
+                                * @param int    $secondary_item_id     Notification secondary item ID.
+                                * @param int    $action_item_count     Number of notifications with the same action.
+                                * @param string $format                Format of return. Either 'string' or 'object'.
+                                * @param string $component_action_name Canonical notification action.
+                                * @param string $component_name        Notification component ID.
+                                * @param int    $id                    Notification ID.
+                                *
+                                * @return string|array If $format is 'string', return a string of the notification content.
+                                *                      If $format is 'object', return an array formatted like:
+                                *                      array( 'text' => 'CONTENT', 'link' => 'LINK' )
+                                */
+                               $content = apply_filters_ref_array( 'bp_notifications_get_notifications_for_user', $ref_array );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        // Create the object to be returned.
-                                       $notification_object = $component_action_items[0];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         // Create the object to be returned.
+                               $notification_object = $notification_item;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        // Minimal backpat with non-compatible notification
-                                       // callback functions.
-                                       if ( is_string( $content ) ) {
-                                               $notification_object->content = $content;
-                                               $notification_object->href    = bp_loggedin_user_domain();
-                                       } else {
-                                               $notification_object->content = $content['text'];
-                                               $notification_object->href    = $content['link'];
-                                       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         // Minimal backpat with non-compatible notification
+                               // callback functions.
+                               if ( is_string( $content ) ) {
+                                       $notification_object->content = $content;
+                                       $notification_object->href    = bp_loggedin_user_domain();
+                               } else {
+                                       $notification_object->content = $content['text'];
+                                       $notification_object->href    = $content['link'];
+                               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        $renderable[] = $notification_object;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         $renderable[] = $notification_object;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                // Return an array of content strings.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                } else {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 } else {
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        /** This filters is documented in bp-notifications/bp-notifications-functions.php */
-                                       $renderable[] = apply_filters_ref_array( 'bp_notifications_get_notifications_for_user', $ref_array );
-                               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         /** This filters is documented in bp-notifications/bp-notifications-functions.php */
+                               $renderable[] = apply_filters_ref_array( 'bp_notifications_get_notifications_for_user', $ref_array );
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span></span></pre></div>
<a id="trunksrcbpnotificationsclassesclassbpnotificationsnotificationphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/bp-notifications/classes/class-bp-notifications-notification.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/bp-notifications/classes/class-bp-notifications-notification.php        2018-03-05 11:10:35 UTC (rev 11887)
+++ trunk/src/bp-notifications/classes/class-bp-notifications-notification.php  2018-03-05 12:57:24 UTC (rev 11888)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1131,4 +1131,50 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                return self::update( $update_args, $where_args );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * Get a user's unread notifications, grouped by component and action.
+        *
+        * Multiple notifications of the same type (those that share the same component_name
+        * and component_action) are collapsed for formatting as "You have 5 pending
+        * friendship requests", etc. See bp_notifications_get_notifications_for_user().
+        * For a full-fidelity list of user notifications, use
+        * bp_notifications_get_all_notifications_for_user().
+        *
+        * @since 3.0.0
+        *
+        * @param int $user_id ID of the user whose notifications are being fetched.
+        * @return array Notifications items for formatting into a list.
+        */
+       public static function get_grouped_notifications_for_user( $user_id ) {
+               global $wpdb;
+
+               // Load BuddyPress.
+               $bp = buddypress();
+
+               // SELECT.
+               $select_sql = "SELECT id, user_id, item_id, secondary_item_id, component_name, component_action, date_notified, is_new, COUNT(id) as total_count ";
+
+               // FROM.
+               $from_sql = "FROM {$bp->notifications->table_name} n ";
+
+               // WHERE.
+               $where_sql = self::get_where_sql( array(
+                       'user_id'        => $user_id,
+                       'is_new'         => 1,
+                       'component_name' => bp_notifications_get_registered_components(),
+               ), $select_sql, $from_sql );
+
+               // GROUP
+               $group_sql = "GROUP BY user_id, component_name, component_action";
+
+               // SORT
+               $order_sql = "ORDER BY date_notified desc";
+
+               // Concatenate query parts.
+               $sql = "{$select_sql} {$from_sql} {$where_sql} {$group_sql} {$order_sql}";
+
+               // Return the queried results.
+               return $wpdb->get_results( $sql );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunittestcasesnotificationsfunctionsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/testcases/notifications/functions.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/testcases/notifications/functions.php 2018-03-05 11:10:35 UTC (rev 11887)
+++ trunk/tests/phpunit/testcases/notifications/functions.php   2018-03-05 12:57:24 UTC (rev 11888)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -403,4 +403,33 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public function dummy_notification_callback( $action, $item_id, $secondary_item_id, $total_items, $format = 'string', $id = 0 ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $this->n_args = compact( 'action', 'item_id', 'secondary_item_id', 'total_items', 'id', 'format' );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * @group cache
+        * @ticket BP7130
+        */
+       public function test_get_grouped_notifications_for_user_cache_invalidation() {
+               $u = self::factory()->user->create();
+
+               $n1 = self::factory()->notification->create( array(
+                       'component_name'    => 'activity',
+                       'component_action'  => 'new_at_mention',
+                       'item_id'           => 99,
+                       'user_id'           => $u,
+               ) );
+
+               // Prime cache.
+               $found = bp_notifications_get_grouped_notifications_for_user( $u );
+               $this->assertEquals( 1, $found[0]->total_count );
+
+               $n2 = self::factory()->notification->create( array(
+                       'component_name'    => 'activity',
+                       'component_action'  => 'new_at_mention',
+                       'item_id'           => 100,
+                       'user_id'           => $u,
+               ) );
+
+               $found = bp_notifications_get_grouped_notifications_for_user( $u );
+               $this->assertEquals( 2, $found[0]->total_count );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre>
</div>
</div>

</body>
</html>