mirror of
https://github.com/WordPress/five-for-the-future.git
synced 2025-04-21 10:33:44 +03:00
Contributors: Add CSV report of inactive contributors
This commit is contained in:
parent
add3f255fb
commit
b86470575f
|
@ -725,7 +725,7 @@ function add_user_data_to_xprofile( array $xprofiles ) : array {
|
||||||
// phpcs:disable -- `$id_placeholders` is safely created above.
|
// phpcs:disable -- `$id_placeholders` is safely created above.
|
||||||
$established_users = $wpdb->get_results( $wpdb->prepare( "
|
$established_users = $wpdb->get_results( $wpdb->prepare( "
|
||||||
SELECT
|
SELECT
|
||||||
u.ID, u.user_email, u.user_registered, u.user_nicename,
|
u.ID, u.user_login, u.user_email, u.user_registered, u.user_nicename,
|
||||||
um.meta_keys, um.meta_values
|
um.meta_keys, um.meta_values
|
||||||
FROM `$wpdb->users` u
|
FROM `$wpdb->users` u
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
|
@ -748,6 +748,7 @@ function add_user_data_to_xprofile( array $xprofiles ) : array {
|
||||||
foreach ( $established_users as $user ) {
|
foreach ( $established_users as $user ) {
|
||||||
$full_user = array(
|
$full_user = array(
|
||||||
'user_id' => absint( $user->ID ),
|
'user_id' => absint( $user->ID ),
|
||||||
|
'user_login' => $user->user_login,
|
||||||
'user_email' => $user->user_email,
|
'user_email' => $user->user_email,
|
||||||
'user_registered' => intval( strtotime( $user->user_registered ) ),
|
'user_registered' => intval( strtotime( $user->user_registered ) ),
|
||||||
'hours_per_week' => $xprofiles[ $user->ID ]->hours_per_week,
|
'hours_per_week' => $xprofiles[ $user->ID ]->hours_per_week,
|
||||||
|
@ -833,3 +834,75 @@ function notify_inactive_contributor( array $contributor ) : void {
|
||||||
update_user_meta( $contributor['user_id'], '5ftf_last_inactivity_email', time() );
|
update_user_meta( $contributor['user_id'], '5ftf_last_inactivity_email', time() );
|
||||||
bump_stats_extra( 'five-for-the-future', 'Sent Inactive Contributor Email' );
|
bump_stats_extra( 'five-for-the-future', 'Sent Inactive Contributor Email' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an array of all the inactive contributors.
|
||||||
|
*/
|
||||||
|
function get_inactive_contributors(): array {
|
||||||
|
$inactive_contributor_ids = get_option( FiveForTheFuture\PREFIX . '_inactive_contributor_ids', array() );
|
||||||
|
$inactive_contributor_data = XProfile\get_xprofile_contribution_data( $inactive_contributor_ids );
|
||||||
|
$inactive_users = XProfile\prepare_xprofile_contribution_data( $inactive_contributor_data );
|
||||||
|
|
||||||
|
// `add_user_data_to_xprofile()` expects an array of objects.
|
||||||
|
array_walk( $inactive_users, function ( &$value ) {
|
||||||
|
$value = (object) $value;
|
||||||
|
} );
|
||||||
|
|
||||||
|
$inactive_users = add_user_data_to_xprofile( $inactive_users );
|
||||||
|
$inactive_users = prune_unnotifiable_users( $inactive_users );
|
||||||
|
$inactive_users = add_companies_to_contributors( $inactive_users );
|
||||||
|
|
||||||
|
return $inactive_users;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add company names to a given set of contributors.
|
||||||
|
*/
|
||||||
|
function add_companies_to_contributors( array $contributors ): array {
|
||||||
|
$usernames = wp_list_pluck( $contributors, 'user_login' );
|
||||||
|
$companies = get_companies_for_contributors( $usernames );
|
||||||
|
|
||||||
|
foreach ( $contributors as & $contributor ) {
|
||||||
|
if ( empty( $companies[ $contributor['user_login'] ] ) ) {
|
||||||
|
$contributor['companies'] = array();
|
||||||
|
} else {
|
||||||
|
$contributor['companies'] = $companies[ $contributor['user_login'] ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $contributors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the companies that sponsor the given usernames.
|
||||||
|
*/
|
||||||
|
function get_companies_for_contributors( array $usernames ): array {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$username_companies = array();
|
||||||
|
$username_placeholders = implode( ', ', array_fill( 0, count( $usernames ), '%s' ) );
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
SELECT
|
||||||
|
company.post_title AS company_name,
|
||||||
|
contributor.post_title as username
|
||||||
|
FROM $wpdb->posts contributor
|
||||||
|
JOIN $wpdb->posts company ON contributor.post_parent = company.ID
|
||||||
|
WHERE
|
||||||
|
contributor.post_type = '5ftf_contributor' AND
|
||||||
|
contributor.post_title IN ( $username_placeholders ) AND
|
||||||
|
contributor.post_status = 'publish'
|
||||||
|
LIMIT 3000
|
||||||
|
";
|
||||||
|
|
||||||
|
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- It is prepared, PHPCS just doesn't like $username_placeholders. That's necessary until https://core.trac.wordpress.org/ticket/54042 is merged, though.
|
||||||
|
$companies = $wpdb->get_results( $wpdb->prepare( $query, $usernames ) );
|
||||||
|
|
||||||
|
foreach ( $companies as $company ) {
|
||||||
|
$username_companies[ $company->username ][] = $company->company_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $username_companies;
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ namespace WordPressDotOrg\FiveForTheFuture\Stats;
|
||||||
|
|
||||||
use WordPressDotOrg\FiveForTheFuture;
|
use WordPressDotOrg\FiveForTheFuture;
|
||||||
use WordPressDotOrg\FiveForTheFuture\{ Contributor, Pledge, XProfile };
|
use WordPressDotOrg\FiveForTheFuture\{ Contributor, Pledge, XProfile };
|
||||||
|
use WordPressdotorg\MU_Plugins\Utilities\Export_CSV;
|
||||||
use WP_Query;
|
use WP_Query;
|
||||||
|
|
||||||
use const WordPressDotOrg\FiveForTheFuture\PREFIX;
|
use const WordPressDotOrg\FiveForTheFuture\PREFIX;
|
||||||
|
@ -16,8 +17,11 @@ defined( 'WPINC' ) || die();
|
||||||
|
|
||||||
const CPT_ID = PREFIX . '_stats_snapshot'; // Deprecated, new stats are in MC.
|
const CPT_ID = PREFIX . '_stats_snapshot'; // Deprecated, new stats are in MC.
|
||||||
|
|
||||||
add_action( 'init', __NAMESPACE__ . '\register_post_types' );
|
add_action( 'init', __NAMESPACE__ . '\register_post_types' );
|
||||||
add_action( 'init', __NAMESPACE__ . '\schedule_cron_jobs' );
|
add_action( 'init', __NAMESPACE__ . '\schedule_cron_jobs' );
|
||||||
|
add_action( 'admin_menu', __NAMESPACE__ . '\add_admin_pages' );
|
||||||
|
add_action( 'admin_init', __NAMESPACE__ . '\send_inactive_contributor_csv' );
|
||||||
|
|
||||||
add_action( PREFIX . '_record_snapshot', __NAMESPACE__ . '\record_snapshot' );
|
add_action( PREFIX . '_record_snapshot', __NAMESPACE__ . '\record_snapshot' );
|
||||||
|
|
||||||
add_shortcode( PREFIX . '_stats', __NAMESPACE__ . '\render_shortcode' );
|
add_shortcode( PREFIX . '_stats', __NAMESPACE__ . '\render_shortcode' );
|
||||||
|
@ -56,6 +60,20 @@ function schedule_cron_jobs() {
|
||||||
wp_schedule_event( time(), 'daily', PREFIX . '_record_snapshot' );
|
wp_schedule_event( time(), 'daily', PREFIX . '_record_snapshot' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register wp-admin pages.
|
||||||
|
*/
|
||||||
|
function add_admin_pages() {
|
||||||
|
add_submenu_page(
|
||||||
|
'edit.php?post_type=5ftf_pledge',
|
||||||
|
'Stats',
|
||||||
|
'Stats',
|
||||||
|
'manage_options',
|
||||||
|
'5ftf_stats',
|
||||||
|
__NAMESPACE__ . '\render_stats_page'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record a snapshot of the current stats, so we can track trends over time.
|
* Record a snapshot of the current stats, so we can track trends over time.
|
||||||
*
|
*
|
||||||
|
@ -85,6 +103,9 @@ function record_snapshot() {
|
||||||
bump_stats_extra( 'five-for-the-future', $grouped_name, $contributors );
|
bump_stats_extra( 'five-for-the-future', $grouped_name, $contributors );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This doesn't fit in MC. It's better to show it in an admin page on this site.
|
||||||
|
update_option( PREFIX . '_inactive_contributor_ids', $stats['inactive_contributor_ids'] );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -104,6 +125,7 @@ function get_snapshot_data() {
|
||||||
'self_sponsored_hours' => 0,
|
'self_sponsored_hours' => 0,
|
||||||
'team_company_sponsored_contributors' => array(),
|
'team_company_sponsored_contributors' => array(),
|
||||||
'team_self_sponsored_contributors' => array(),
|
'team_self_sponsored_contributors' => array(),
|
||||||
|
'inactive_contributor_ids' => array(),
|
||||||
);
|
);
|
||||||
|
|
||||||
$companies = new WP_Query( array(
|
$companies = new WP_Query( array(
|
||||||
|
@ -153,6 +175,8 @@ function get_snapshot_data() {
|
||||||
} else {
|
} else {
|
||||||
$active_self_sponsored_contributors++;
|
$active_self_sponsored_contributors++;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$snapshot_data['inactive_contributor_ids'][] = $user['user_id'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$team_contributor_key = sprintf( 'team_%s_contributors', $attribution_prefix );
|
$team_contributor_key = sprintf( 'team_%s_contributors', $attribution_prefix );
|
||||||
|
@ -220,3 +244,85 @@ function render_shortcode() {
|
||||||
require FiveForTheFuture\get_views_path() . 'list-stats.php';
|
require FiveForTheFuture\get_views_path() . 'list-stats.php';
|
||||||
return ob_get_clean();
|
return ob_get_clean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the Stats page in wp-admin.
|
||||||
|
*/
|
||||||
|
function render_stats_page(): void {
|
||||||
|
?>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div.wrap#fftf_stats ul {
|
||||||
|
margin-left: 20px;
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div id="fftf_stats" class="wrap">
|
||||||
|
<h1 class="wp-heading-inline">
|
||||||
|
Five for the Future Stats
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="https://mc.wordpress.org/wporg-stats.php?name=five-for-the-future">Primary stats are in MC</a> (# of contributors, % active, team stats, etc)
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="https://wordpress.org/five-for-the-future/?page_id=684">Historical/deprecated data is on the Impact page</a>.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<form method="post">
|
||||||
|
<?php wp_nonce_field( '5ftf_download_inactive_contributors' ); ?>
|
||||||
|
|
||||||
|
Contact info for inactive contributors
|
||||||
|
|
||||||
|
<button name="5ftf_download_inactive_contributors" type="submit">
|
||||||
|
Download CSV
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push the inactive contributor CSV to the user's browser
|
||||||
|
*/
|
||||||
|
function send_inactive_contributor_csv(): void {
|
||||||
|
if (
|
||||||
|
! isset( $_POST['5ftf_download_inactive_contributors'] ) ||
|
||||||
|
! current_user_can( 'manage_options' ) ||
|
||||||
|
! wp_verify_nonce( $_POST['_wpnonce'], '5ftf_download_inactive_contributors' )
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$formatted_data = array();
|
||||||
|
$inactive_contributors = Contributor\get_inactive_contributors();
|
||||||
|
|
||||||
|
foreach ( $inactive_contributors as & $contributor ) {
|
||||||
|
$formatted_data[] = array(
|
||||||
|
$contributor['user_login'],
|
||||||
|
'https://profiles.wordpress.org/' . $contributor['user_login'] . '/',
|
||||||
|
$contributor['user_email'],
|
||||||
|
implode( ', ', $contributor['companies'] ),
|
||||||
|
$contributor['hours_per_week'],
|
||||||
|
implode( ', ', $contributor['team_names'] ),
|
||||||
|
gmdate( 'Y-m-d', $contributor['last_logged_in'] ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$exporter = new Export_CSV( array(
|
||||||
|
'filename' => 'inactive-contributors',
|
||||||
|
'headers' => array( 'Username', 'Profile', 'Email', 'Companies', 'Hours Per Week', 'Teams', 'Last Login' ),
|
||||||
|
'data' => $formatted_data,
|
||||||
|
) );
|
||||||
|
|
||||||
|
$exporter->emit_file();
|
||||||
|
}
|
||||||
|
|
|
@ -84,6 +84,10 @@ function get_all_xprofile_contributor_hours_teams() : array {
|
||||||
function get_xprofile_contribution_data( array $user_ids ) {
|
function get_xprofile_contribution_data( array $user_ids ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
|
if ( empty( $user_ids ) ) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
$sql = $wpdb->prepare( '
|
$sql = $wpdb->prepare( '
|
||||||
SELECT user_id, field_id, value
|
SELECT user_id, field_id, value
|
||||||
FROM bpmain_bp_xprofile_data
|
FROM bpmain_bp_xprofile_data
|
||||||
|
@ -111,6 +115,7 @@ function prepare_xprofile_contribution_data( array $raw_data ) {
|
||||||
|
|
||||||
foreach ( $raw_data as $datum ) {
|
foreach ( $raw_data as $datum ) {
|
||||||
$user_id = $datum['user_id'];
|
$user_id = $datum['user_id'];
|
||||||
|
$prepared_data[ $user_id ]['user_id'] = $user_id;
|
||||||
$field_key = $field_keys_by_id[ (int) $datum['field_id'] ];
|
$field_key = $field_keys_by_id[ (int) $datum['field_id'] ];
|
||||||
$field_value = maybe_unserialize( $datum['value'] );
|
$field_value = maybe_unserialize( $datum['value'] );
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,12 @@ class Test_Stats extends WP_UnitTestCase {
|
||||||
'Training Team' => 1,
|
'Training Team' => 1,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
'inactive_contributor_ids' => array(
|
||||||
|
0 => 9,
|
||||||
|
1 => 11,
|
||||||
|
2 => 13,
|
||||||
|
),
|
||||||
|
|
||||||
'companies' => 2,
|
'companies' => 2,
|
||||||
'company_sponsored_contributors' => 2,
|
'company_sponsored_contributors' => 2,
|
||||||
'self_sponsored_contributors' => 3,
|
'self_sponsored_contributors' => 3,
|
||||||
|
|
Loading…
Reference in a new issue