From 7a169b55ad8ba941ce4bbcc883ad62c2b67e4c12 Mon Sep 17 00:00:00 2001 From: Adam Cassis Date: Wed, 10 Apr 2024 10:31:55 +0200 Subject: [PATCH 1/3] fix(memberships-data): lowercase returned emails --- includes/woocommerce-memberships/class-admin.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/includes/woocommerce-memberships/class-admin.php b/includes/woocommerce-memberships/class-admin.php index 38a752e9..29178114 100644 --- a/includes/woocommerce-memberships/class-admin.php +++ b/includes/woocommerce-memberships/class-admin.php @@ -74,14 +74,18 @@ public static function init() { /** * Get active members' emails. * - * @param \WC_Memberships_Membership_Plan $plan the membership plan. + * @param \WC_Memberships_Membership_Plan $plan The membership plan. */ public static function get_active_members_emails( $plan ) { $active_memberships = $plan->get_memberships( [ 'post_status' => 'wcm-active' ] ); return array_map( function ( $membership ) { $user = get_user_by( 'id', $membership->get_user_id() ); - return $user->user_email; + if ( $user ) { + return strtolower( $user->user_email ); + } else { + return ''; + } }, $active_memberships ); From 88221c18ef630ca3d89b47300b2af0cf6538e3eb Mon Sep 17 00:00:00 2001 From: Adam Cassis Date: Thu, 23 May 2024 09:44:44 +0200 Subject: [PATCH 2/3] feat(auditing): user discrepancies --- includes/class-users.php | 67 ++++++++++++--------- includes/hub/admin/class-nodes-list.php | 79 ++++++++++++++++++++++++- includes/hub/class-node.php | 10 +--- includes/node/class-info-endpoints.php | 6 +- includes/utils/class-users.php | 61 ++++++++++++++++++- 5 files changed, 179 insertions(+), 44 deletions(-) diff --git a/includes/class-users.php b/includes/class-users.php index 16fa3507..f5391d99 100644 --- a/includes/class-users.php +++ b/includes/class-users.php @@ -19,7 +19,7 @@ class Users { */ public static function init() { add_filter( 'manage_users_columns', [ __CLASS__, 'manage_users_columns' ] ); - add_filter( 'manage_users_custom_column', [ __CLASS__, 'manage_users_custom_column' ], 10, 3 ); + add_filter( 'manage_users_custom_column', [ __CLASS__, 'manage_users_custom_column' ], 99, 3 ); // priority must be higher than Jetpack's jetpack_show_connection_status (10). add_filter( 'users_list_table_query_args', [ __CLASS__, 'users_list_table_query_args' ] ); } @@ -30,9 +30,7 @@ public static function init() { * @return array */ public static function manage_users_columns( $columns ) { - if ( Site_Role::is_hub() ) { - $columns['newspack_network_activity'] = __( 'Newspack Network Activity', 'newspack-network' ); - } + $columns['newspack_network_activity'] = __( 'Newspack Network Activity', 'newspack-network' ); if ( \Newspack_Network\Admin::use_experimental_auditing_features() ) { $columns['newspack_network_user'] = __( 'Network Original User', 'newspack-network' ); } @@ -60,36 +58,45 @@ public static function manage_users_custom_column( $value, $column_name, $user_i ); } } - if ( 'newspack_network_activity' === $column_name && Site_Role::is_hub() ) { + if ( 'newspack_network_activity' === $column_name ) { $user = get_user_by( 'id', $user_id ); if ( ! $user ) { return $value; } + if ( Site_Role::is_hub() ) { + $last_activity = \Newspack_Network\Hub\Stores\Event_Log::get( [ 'email' => $user->user_email ], 1 ); + if ( empty( $last_activity ) ) { + return '-'; + } - $last_activity = \Newspack_Network\Hub\Stores\Event_Log::get( [ 'email' => $user->user_email ], 1 ); - - if ( empty( $last_activity ) ) { - return '-'; + $event_log_url = add_query_arg( + [ + 'page' => EVENT_LOG_PAGE_SLUG, + 'email' => urlencode( $user->user_email ), + ], + admin_url( 'admin.php' ) + ); + return sprintf( + '%s: %s
%s', + __( 'Last Activity', 'newspack-network' ), + $last_activity[0]->get_summary(), + $event_log_url, + __( 'View all', 'newspack-network' ) + ); + } else { + $event_log_url = add_query_arg( + [ + 'page' => EVENT_LOG_PAGE_SLUG, + 'email' => urlencode( $user->user_email ), + ], + untrailingslashit( Node\Settings::get_hub_url() ) . '/wp-admin/admin.php' + ); + return sprintf( + '%s', + $event_log_url, + __( 'View activity', 'newspack-network' ) + ); } - - $last_activity = $last_activity[0]; - - $summary = $last_activity->get_summary(); - $event_log_url = add_query_arg( - [ - 'page' => EVENT_LOG_PAGE_SLUG, - 'email' => $user->user_email, - ], - admin_url( 'admin.php' ) - ); - return sprintf( - '%s: %s
%s', - __( 'Last Activity', 'newspack-network' ), - $summary, - $event_log_url, - __( 'View all', 'newspack-network' ) - ); - } return $value; } @@ -106,6 +113,10 @@ public static function users_list_table_query_args( $args ) { $args['role__in'] = explode( ',', sanitize_text_field( $_REQUEST['role__in'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended unset( $args['role'] ); } + if ( isset( $_REQUEST['role__not_in'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $args['role__not_in'] = explode( ',', sanitize_text_field( $_REQUEST['role__not_in'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + unset( $args['role'] ); + } return $args; } } diff --git a/includes/hub/admin/class-nodes-list.php b/includes/hub/admin/class-nodes-list.php index d02d43f5..caba2e5e 100644 --- a/includes/hub/admin/class-nodes-list.php +++ b/includes/hub/admin/class-nodes-list.php @@ -27,6 +27,20 @@ public static function init() { add_action( 'admin_bar_menu', [ __CLASS__, 'admin_bar_menu' ], 100 ); } + /** + * Cache for site info responses. + * + * @var array + */ + private static $node_site_info_cache = []; + + /** + * Cache for Hub site info. + * + * @var array + */ + private static $hub_site_info = false; + /** * Modify columns on post type table * @@ -37,16 +51,32 @@ public static function posts_columns( $columns ) { unset( $columns['date'] ); unset( $columns['stats'] ); if ( \Newspack_Network\Admin::use_experimental_auditing_features() ) { + $sync_users_count = \Newspack_Network\Utils\Users::get_synchronized_users_count(); $sync_users_info = sprintf( ' ', sprintf( /* translators: list of user roles which will entail synchronization */ esc_attr__( 'Users with the following roles: %1$s (%2$d on the Hub)', 'newspack-network' ), implode( ', ', \Newspack_Network\Utils\Users::get_synced_user_roles() ), - \Newspack_Network\Utils\Users::get_synchronized_users_count() + $sync_users_count + ) + ); + /* translators: %d is the synchronizable users count. */ + $columns['sync_users'] = sprintf( __( 'Synchronizable Users (%d)', 'newspack-network' ), $sync_users_count ) . $sync_users_info; + if ( isset( $_GET['_newspack_user_discrepancies'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $columns['user_discrepancies'] = __( 'Discrepancies in Sync. Users', 'newspack-network' ); + } + + $not_sync_users_info = sprintf( + ' ', + sprintf( + /* translators: list of user roles which will entail synchronization */ + esc_attr__( 'Users with roles different than the following roles: %1$s (%2$d on the Hub)', 'newspack-network' ), + implode( ', ', \Newspack_Network\Utils\Users::get_synced_user_roles() ), + \Newspack_Network\Utils\Users::get_not_synchronized_users_count() ) ); - $columns['sync_users'] = __( 'Synchronizable Users', 'newspack-network' ) . $sync_users_info; + $columns['not_sync_users'] = __( 'Non-synchronizable Users', 'newspack-network' ) . $not_sync_users_info; } $columns['links'] = __( 'Links', 'newspack-network' ); return $columns; @@ -80,6 +110,38 @@ function ( $bookmark ) {

get_site_info(); + } + $node_site_info = self::$node_site_info_cache[ $post_id ]; + + if ( isset( $_GET['_newspack_user_discrepancies'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ! self::$hub_site_info ) { + self::$hub_site_info = [ + 'sync_user_emails' => \Newspack_Network\Utils\Users::get_synchronized_users_emails(), + ]; + } + + // Display user discrepancies. + $node_users_emails = $node_site_info->sync_users_emails ?? []; + // Users who are on the Hub but not on the Node. + $not_on_node = array_diff( self::$hub_site_info['sync_user_emails'], $node_users_emails ); + // Users who are not on the Node but are on the Hub. + $not_on_hub = array_diff( $node_users_emails, self::$hub_site_info['sync_user_emails'] ); + if ( 'user_discrepancies' === $column ) { + ?> + + + + get_url() ) . 'wp-admin/users.php' ); ?> - get_sync_users_count() ); ?> + sync_users_count ?? 0 ); ?> + implode( ',', \Newspack_Network\Utils\Users::get_synced_user_roles() ), + ], + trailingslashit( $node->get_url() ) . 'wp-admin/users.php' + ); + ?> + not_sync_users_count ?? 0 ); ?> get_url() . '/wp-json/newspack-network/v1/info', [ @@ -183,12 +183,4 @@ private function get_site_info() { ); return json_decode( wp_remote_retrieve_body( $response ) ); } - - /** - * Get synchronized users count. - */ - public function get_sync_users_count() { - $site_info = $this->get_site_info(); - return $site_info->sync_users_count ?? 0; - } } diff --git a/includes/node/class-info-endpoints.php b/includes/node/class-info-endpoints.php index b5b57238..6761207c 100644 --- a/includes/node/class-info-endpoints.php +++ b/includes/node/class-info-endpoints.php @@ -46,7 +46,11 @@ public static function register_routes() { public static function handle_info_request() { return rest_ensure_response( [ - 'sync_users_count' => \Newspack_Network\Utils\Users::get_synchronized_users_count(), + 'sync_users_count' => \Newspack_Network\Utils\Users::get_synchronized_users_count(), + 'sync_users_emails' => \Newspack_Network\Utils\Users::get_synchronized_users_emails(), + 'not_sync_users_count' => \Newspack_Network\Utils\Users::get_not_synchronized_users_count(), + 'not_sync_users_emails' => \Newspack_Network\Utils\Users::get_not_synchronized_users_emails(), + 'no_role_users_emails' => \Newspack_Network\Utils\Users::get_no_role_users_emails(), ] ); } diff --git a/includes/utils/class-users.php b/includes/utils/class-users.php index 41aa3c58..c4baeaa1 100644 --- a/includes/utils/class-users.php +++ b/includes/utils/class-users.php @@ -174,13 +174,68 @@ public static function get_synced_user_roles() { * Get synchronized users count. */ public static function get_synchronized_users_count() { - $users = get_users( + return count( self::get_synchronized_users( [ 'id' ] ) ); + } + + /** + * Get synchronized users emails. + */ + public static function get_synchronized_users_emails() { + return array_map( 'strtolower', array_column( self::get_synchronized_users( [ 'user_email' ] ), 'user_email' ) ); + } + + /** + * Get no-role users emails. + */ + public static function get_no_role_users_emails() { + global $wpdb; + $no_role_users_emails = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + "SELECT user_email FROM wp_users WHERE ID IN (SELECT user_id FROM wp_usermeta WHERE meta_key = 'wp_capabilities' AND (meta_value = 'a:0:{}' OR meta_value = '')) OR ID NOT IN (SELECT user_id FROM wp_usermeta WHERE meta_key = 'wp_capabilities')" + ); + return array_map( 'strtolower', array_column( $no_role_users_emails, 'user_email' ) ); + } + + /** + * Get synchronized users. + * + * @param array $fields Fields to return. + */ + public static function get_synchronized_users( $fields = [] ) { + return get_users( [ 'role__in' => self::get_synced_user_roles(), - 'fields' => [ 'id' ], + 'fields' => $fields, 'number' => -1, ] ); - return count( $users ); + } + + /** + * Get not synchronized users count. + * + * @param array $fields Fields to return. + */ + public static function get_not_synchronized_users( $fields = [] ) { + return get_users( + [ + 'role__not_in' => self::get_synced_user_roles(), + 'fields' => $fields, + 'number' => -1, + ] + ); + } + + /** + * Get synchronized users emails. + */ + public static function get_not_synchronized_users_emails() { + return array_map( 'strtolower', array_column( self::get_not_synchronized_users( [ 'user_email' ] ), 'user_email' ) ); + } + + /** + * Get not synchronized users count. + */ + public static function get_not_synchronized_users_count() { + return count( self::get_not_synchronized_users( [ 'id' ] ) ); } } From 2b15092383b3ee96202f534f32d78bc5f0205aee Mon Sep 17 00:00:00 2001 From: Adam Cassis Date: Mon, 1 Jul 2024 09:08:52 +0200 Subject: [PATCH 3/3] Update includes/hub/admin/class-nodes-list.php Co-authored-by: Derrick Koo --- includes/hub/admin/class-nodes-list.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/hub/admin/class-nodes-list.php b/includes/hub/admin/class-nodes-list.php index caba2e5e..1332753b 100644 --- a/includes/hub/admin/class-nodes-list.php +++ b/includes/hub/admin/class-nodes-list.php @@ -134,7 +134,7 @@ function ( $bookmark ) {