<!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>[1835] sites/trunk/translate.wordpress.org/includes/gp-plugins/wporg-routes/routes/locale.php: Translate: Introduce the backend for multiple project sorting options, this still lacks a UI to switch between these sort priorities.</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://meta.trac.wordpress.org/changeset/1835">1835</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://meta.trac.wordpress.org/changeset/1835","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>dd32</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2015-08-19 04:32:23 +0000 (Wed, 19 Aug 2015)</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'>Translate: Introduce the backend for multiple project sorting options, this still lacks a UI to switch between these sort priorities.
The default sort priority of untranslated favourites, followed by untranslated items, followed by fully translated items applies now.
The Waiting tab will be visible for Translation Editors shortly.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunktranslatewordpressorgincludesgppluginswporgroutesrouteslocalephp">sites/trunk/translate.wordpress.org/includes/gp-plugins/wporg-routes/routes/locale.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunktranslatewordpressorgincludesgppluginswporgroutesrouteslocalephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/translate.wordpress.org/includes/gp-plugins/wporg-routes/routes/locale.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/translate.wordpress.org/includes/gp-plugins/wporg-routes/routes/locale.php    2015-08-19 04:20:40 UTC (rev 1834)
+++ sites/trunk/translate.wordpress.org/includes/gp-plugins/wporg-routes/routes/locale.php      2015-08-19 04:32:23 UTC (rev 1835)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -13,30 +13,56 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string $set_slug         Slug of the translation set.
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string $project_path     Path of a project.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        public function get_locale_projects( $locale_slug, $set_slug = 'default', $project_path = 'wp' ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function get_locale_projects( $locale_slug, $set_slug = 'default', $project_path = 'waiting' ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 global $gpdb;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $per_page = 20;
</span><span class="cx" style="display: block; padding: 0 10px">                $page = (int) gp_get( 'page', 1 );
</span><span class="cx" style="display: block; padding: 0 10px">                $search = gp_get( 's', '' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $filter = gp_get( 'filter', 'default' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $locale = GP_Locales::by_slug( $locale_slug );
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! $locale ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        return $this->die_with_404();
</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">+                // Grab the top level projects to show in the menu first, so as to be able to handle the default Waiting / WP tab selection
+               $top_level_projects = $this->get_active_top_level_projects();
+               usort( $top_level_projects, array( $this, '_sort_reverse_name_callback' ) );
+
+               // Filter out the Waiting Tab if the current user cannot validate strings
+               $user = GP::$user->current();
+               if (
+                       ! $user->id || // Not logged in
+                       ! isset( GP::$plugins->wporg_rosetta_roles ) || // Rosetta Roles plugin is not enabled
+                       ! (
+                               GP::$plugins->wporg_rosetta_roles->is_global_administrator( $user->id ) || // Not a global admin
+                               GP::$plugins->wporg_rosetta_roles->is_approver_for_locale( $user->id, $locale_slug ) // Doesn't have project-level access either
+                       ) ) { // Add check to see if there are any waiting translations for this locale?
+                       foreach ( $top_level_projects as $i => $project ) {
+                               if ( 'waiting' == $project->slug ) {
+                                       unset( $top_level_projects[ $i ] );
+                                       break;
+                               }
+                       }
+
+                       // Reset to default path of wp if the Waiting tab shouldn't be shown for this user.
+                       $project_path = 'wp';
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $project = GP::$project->by_path( $project_path );
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! $project ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        return $this->die_with_404();
</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">+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $paged_sub_projects = $this->get_paged_active_sub_projects(
</span><span class="cx" style="display: block; padding: 0 10px">                        $project,
</span><span class="cx" style="display: block; padding: 0 10px">                        array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'page' => $page,
</span><span class="cx" style="display: block; padding: 0 10px">                                'per_page' => $per_page,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'orderby' => 'name',
</del><span class="cx" style="display: block; padding: 0 10px">                                 'search' => $search,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                'filter' => $filter,
</ins><span class="cx" style="display: block; padding: 0 10px">                                 'set_slug' => $set_slug,
</span><span class="cx" style="display: block; padding: 0 10px">                                'locale' => $locale_slug,
</span><span class="cx" style="display: block; padding: 0 10px">                        )
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -46,8 +72,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        return $this->die_with_404();
</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">-                $sub_projects   = $paged_sub_projects['projects'];
-               $pages          = $paged_sub_projects['pages'];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $sub_projects = $paged_sub_projects['projects'];
+               $pages        = $paged_sub_projects['pages'];
</ins><span class="cx" style="display: block; padding: 0 10px">                 unset( $paged_sub_projects );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $project_status = $project_icons = array();
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -68,9 +94,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $contributors_count = array();
</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">-                $top_level_projects = $this->get_active_top_level_projects();
-               usort( $top_level_projects, array( $this, '_sort_reverse_name_callback' ) );
-
</del><span class="cx" style="display: block; padding: 0 10px">                 $variants = $this->get_locale_variants( $locale_slug, $project_ids );
</span><span class="cx" style="display: block; padding: 0 10px">                // If there were no results for the current variant in the current project branch, it should still show it.
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! in_array( $set_slug, $variants, true ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -140,6 +163,17 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @return string HTML markup of an icon.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        private function get_project_icon( $project, $sub_project, $size = 128 ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               // The Waiting tab will have $sub_project's which are not sub-projects of $project
+               if ( $sub_project->parent_project_id !== $project->id ) {
+                       $project = GP::$project->get( $sub_project->parent_project_id );
+                       // In the case of Plugins, we may need to go up another level yet
+                       if ( $project->parent_project_id ) {
+                               $sub_project = $project;
+                               $project = GP::$project->get( $sub_project->parent_project_id );
+                       }
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 switch( $project->slug ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        case 'wp':
</span><span class="cx" style="display: block; padding: 0 10px">                                return '<div class="wordpress-icon"><span class="dashicons dashicons-wordpress-alt"></span></div>';
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -200,7 +234,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $project_ids = implode( ',', $project_ids );
</span><span class="cx" style="display: block; padding: 0 10px">                $slugs = $gpdb->get_col( $gpdb->prepare( "
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        SELECT DISTINCT(slug), name
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 SELECT DISTINCT slug
</ins><span class="cx" style="display: block; padding: 0 10px">                         FROM {$gpdb->translation_sets}
</span><span class="cx" style="display: block; padding: 0 10px">                        WHERE
</span><span class="cx" style="display: block; padding: 0 10px">                                project_id IN( $project_ids )
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -336,28 +370,120 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $defaults = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'per_page' => 20,
</span><span class="cx" style="display: block; padding: 0 10px">                        'page'     => 1,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'orderby'  => 'id',
-                       'order'    => 'ASC',
</del><span class="cx" style="display: block; padding: 0 10px">                         'search'   => false,
</span><span class="cx" style="display: block; padding: 0 10px">                        'set_slug' => '',
</span><span class="cx" style="display: block; padding: 0 10px">                        'locale'   => '',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'filter'     => 'default',
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                $r = wp_parse_args( $args, $defaults );
</span><span class="cx" style="display: block; padding: 0 10px">                extract( $r, EXTR_SKIP );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $order = ( 'ASC' === $order ) ? 'ASC' : 'DESC';
-               $orderby = ( in_array( $orderby, array( 'id', 'name' ) ) ? $orderby : 'id' );
-
</del><span class="cx" style="display: block; padding: 0 10px">                 $limit_sql = '';
</span><span class="cx" style="display: block; padding: 0 10px">                if ( $per_page ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $limit_sql = $gpdb->prepare( 'LIMIT %d, %d', ( $page - 1 ) * $per_page, $per_page );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               $parent_project_sql = $gpdb->prepare( 'AND tp.parent_project_id = %d', $project->id );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $search_sql = '';
</span><span class="cx" style="display: block; padding: 0 10px">                if ( $search ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $esc_search = '%%' . like_escape( $search ) . '%%';
</span><span class="cx" style="display: block; padding: 0 10px">                        $search_sql = $gpdb->prepare( 'AND ( tp.name LIKE %s OR tp.slug LIKE %s )', $esc_search, $esc_search );
</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">+                // Special Waiting Project Tab
+               // This removes the parent_project_id restriction and replaces it with all-translation-editer-projects
+               if ( 'waiting' == $project->slug && GP::$user->current()->id && isset( GP::$plugins->wporg_rosetta_roles ) ) {
+
+                       if ( 'default' === $filter ) {
+                               $filter = 'strings-waiting';
+                       }
+
+                       $user_id = GP::$user->current()->id;
+
+                       // Global Admin or Locale-specific admin
+                       $can_approve_for_all = GP::$plugins->wporg_rosetta_roles->is_global_administrator( $user_id );
+
+                       // Check to see if they have any special approval permissions
+                       $allowed_projects = array();
+                       if ( ! $can_approve_for_all && GP::$plugins->wporg_rosetta_roles->is_approver_for_locale( $user_id, $locale ) ) {
+                               $allowed_projects = GP::$plugins->wporg_rosetta_roles->get_project_id_access_list( $user_id, $locale, true );
+
+                               // Check to see if they can approve for all projects in this locale.
+                               if ( in_array( 'all', $allowed_projects ) ) {
+                                       $can_approve_for_all = true;
+                                       $allowed_projects = array();
+                               }
+                       }
+
+                       $parent_project_sql = '';
+                       if ( $can_approve_for_all ) {
+                               // The current user can approve for all projects, so just grab all with any waiting strings.
+                               $parent_project_sql = 'AND stats.waiting > 0';
+
+                       } elseif ( $allowed_projects ) {
+                               // The current user can approve for a small set of projects.
+                               // We only need to check against tp.id and not tp_sub.id in this case as we've overriding the parent_project_id check
+                               $ids = implode( ', ', array_map( 'intval', $allowed_projects ) );
+                               $parent_project_sql = "AND tp_sub.id IN( $ids ) AND stats.waiting > 0";
+
+                       } else {
+                               // The current user can't approve for any locale projects, or is logged out.
+                               $parent_project_sql = 'AND 0=1';
+
+                       }
+
+               }
+
+               $filter_order_by = $filter_where = '';
+               switch ( $filter ) {
+                       default:
+                       case 'default':
+                               // Float favorites to the start, but only if they have untranslated strings
+                               $user_fav_projects = array_map( array( $gpdb, 'escape' ), $this->get_user_favorites( $project->slug ) );
+
+                               // Float Favorites to the start, float fully translated to the bottom, order the rest by name
+                               if ( $user_fav_projects ) {
+                                       $filter_order_by = 'FIELD( tp.path, "' . implode( '", "', $user_fav_projects ) . '" ) > 0 AND stats.untranslated > 0 DESC, stats.untranslated > 0 DESC, tp.name ASC';
+                               } else {
+                                       $filter_order_by = 'stats.untranslated > 0 DESC, tp.name ASC';
+                               }
+                               break;
+
+                       case 'favorites':
+                               // Only list favorites
+                               $user_fav_projects = array_map( array( $gpdb, 'escape' ), $this->get_user_favorites( $project->slug ) );
+
+                               if ( $user_fav_projects ) {
+                                       $filter_where = 'AND tp.path IN( "' . implode( '", "', $user_fav_projects ) . '" )';
+                               } else {
+                                       $filter_where = 'AND 0=1';
+                               }
+                               $filter_order_by = 'stats.untranslated > 0 DESC, tp.name ASC';
+
+                               break;
+
+                       case 'strings-remaining':
+                               $filter_where = 'AND stats.untranslated > 0';
+                               $filter_order_by = 'stats.untranslated DESC, tp.name ASC';
+                               break;
+
+                       case 'strings-waiting':
+                               $filter_where = 'AND stats.waiting > 0';
+                               $filter_order_by = 'stats.waiting DESC, tp.name ASC';
+                               break;
+
+                       case 'strings-fuzzy-and-warnings':
+                               $filter_where = 'AND ( stats.fuzzy > 0 OR stats.warnings > 0 )';
+                               $filter_order_by = '(stats.fuzzy+stats.warnings) DESC, tp.name ASC';
+                               break;
+
+                       case 'percent-completed':
+                               $filter_where = 'AND stats.untranslated > 0';
+                               $filter_order_by = ' ( stats.current / stats.all ) DESC, tp.name ASC';
+                               break;
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 /*
</span><span class="cx" style="display: block; padding: 0 10px">                 * Find all child projects with translation sets that match the current locale/slug.
</span><span class="cx" style="display: block; padding: 0 10px">                 *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -373,15 +499,17 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                LEFT JOIN {$gpdb->projects} tp_sub ON tp.id = tp_sub.parent_project_id AND tp_sub.active = 1
</span><span class="cx" style="display: block; padding: 0 10px">                                LEFT JOIN {$gpdb->translation_sets} sets ON sets.project_id = tp.id AND sets.locale = %s AND sets.slug = %s
</span><span class="cx" style="display: block; padding: 0 10px">                                LEFT JOIN {$gpdb->translation_sets} sets_sub ON sets_sub.project_id = tp_sub.id AND sets_sub.locale = %s AND sets_sub.slug = %s
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                LEFT JOIN {$gpdb->project_translation_status} stats ON stats.project_id = tp.id AND stats.translation_set_id = sets.id
</ins><span class="cx" style="display: block; padding: 0 10px">                         WHERE
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                tp.parent_project_id = %d
-                               AND tp.active = 1
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         tp.active = 1
</ins><span class="cx" style="display: block; padding: 0 10px">                                 AND ( sets.id IS NOT NULL OR sets_sub.id IS NOT NULL )
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                $parent_project_sql
</ins><span class="cx" style="display: block; padding: 0 10px">                                 $search_sql
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                $filter_where
</ins><span class="cx" style="display: block; padding: 0 10px">                         GROUP BY tp.id
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        ORDER BY tp.$orderby $order
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 ORDER BY $filter_order_by
</ins><span class="cx" style="display: block; padding: 0 10px">                         $limit_sql
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                ", $locale, $set_slug, $locale, $set_slug, $project->id  );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         ", $locale, $set_slug, $locale, $set_slug );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $results = (int) $project->found_rows();
</span><span class="cx" style="display: block; padding: 0 10px">                $pages = (int) ceil( $results / $per_page );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -398,6 +526,54 @@
</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">+         * Retrieves a list of projects which the current user has favorited.
+        *
+        * @return array List of favorited items, eg [ 'wp-themes/twentyten', 'wp-themes/twentyeleven' ]
+        */
+       function get_user_favorites( $project_slug = false ) {
+               global $gpdb;
+               $user = GP::$user->current();
+
+               if ( ! $user->id ) {
+                       return array();
+               }
+
+               switch ( $project_slug ) {
+                       default:
+                               // Fall through to include both Themes and Plugins
+                       case 'wp-themes':
+                               // Theme favorites are stored as theme slugs, these map 1:1 to GlotPress projects
+                               $theme_favorites = array_map( function( $slug ) {
+                                       return "wp-themes/$slug";
+                               }, (array) $user->get_meta( 'theme_favorites' ) );
+
+                               if ( 'wp-themes' === $project_slug ) {
+                                       return $theme_favorites;
+                               }
+
+                       case 'wp-plugins':
+                               // Plugin favorites are stored as topic ID's
+                               $plugin_fav_ids = array_keys( (array)$user->get_meta( PLUGINS_TABLE_PREFIX . 'plugin_favorite' ) );
+                               $plugin_fav_slugs = array();
+                               if ( $plugin_fav_ids ) {
+                                       $plugin_fav_ids = implode( ',', array_map( 'intval', $plugin_fav_ids ) );
+                                       $plugin_fav_slugs = $gpdb->get_col( "SELECT topic_slug FROM " . PLUGINS_TABLE_PREFIX . "topics WHERE topic_id IN( $plugin_fav_ids )" );
+                               }
+
+                               $plugin_favorites = array_map( function( $slug ) {
+                                       return "wp-plugins/$slug";
+                               }, $plugin_fav_slugs );
+
+                               if ( 'wp-plugins' === $project_slug ) {
+                                       return $plugin_favorites;
+                               }
+               }
+
+               // Return all favorites, for uses in things like the Waiting tab
+               return array_merge( $theme_favorites, $plugin_favorites );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Retrieves active top level projects.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return array List of top level projects.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -416,6 +592,10 @@
</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">        private function _sort_reverse_name_callback( $a, $b ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // The Waiting project should always be first.
+               if ( $a->slug == 'waiting' ) {
+                       return -1;
+               }
</ins><span class="cx" style="display: block; padding: 0 10px">                 return - strcasecmp( $a->name, $b->name );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre>
</div>
</div>

</body>
</html>