diff --git a/plugins/wporg-5ftf/includes/contributor.php b/plugins/wporg-5ftf/includes/contributor.php index 1347358..0b46465 100644 --- a/plugins/wporg-5ftf/includes/contributor.php +++ b/plugins/wporg-5ftf/includes/contributor.php @@ -725,7 +725,7 @@ function add_user_data_to_xprofile( array $xprofiles ) : array { // phpcs:disable -- `$id_placeholders` is safely created above. $established_users = $wpdb->get_results( $wpdb->prepare( " 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 FROM `$wpdb->users` u LEFT JOIN ( @@ -748,6 +748,7 @@ function add_user_data_to_xprofile( array $xprofiles ) : array { foreach ( $established_users as $user ) { $full_user = array( 'user_id' => absint( $user->ID ), + 'user_login' => $user->user_login, 'user_email' => $user->user_email, 'user_registered' => intval( strtotime( $user->user_registered ) ), '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() ); 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; +} diff --git a/plugins/wporg-5ftf/includes/stats.php b/plugins/wporg-5ftf/includes/stats.php index eadf226..b1c1db4 100644 --- a/plugins/wporg-5ftf/includes/stats.php +++ b/plugins/wporg-5ftf/includes/stats.php @@ -8,6 +8,7 @@ namespace WordPressDotOrg\FiveForTheFuture\Stats; use WordPressDotOrg\FiveForTheFuture; use WordPressDotOrg\FiveForTheFuture\{ Contributor, Pledge, XProfile }; +use WordPressdotorg\MU_Plugins\Utilities\Export_CSV; use WP_Query; use const WordPressDotOrg\FiveForTheFuture\PREFIX; @@ -16,8 +17,11 @@ defined( 'WPINC' ) || die(); const CPT_ID = PREFIX . '_stats_snapshot'; // Deprecated, new stats are in MC. -add_action( 'init', __NAMESPACE__ . '\register_post_types' ); -add_action( 'init', __NAMESPACE__ . '\schedule_cron_jobs' ); +add_action( 'init', __NAMESPACE__ . '\register_post_types' ); +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_shortcode( PREFIX . '_stats', __NAMESPACE__ . '\render_shortcode' ); @@ -56,6 +60,20 @@ function schedule_cron_jobs() { 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. * @@ -85,6 +103,9 @@ function record_snapshot() { 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, 'team_company_sponsored_contributors' => array(), 'team_self_sponsored_contributors' => array(), + 'inactive_contributor_ids' => array(), ); $companies = new WP_Query( array( @@ -153,6 +175,8 @@ function get_snapshot_data() { } else { $active_self_sponsored_contributors++; } + } else { + $snapshot_data['inactive_contributor_ids'][] = $user['user_id']; } $team_contributor_key = sprintf( 'team_%s_contributors', $attribution_prefix ); @@ -220,3 +244,85 @@ function render_shortcode() { require FiveForTheFuture\get_views_path() . 'list-stats.php'; return ob_get_clean(); } + +/** + * Render the Stats page in wp-admin. + */ +function render_stats_page(): void { + ?> + + + +
+

+ Five for the Future Stats +

+ + + +
+ + 'inactive-contributors', + 'headers' => array( 'Username', 'Profile', 'Email', 'Companies', 'Hours Per Week', 'Teams', 'Last Login' ), + 'data' => $formatted_data, + ) ); + + $exporter->emit_file(); +} diff --git a/plugins/wporg-5ftf/includes/xprofile.php b/plugins/wporg-5ftf/includes/xprofile.php index aae2c26..352000b 100644 --- a/plugins/wporg-5ftf/includes/xprofile.php +++ b/plugins/wporg-5ftf/includes/xprofile.php @@ -84,6 +84,10 @@ function get_all_xprofile_contributor_hours_teams() : array { function get_xprofile_contribution_data( array $user_ids ) { global $wpdb; + if ( empty( $user_ids ) ) { + return array(); + } + $sql = $wpdb->prepare( ' SELECT user_id, field_id, value FROM bpmain_bp_xprofile_data @@ -111,6 +115,7 @@ function prepare_xprofile_contribution_data( array $raw_data ) { foreach ( $raw_data as $datum ) { $user_id = $datum['user_id']; + $prepared_data[ $user_id ]['user_id'] = $user_id; $field_key = $field_keys_by_id[ (int) $datum['field_id'] ]; $field_value = maybe_unserialize( $datum['value'] ); diff --git a/plugins/wporg-5ftf/tests/test-stats.php b/plugins/wporg-5ftf/tests/test-stats.php index 06d6d09..1897b7d 100644 --- a/plugins/wporg-5ftf/tests/test-stats.php +++ b/plugins/wporg-5ftf/tests/test-stats.php @@ -78,6 +78,12 @@ class Test_Stats extends WP_UnitTestCase { 'Training Team' => 1, ), + 'inactive_contributor_ids' => array( + 0 => 9, + 1 => 11, + 2 => 13, + ), + 'companies' => 2, 'company_sponsored_contributors' => 2, 'self_sponsored_contributors' => 3,