Exemplo n.º 1
0
/**
 * The leaderboard template loop.
 *
 * Doesn't use WP_Query, but the template loop and its data are structured in a vaguely similar
 * way to the dpa_has_achievements() and dpa_has_progress() loops (which do use WP_Query).
 *
 * @param array $args Optional. Associative array of optional arguments. See function for details.
 * @return bool Returns true if the query had any results to loop over
 * @since Achievements (3.4)
 */
function dpa_has_leaderboard($args = array())
{
    // If multisite and running network-wide, switch_to_blog to the data store site
    if (is_multisite() && dpa_is_running_networkwide()) {
        switch_to_blog(DPA_DATA_STORE);
    }
    $defaults = array('paged' => dpa_get_leaderboard_paged(), 'posts_per_page' => dpa_get_leaderboard_items_per_page(), 'user_ids' => array());
    $args = dpa_parse_args($args, $defaults, 'has_leaderboard');
    // Run the query
    achievements()->leaderboard_query = dpa_get_leaderboard($args);
    // Only add pagination if query returned results
    if ((count(achievements()->leaderboard_query['results']) || achievements()->leaderboard_query['total']) && $args['posts_per_page']) {
        // If a top-level /leaderboard/ rewrite is ever added, we can make this use pretty pagination. Also see dpa_get_leaderboard_paged().
        $base = add_query_arg('leaderboard-page', '%#%');
        // Pagination settings with filter
        $leaderboard_pagination = apply_filters('dpa_leaderboard_pagination', array('base' => $base, 'current' => $args['paged'], 'format' => '', 'mid_size' => 1, 'next_text' => is_rtl() ? '←' : '→', 'prev_text' => is_rtl() ? '→' : '←', 'total' => (int) $args['posts_per_page'] === achievements()->leaderboard_query['total'] ? 1 : ceil(achievements()->leaderboard_query['total'] / (int) $args['posts_per_page'])));
        achievements()->leaderboard_query['paged'] = (int) $args['paged'];
        achievements()->leaderboard_query['pagination_links'] = paginate_links($leaderboard_pagination);
        achievements()->leaderboard_query['posts_per_page'] = (int) $args['posts_per_page'];
    }
    // If multisite and running network-wide, undo the switch_to_blog
    if (is_multisite() && dpa_is_running_networkwide()) {
        restore_current_blog();
    }
    return apply_filters('dpa_has_leaderboard', !empty(achievements()->leaderboard_query['results']));
}
Exemplo n.º 2
0
/**
 * Get the current state of the leaderboard, sorted by users' karma points.
 *
 * If you try to use this function, you will need to implement your own switch_to_blog() and wp_reset_postdata() handling if running in a multisite
 * and in a dpa_is_running_networkwide() configuration, otherwise the data won't be fetched from the appropriate site.
 *
 * This function accept a 'user_ids' parameter in the $argument, which accepts an array of user IDs.
 * It is only useful if you want to create a leaderboard that only contains the specified users; for example,
 * you and your friends could have your own mini-league, or in BuddyPress, each Group could have its own leaderboard.
 *
 * It is totally useless if you're trying to find the position for one or more specific users in the *overall* leaderboard.
 *
 * @param array $args Optional. Associative array of optional arguments. See function for details.
 * @return array|bool If no results, false. Otherwise, an associative array: array('results' => array([0] => array('rank' => int, 'user_id' => int, 'karma' => int, 'display_name' => string), ...), 'total' => int).
 * @since Achievements (3.4)
 */
