<!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][8027] trunk: Introduce bp_core_user_displaynames(), for quick, bulk displayname lookup</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">
<dt>Revision</dt> <dd><a href="http://buddypress.trac.wordpress.org/changeset/8027">8027</a></dd>
<dt>Author</dt> <dd>boonebgorges</dd>
<dt>Date</dt> <dd>2014-03-03 21:35:48 +0000 (Mon, 03 Mar 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Introduce bp_core_user_displaynames(), for quick, bulk displayname lookup

The technique BP uses for determining a user's displayname takes into
consideration whether the xprofile component is active, whether the user has a
WP display_name, and a number of other factors. Because the values are used so
often throughout BP, they are then cached in WP's object cache. However, there
are a number of places throughout BuddyPress where displaynames are fetched
in a way that is inconsistent with the "canonical" workflow (ie, by querying
the xprofile tables correctly). This causes inconsistent results, and also can
result in performance degradation when the persistent cache is skipped.
Moreover, the singular nature of bp_core_get_user_displayname() meant that
doing it the "right" way meant introducing large numbers of queries to a given
page load.

This changeset introduces bp_core_get_user_displaynames(), which contains all
the critical logic of bp_core_get_user_displayname() - including fallbacks for
various setups and support for the object cache - but allows for fetching large
numbers of displaynames without multiple queries. bp_core_get_user_displayname()
is now a wrapper for the more general function.

The function has been swapped in throughout BuddyPress as appropriate.

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

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkbpactivitybpactivityclassesphp">trunk/bp-activity/bp-activity-classes.php</a></li>
<li><a href="#trunkbpmembersbpmembersfunctionsphp">trunk/bp-members/bp-members-functions.php</a></li>
<li><a href="#trunkbpxprofilebpxprofilefiltersphp">trunk/bp-xprofile/bp-xprofile-filters.php</a></li>
<li><a href="#trunkteststestcasesmembersfunctionsphp">trunk/tests/testcases/members/functions.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkbpactivitybpactivityclassesphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-activity/bp-activity-classes.php (8026 => 8027)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-activity/bp-activity-classes.php        2014-03-03 21:35:15 UTC (rev 8026)
+++ trunk/bp-activity/bp-activity-classes.php   2014-03-03 21:35:48 UTC (rev 8027)
</span><span class="lines">@@ -544,23 +544,13 @@
</span><span class="cx">                  $activity_user_ids = wp_list_pluck( $activities, 'user_id' );
</span><span class="cx"> 
</span><span class="cx">                  if ( ! empty( $activity_user_ids ) ) {
</span><del>-                               $names = BP_XProfile_ProfileData::get_value_byid( 1, $activity_user_ids );
-                               if ( ! empty( $names ) ) {
-
-                                       $tmp_names = array();
-
-                                       foreach ( (array) $names as $name ) {
-                                               $tmp_names[ $name->user_id ] = $name->value;
-                                       }
-
</del><ins>+                                $fullnames = bp_core_get_user_displaynames( $activity_user_ids );
+                               if ( ! empty( $fullnames ) ) {
</ins><span class="cx">                                   foreach ( (array) $activities as $i => $activity ) {
</span><del>-                                               if ( ! empty( $tmp_names[ $activity->user_id ] ) ) {
-                                                       $activities[ $i ]->user_fullname = $tmp_names[ $activity->user_id ];
</del><ins>+                                                if ( ! empty( $fullnames[ $activity->user_id ] ) ) {
+                                                       $activities[ $i ]->user_fullname = $fullnames[ $activity->user_id ];
</ins><span class="cx">                                           }
</span><span class="cx">                                  }
</span><del>-
-                                       unset( $names );
-                                       unset( $tmp_names );
</del><span class="cx">                           }
</span><span class="cx">                  }
</span><span class="cx">          }
</span></span></pre></div>
<a id="trunkbpmembersbpmembersfunctionsphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-members/bp-members-functions.php (8026 => 8027)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-members/bp-members-functions.php        2014-03-03 21:35:15 UTC (rev 8026)
+++ trunk/bp-members/bp-members-functions.php   2014-03-03 21:35:48 UTC (rev 8027)
</span><span class="lines">@@ -389,60 +389,121 @@
</span><span class="cx">  return apply_filters( 'bp_core_get_userlink', '<a href="' . $url . '" title="' . $display_name . '">' . $display_name . '</a>', $user_id );
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+/**
+ * Fetch the display name for a group of users.
+ *
+ * Uses the 'Name' field in xprofile if available. Falls back on WP
+ * display_name, and then user_nicename.
+ *
+ * @since BuddyPress (2.0.0)
+ *
+ * @param array $user_ids
+ */
+function bp_core_get_user_displaynames( $user_ids ) {
</ins><span class="cx"> 
</span><ins>+       // Sanitize
+       $user_ids = wp_parse_id_list( $user_ids );
+
+       // Remove dupes and empties
+       $user_ids = array_unique( array_filter( $user_ids ) );
+
+       if ( empty( $user_ids ) ) {
+               return array();
+       }
+
+       $uncached_ids = array();
+       foreach ( $user_ids as $user_id ) {
+               if ( false === wp_cache_get( 'bp_user_fullname_' . $user_id, 'bp' ) ) {
+                       $uncached_ids[] = $user_id;
+               }
+       }
+
+       // Prime caches
+       if ( ! empty( $uncached_ids ) ) {
+               if ( bp_is_active( 'xprofile' ) ) {
+                       $fullname_data = BP_XProfile_ProfileData::get_value_byid( 1, $uncached_ids );
+
+                       // Key by user_id
+                       $fullnames = array();
+                       foreach ( $fullname_data as $fd ) {
+                               if ( ! empty( $fd->value ) ) {
+                                       $fullnames[ intval( $fd->user_id ) ] = $fd->value;
+                               }
+                       }
+
+                       // If xprofiledata is not found for any users,  we'll look
+                       // them up separately
+                       $no_xprofile_ids = array_diff( $uncached_ids, array_keys( $fullnames ) );
+               } else {
+                       $fullnames = array();
+                       $no_xprofile_ids = $user_ids;
+               }
+
+               if ( ! empty( $no_xprofile_ids ) ) {
+                       // Use WP_User_Query because we don't need BP information
+                       $query = new WP_User_Query( array(
+                               'include'     => $no_xprofile_ids,
+                               'fields'      => array( 'ID', 'user_nicename', 'display_name', ),
+                               'count_total' => false,
+                               'blog_id'     => 0,
+                       ) );
+
+                       foreach ( $query->results as $qr ) {
+                               $fullnames[ $qr->ID ] = ! empty( $qr->display_name ) ? $qr->display_name : $qr->user_nicename;
+
+                               // If xprofile is active, set this value as the
+                               // xprofile display name as well
+                               if ( bp_is_active( 'xprofile' ) ) {
+                                       xprofile_set_field_data( 1, $qr->ID, $fullnames[ $qr->ID ] );
+                               }
+                       }
+               }
+
+               foreach ( $fullnames as $fuser_id => $fname ) {
+                       wp_cache_set( 'bp_user_fullname_' . $fuser_id, $fname, 'bp' );
+               }
+       }
+
+       $retval = array();
+       foreach ( $user_ids as $user_id ) {
+               $retval[ $user_id ] = wp_cache_get( 'bp_user_fullname_' . $user_id, 'bp' );
+       }
+
+       return $retval;
+}
+
</ins><span class="cx"> /**
</span><del>- * Fetch the display name for a user. This will use the "Name" field in xprofile if it is installed.
- * Otherwise, it will fall back to the normal WP display_name, or user_nicename, depending on what has been set.
</del><ins>+ * Fetch the display name for a user.
</ins><span class="cx">  *
</span><del>- * @package BuddyPress Core
- * @global BuddyPress $bp The one true BuddyPress instance
- * @uses wp_cache_get() Will try and fetch the value from the cache, rather than querying the DB again.
- * @uses get_userdata() Fetches the WP userdata for a specific user.
- * @uses xprofile_set_field_data() Will update the field data for a user based on field name and user id.
- * @uses wp_cache_set() Adds a value to the cache.
- * @return string|bool The display name for the user in question, or false if user not found.
</del><ins>+ * @param int|string $user_id_or_username User ID or username.
+ * @return string|bool The display name for the user in question, or false if
+ *         user not found.
</ins><span class="cx">  */
</span><span class="cx"> function bp_core_get_user_displayname( $user_id_or_username ) {
</span><span class="cx">  global $bp;
</span><span class="cx"> 
</span><span class="cx">  $fullname = '';
</span><span class="cx"> 
</span><del>-       if ( !$user_id_or_username )
</del><ins>+        if ( empty( $user_id_or_username ) ) {
</ins><span class="cx">           return false;
</span><ins>+       }
</ins><span class="cx"> 
</span><del>-       if ( !is_numeric( $user_id_or_username ) )
</del><ins>+        if ( ! is_numeric( $user_id_or_username ) ) {
</ins><span class="cx">           $user_id = bp_core_get_userid( $user_id_or_username );
</span><del>-       else
</del><ins>+        } else {
</ins><span class="cx">           $user_id = $user_id_or_username;
</span><ins>+       }
</ins><span class="cx"> 
</span><del>-       if ( !$user_id )
</del><ins>+        if ( empty( $user_id ) ) {
</ins><span class="cx">           return false;
</span><ins>+       }
</ins><span class="cx"> 
</span><del>-       if ( !$fullname = wp_cache_get( 'bp_user_fullname_' . $user_id, 'bp' ) ) {
-               if ( bp_is_active( 'xprofile' ) ) {
-                       $fullname = xprofile_get_field_data( 1, $user_id );
</del><ins>+        $display_names = bp_core_get_user_displaynames( array( $user_id ) );
</ins><span class="cx"> 
</span><del>-                       if ( empty($fullname) ) {
-                               $ud = bp_core_get_core_userdata( $user_id );
-
-                               if ( !empty( $ud->display_name ) )
-                                       $fullname = $ud->display_name;
-                               elseif ( !empty( $ud->user_nicename ) )
-                                       $fullname = $ud->user_nicename;
-
-                               xprofile_set_field_data( 1, $user_id, $fullname );
-                       }
-               } else {
-                       $ud = bp_core_get_core_userdata($user_id);
-
-                       if ( !empty( $ud->display_name ) )
-                               $fullname = $ud->display_name;
-                       elseif ( !empty( $ud->user_nicename ) )
-                               $fullname = $ud->user_nicename;
-               }
-
-               if ( !empty( $fullname ) )
-                       wp_cache_set( 'bp_user_fullname_' . $user_id, $fullname, 'bp' );
</del><ins>+        if ( ! isset( $display_names[ $user_id ] ) ) {
+               $fullname = false;
+       } else {
+               $fullname = $display_names[ $user_id ];
</ins><span class="cx">   }
</span><span class="cx"> 
</span><span class="cx">  return apply_filters( 'bp_core_get_user_displayname', $fullname, $user_id );
</span></span></pre></div>
<a id="trunkbpxprofilebpxprofilefiltersphp"></a>
<div class="modfile"><h4>Modified: trunk/bp-xprofile/bp-xprofile-filters.php (8026 => 8027)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/bp-xprofile/bp-xprofile-filters.php        2014-03-03 21:35:15 UTC (rev 8026)
+++ trunk/bp-xprofile/bp-xprofile-filters.php   2014-03-03 21:35:48 UTC (rev 8027)
</span><span class="lines">@@ -215,9 +215,9 @@
</span><span class="cx">  }
</span><span class="cx"> 
</span><span class="cx">  // Pull up the xprofile fullname of each commenter
</span><del>-       if ( $fullnames = BP_XProfile_ProfileData::get_value_byid( 1, $user_ids ) ) {
-               foreach( (array) $fullnames as $user ) {
-                       $users[ $user->user_id ] = trim( stripslashes( $user->value ) );
</del><ins>+        if ( $fullnames = bp_core_get_user_displaynames( $user_ids ) ) {
+               foreach( (array) $fullnames as $user_id => $user_fullname ) {
+                       $users[ $user_id ] = trim( stripslashes( $user_fullname ) );
</ins><span class="cx">           }
</span><span class="cx">  }
</span><span class="cx"> 
</span><span class="lines">@@ -251,12 +251,12 @@
</span><span class="cx">          return;
</span><span class="cx">  }
</span><span class="cx"> 
</span><del>-       $user_id_names = BP_XProfile_ProfileData::get_value_byid( bp_xprofile_fullname_field_id(), $user_query->user_ids );
</del><ins>+        $user_id_names = bp_core_get_user_displaynames( $user_query->user_ids );
</ins><span class="cx"> 
</span><span class="cx">  // Loop through names and override each user's fullname
</span><del>-       foreach ( $user_id_names as $user ) {
-               if ( isset( $user_query->results[ $user->user_id ] ) ) {
-                       $user_query->results[ $user->user_id ]->fullname = $user->value;
</del><ins>+        foreach ( $user_id_names as $user_id => $user_fullname ) {
+               if ( isset( $user_query->results[ $user_id ] ) ) {
+                       $user_query->results[ $user_id ]->fullname = $user_fullname;
</ins><span class="cx">           }
</span><span class="cx">  }
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkteststestcasesmembersfunctionsphp"></a>
<div class="modfile"><h4>Modified: trunk/tests/testcases/members/functions.php (8026 => 8027)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/tests/testcases/members/functions.php      2014-03-03 21:35:15 UTC (rev 8026)
+++ trunk/tests/testcases/members/functions.php 2014-03-03 21:35:48 UTC (rev 8027)
</span><span class="lines">@@ -209,4 +209,74 @@
</span><span class="cx">                  unset( buddypress()->active_components['xprofile'] );
</span><span class="cx">          }
</span><span class="cx">  }
</span><ins>+
+       /**
+        * @group bp_core_get_user_displaynames
+        */
+       public function test_bp_core_get_user_displayname_arrays_all_bad_entries() {
+               $this->assertSame( array(), bp_core_get_user_displaynames( array( 0, 'foo', ) ) );
+       }
+
+       /**
+        * @group bp_core_get_user_displaynames
+        */
+       public function test_bp_core_get_user_displaynames_all_uncached() {
+               $u1 = $this->create_user();
+               $u2 = $this->create_user();
+
+               xprofile_set_field_data( 1, $u1, 'Foo' );
+               xprofile_set_field_data( 1, $u2, 'Bar' );
+
+               $expected = array(
+                       $u1 => 'Foo',
+                       $u2 => 'Bar',
+               );
+
+               $this->assertSame( $expected, bp_core_get_user_displaynames( array( $u1, $u2, ) ) );
+       }
+
+       /**
+        * @group bp_core_get_user_displaynames
+        */
+       public function test_bp_core_get_user_displaynames_one_not_in_xprofile() {
+               $u1 = $this->create_user();
+               $u2 = $this->create_user( array(
+                       'display_name' => 'Bar',
+               ) );
+
+               xprofile_set_field_data( 1, $u1, 'Foo' );
+
+               // Delete directly because BP won't let you delete a required
+               // field through the API
+               global $wpdb, $bp;
+               $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_data} WHERE user_id = %d AND field_id = 1", $u2 ) );
+               wp_cache_delete( 'bp_user_fullname_' . $u2, 'bp' );
+               wp_cache_delete( 1, 'bp_xprofile_data_' . $u2, 'bp' );
+
+               $expected = array(
+                       $u1 => 'Foo',
+                       $u2 => 'Bar',
+               );
+
+               $this->assertSame( $expected, bp_core_get_user_displaynames( array( $u1, $u2, ) ) );
+       }
+
+       /**
+        * @group bp_core_get_user_displaynames
+        */
+       public function test_bp_core_get_user_displaynames_one_in_cache() {
+               $u1 = $this->create_user();
+               xprofile_set_field_data( 1, $u1, 'Foo' );
+
+               // Fake the cache for $u2
+               $u2 = 123;
+               wp_cache_set( 'bp_user_fullname_' . $u2, 'Bar', 'bp' );
+
+               $expected = array(
+                       $u1 => 'Foo',
+                       $u2 => 'Bar',
+               );
+
+               $this->assertSame( $expected, bp_core_get_user_displaynames( array( $u1, $u2, ) ) );
+       }
</ins><span class="cx"> }
</span></span></pre>
</div>
</div>

</body>
</html>