mirror of
https://github.com/WordPress/five-for-the-future.git
synced 2025-04-18 17:33:43 +03:00
parent
152964f5cf
commit
fda5842e86
|
@ -281,7 +281,9 @@ function get_pledge_contributors_data( $pledge_id ) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the user objects that correspond with pledge contributor posts.
|
||||
* Get the user objects that correspond with contributor posts.
|
||||
*
|
||||
* @see `get_contributor_user_ids()` for a similar function.
|
||||
*
|
||||
* @param WP_Post[] $contributor_posts
|
||||
*
|
||||
|
@ -293,6 +295,43 @@ function get_contributor_user_objects( array $contributor_posts ) {
|
|||
}, $contributor_posts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user IDs for the given `CPT_ID` posts.
|
||||
*
|
||||
* This is similar to `get_contributor_user_objects()`, but returns more specific data, and is more performant
|
||||
* with large data sets (e.g., with `get_snapshot_data()`) because there is 1 query instead of
|
||||
* `count( $contributor_posts )`.
|
||||
*
|
||||
* @param WP_Post[] $contributor_posts
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function get_contributor_user_ids( $contributor_posts ) {
|
||||
global $wpdb;
|
||||
|
||||
$usernames = wp_list_pluck( $contributor_posts, 'post_title' );
|
||||
|
||||
/*
|
||||
* Generate placeholders dynamically, so that each username will be quoted individually rather than as a
|
||||
* single string.
|
||||
*
|
||||
* @see https://developer.wordpress.org/reference/classes/wpdb/prepare/#comment-1557
|
||||
*/
|
||||
$usernames_placeholders = implode( ', ', array_fill( 0, count( $usernames ), '%s' ) );
|
||||
|
||||
$query = "
|
||||
SELECT id
|
||||
FROM $wpdb->users
|
||||
WHERE user_login IN( $usernames_placeholders )
|
||||
";
|
||||
|
||||
$user_ids = $wpdb->get_col(
|
||||
$wpdb->prepare( $query, $usernames )
|
||||
);
|
||||
|
||||
return $user_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only show the My Pledges menu to users who are logged in.
|
||||
*
|
||||
|
|
189
plugins/wporg-5ftf/includes/stats.php
Normal file
189
plugins/wporg-5ftf/includes/stats.php
Normal file
|
@ -0,0 +1,189 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Track and display key metrics for the program, to measure growth and effectiveness.
|
||||
*/
|
||||
|
||||
namespace WordPressDotOrg\FiveForTheFuture\Stats;
|
||||
|
||||
use WordPressDotOrg\FiveForTheFuture;
|
||||
use WordPressDotOrg\FiveForTheFuture\{ Contributor, Pledge, XProfile };
|
||||
use WP_Query;
|
||||
|
||||
use const WordPressDotOrg\FiveForTheFuture\PREFIX;
|
||||
|
||||
defined( 'WPINC' ) || die();
|
||||
|
||||
const CPT_ID = PREFIX . '_stats_snapshot';
|
||||
|
||||
add_action( 'init', __NAMESPACE__ . '\register_post_types' );
|
||||
add_action( 'init', __NAMESPACE__ . '\schedule_cron_jobs' );
|
||||
add_action( PREFIX . '_record_snapshot', __NAMESPACE__ . '\record_snapshot' );
|
||||
|
||||
add_shortcode( PREFIX . '_stats', __NAMESPACE__ . '\render_shortcode' );
|
||||
|
||||
|
||||
/**
|
||||
* Register the snapshots post type. Each post represents a snapshot of the stats at a point in time.
|
||||
*/
|
||||
function register_post_types() {
|
||||
$args = array(
|
||||
'supports' => array( 'custom-fields' ),
|
||||
'public' => false,
|
||||
'show_in_rest' => true,
|
||||
|
||||
// Only allow posts to be created programmatically.
|
||||
'capability_type' => CPT_ID,
|
||||
'capabilities' => array(
|
||||
'create_posts' => 'do_not_allow',
|
||||
),
|
||||
);
|
||||
|
||||
register_post_type( CPT_ID, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the cron job to record a stats snapshot.
|
||||
*/
|
||||
function schedule_cron_jobs() {
|
||||
if ( wp_next_scheduled( PREFIX . '_record_snapshot' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule a repeating "single" event to avoid having to create a custom schedule.
|
||||
wp_schedule_single_event(
|
||||
time() + ( 2 * WEEK_IN_SECONDS ),
|
||||
PREFIX . '_record_snapshot'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a snapshot of the current stats, so we can track trends over time.
|
||||
*/
|
||||
function record_snapshot() {
|
||||
$stats = get_snapshot_data();
|
||||
|
||||
$post_id = wp_insert_post( array(
|
||||
'post_type' => CPT_ID,
|
||||
'post_author' => 0,
|
||||
'post_title' => sprintf( '5ftF Stats Snapshot %s', date( 'Y-m-d' ) ),
|
||||
'post_status' => 'publish',
|
||||
) );
|
||||
|
||||
add_post_meta( $post_id, PREFIX . '_total_pledged_hours', $stats['confirmed_hours'] );
|
||||
add_post_meta( $post_id, PREFIX . '_total_pledged_contributors', $stats['confirmed_contributors'] );
|
||||
add_post_meta( $post_id, PREFIX . '_total_pledged_companies', $stats['confirmed_pledges'] );
|
||||
add_post_meta( $post_id, PREFIX . '_total_pledged_team_contributors', $stats['confirmed_team_contributors'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the stats for the current snapshot.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function get_snapshot_data() {
|
||||
$snapshot_data = array(
|
||||
'confirmed_hours' => 0,
|
||||
'confirmed_team_contributors' => array(),
|
||||
);
|
||||
|
||||
$confirmed_pledges = new WP_Query( array(
|
||||
'post_type' => Pledge\CPT_ID,
|
||||
'post_status' => 'publish',
|
||||
'numberposts' => 1, // We only need `found_posts`, not the posts themselves.
|
||||
) );
|
||||
|
||||
$snapshot_data['confirmed_pledges'] = $confirmed_pledges->found_posts;
|
||||
|
||||
/*
|
||||
* A potential future optimization would be make WP_Query only return the `post_title`. The `fields` parameter
|
||||
* doesn't currently support `post_title`, but it may be possible with filters like `posts_fields`
|
||||
* or `posts_fields_request`. That was premature at the time this code was written, though.
|
||||
*/
|
||||
$confirmed_contributors = get_posts( array(
|
||||
'post_type' => Contributor\CPT_ID,
|
||||
'post_status' => 'publish',
|
||||
'numberposts' => 2000,
|
||||
) );
|
||||
|
||||
/*
|
||||
* Removing duplicates because a user sponsored by multiple companies will have multiple contributor posts,
|
||||
* but their stats should only be counted once.
|
||||
*
|
||||
* A potential future optimization would be to remove duplicate `post_title` entries in the query itself,
|
||||
* but `WP_Query` doesn't support `DISTINCT` directly, and it's premature at this point. It may be possible
|
||||
* with the filters mentioned above.
|
||||
*/
|
||||
$confirmed_user_ids = array_unique( Contributor\get_contributor_user_ids( $confirmed_contributors ) );
|
||||
$snapshot_data['confirmed_contributors'] = count( $confirmed_user_ids );
|
||||
|
||||
$contributors_profile_data = XProfile\get_xprofile_contribution_data( $confirmed_user_ids );
|
||||
|
||||
foreach ( $contributors_profile_data as $profile_data ) {
|
||||
switch ( (int) $profile_data['field_id'] ) {
|
||||
case XProfile\FIELD_IDS['hours_per_week']:
|
||||
$snapshot_data['confirmed_hours'] += absint( $profile_data['value'] );
|
||||
break;
|
||||
|
||||
case XProfile\FIELD_IDS['team_names']:
|
||||
/*
|
||||
* BuddyPress validates the team name(s) the user provides before saving them in the database, so
|
||||
* it should be safe to unserialize, and to assume that they're valid.
|
||||
*
|
||||
* The database stores team _names_ rather than _IDs_, though, so if a team is ever renamed, this
|
||||
* data will be distorted.
|
||||
*/
|
||||
$associated_teams = maybe_unserialize( $profile_data['value'] );
|
||||
|
||||
foreach ( $associated_teams as $team ) {
|
||||
if ( isset( $snapshot_data['confirmed_team_contributors'][ $team ] ) ) {
|
||||
$snapshot_data['confirmed_team_contributors'][ $team ]++;
|
||||
} else {
|
||||
$snapshot_data['confirmed_team_contributors'][ $team ] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $snapshot_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the shortcode to display stats.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function render_shortcode() {
|
||||
$snapshots = get_posts( array(
|
||||
'post_type' => CPT_ID,
|
||||
'posts_per_page' => 500,
|
||||
'order' => 'ASC',
|
||||
) );
|
||||
|
||||
$stat_keys = array(
|
||||
PREFIX . '_total_pledged_hours',
|
||||
PREFIX . '_total_pledged_contributors',
|
||||
PREFIX . '_total_pledged_companies',
|
||||
PREFIX . '_total_pledged_team_contributors',
|
||||
);
|
||||
|
||||
$stat_values = array();
|
||||
|
||||
// todo produce whatever data structure the visualization framework wants, and any a11y text fallback necessary.
|
||||
// don't trust that visualization library will escape things properly, run numbers through absint(), team names through sanitize_text_field(), etc.
|
||||
|
||||
foreach ( $snapshots as $snapshot ) {
|
||||
$timestamp = strtotime( $snapshot->post_date );
|
||||
|
||||
foreach ( $stat_keys as $stat_key ) {
|
||||
$stat_value = $snapshot->{ $stat_key };
|
||||
$stat_values[ $stat_key ][ $timestamp ] = $stat_value;
|
||||
}
|
||||
}
|
||||
|
||||
ob_start();
|
||||
require FiveForTheFuture\get_views_path() . 'list-stats.php';
|
||||
return ob_get_clean();
|
||||
}
|
|
@ -34,6 +34,7 @@ function load() {
|
|||
require_once get_includes_path() . 'xprofile.php';
|
||||
require_once get_includes_path() . 'endpoints.php';
|
||||
require_once get_includes_path() . 'miscellaneous.php';
|
||||
require_once get_includes_path() . 'stats.php';
|
||||
|
||||
// The logger expects things like `$_POST` which aren't set during unit tests.
|
||||
if ( ! $running_unit_tests ) {
|
||||
|
|
54
plugins/wporg-5ftf/views/list-stats.php
Normal file
54
plugins/wporg-5ftf/views/list-stats.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace WordPressDotOrg\FiveForTheFuture\View;
|
||||
use WP_Post;
|
||||
|
||||
defined( 'WPINC' ) || die();
|
||||
|
||||
/**
|
||||
* @var WP_Post[] $stat_values
|
||||
*/
|
||||
|
||||
?>
|
||||
|
||||
<p>
|
||||
This is just rough text-based output to check that it's working in a way that will be friendly for the vizualization that will be added in #38 (and a11y fallbacks, if any are needed).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
When that is implemented, the controller can add the data to a JSON array with `date` => `value` entries, or whatever the visualization library wants, rather than looping through it below.
|
||||
</p>
|
||||
|
||||
<?php
|
||||
|
||||
/*
|
||||
Label the h3s w/ full text descriptions instead of slugs. rough ones:
|
||||
number of total pledged hours across all companies/teams/etc
|
||||
number of people contributing (regardless of how many hours
|
||||
number of companies contributing (regardless of how many hours
|
||||
# of contributors sponsored for each team
|
||||
|
||||
how to visualize teams? maybe a dropdown w/ each team, so not a huge long list of chart for each
|
||||
*/
|
||||
|
||||
?>
|
||||
|
||||
<ul>
|
||||
<?php foreach ( $stat_values as $label => $values ) : ?>
|
||||
<h3>
|
||||
<?php echo esc_html( $label ); ?>
|
||||
</h3>
|
||||
|
||||
<?php foreach ( $values as $timestamp => $value ) : ?>
|
||||
<li>
|
||||
<?php echo esc_html( $timestamp ); ?> -
|
||||
|
||||
<?php if ( is_array( $value ) ) : ?>
|
||||
<?php echo esc_html( print_r( $value, true ) ); ?>
|
||||
<?php else : ?>
|
||||
<?php echo esc_html( $value ); ?>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
Loading…
Reference in a new issue