function dpa_get_leaderboard(array $args = array())
{
    global $wpdb;
    $defaults = array('paged' => dpa_get_leaderboard_paged(), 'populate_extras' => true, 'posts_per_page' => dpa_get_leaderboard_items_per_page(), 'user_ids' => array());
    $args = dpa_parse_args($args, $defaults, 'get_leaderboard');
    $points_key = "{$wpdb->prefix}_dpa_points";
    $num_users = empty($args['user_ids']) ? 0 : count((array) $args['user_ids']);
    // No, we're not allowing infinite results. This is always a bad idea.
    if ((int) $args['posts_per_page'] < 1) {
        $args['posts_per_page'] = dpa_get_leaderboard_items_per_page();
    }
    // We use this later to help get/set the object cache
    $last_changed = wp_cache_get('last_changed', 'achievements_leaderboard');
    if ($last_changed === false) {
        $last_changed = microtime();
        wp_cache_add('last_changed', $last_changed, 'achievements_leaderboard');
    }
    /**
     * 1) Get all the distinct values of the _dpa_points keys from the usermeta table.
     *
     * We do the SELECT DISTINCT and sorting in PHP because meta_value is not indexed; this would cause use MySQL to use a temp table.
     */
    $points_query = $wpdb->prepare("SELECT meta_value\n\t\tFROM {$wpdb->usermeta}\n\t\tWHERE meta_key = %s", $points_key);
    if ($num_users > 0) {
        $points_query .= $wpdb->prepare(' AND user_id IN (' . implode(',', wp_parse_id_list((array) $args['user_ids'])) . ') LIMIT %d', $num_users);
    }
    // Only query if not in cache
    $points_cache_key = 'get_leaderboard_points' . md5(serialize($points_query)) . ":{$last_changed}";
    $points = wp_cache_get($points_cache_key, 'achievements_leaderboard_ids');
    if ($points === false) {
        $points = $wpdb->get_col($points_query);
        wp_cache_add($points_cache_key, $points, 'achievements_leaderboard_ids');
    }
    if (empty($points)) {
        // If points is empty, no-one has any karma, so bail out.
        return array('results' => array(), 'total' => 0);
    }
    /**
     * Can't use wp_parse_id_list() here because that casts the values to unsigned ints.
     * The leaderboard might contain users with negative karma point totals.
     */
    $points = array_unique(array_map('intval', $points));
    rsort($points, SORT_NUMERIC);
    // Sort descending for FIND_IN_SET
    $points = implode(',', $points);
    // Format for FIND_IN_SET
    /**
     * 2a) Start building the SQL to get each user's rank, user ID, and points total.
     */
    $query = $wpdb->prepare("SELECT SQL_CALC_FOUND_ROWS FIND_IN_SET( karma.meta_value, %s ) as rank, ID as user_id, karma.meta_value as karma\n\t\tFROM {$wpdb->users} AS person\n\t\tINNER JOIN {$wpdb->usermeta} as karma ON person.ID = karma.user_id AND karma.meta_key = %s", $points, $points_key);
    /**
     * 2b) Sort users correctly even if some of them don't have any karma points.
     *
     * `ORDER BY... rank` causes a filesort because usermeta has no index on meta_value :(
     */
    $query .= ' ORDER BY CASE WHEN rank IS NULL THEN 1 ELSE 0 END, rank';
    /**
     * 2c) Handle pagination
     */
    $offset = ((int) $args['paged'] - 1) * (int) $args['posts_per_page'];
    $query .= $wpdb->prepare(' LIMIT %d, %d', $offset, $args['posts_per_page']);
    /**
     * 3) Run the query and cache results
     */
    $cache_key = 'get_leaderboard:' . md5(serialize($query)) . ":{$last_changed}";
    $results = wp_cache_get($cache_key, 'achievements_leaderboard');
    // Only query if not in cache
    if ($results === false) {
        $results = $wpdb->get_results($query);
        $results_found = $wpdb->get_var('SELECT FOUND_ROWS()');
        // All the returned values should be ints, not strings, so cast them here.
        foreach ($results as $result) {
            foreach ($result as &$value) {
                $value = (int) $value;
            }
        }
        $results = array('results' => $results, 'total' => (int) $results_found);
        wp_cache_add($cache_key, $results, 'achievements_leaderboard');
    }
    /**
     * 4) Maybe get users' display names
     */
    if ($args['populate_extras']) {
        $users = get_users(array('fields' => array('ID', 'display_name'), 'include' => wp_list_pluck($results['results'], 'user_id')));
        // For now, handle any cached user IDs for spammers or deleted users by setting a blank display name.
        foreach ($results['results'] as &$leaderboard_user) {
            $leaderboard_user->display_name = '';
        }
        foreach ($users as $user) {
            foreach ($results['results'] as &$leaderboard_user) {
                if ((int) $user->ID === $leaderboard_user->user_id) {
                    $leaderboard_user->display_name = $user->display_name;
                    break;
                }
            }
        }
    }
    // Why an ArrayObject? See http://stackoverflow.com/questions/10454779/php-indirect-modification-of-overloaded-property
    return apply_filters('dpa_get_leaderboard', new ArrayObject($results), $defaults, $args, $points_cache_key, $cache_key);
}