Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Data integrity improvements #89

Draft
wants to merge 16 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions includes/class-accepted-actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class Accepted_Actions {
'donation_new' => 'Donation_New',
'donation_subscription_cancelled' => 'Donation_Subscription_Cancelled',
'network_user_updated' => 'User_Updated',
'network_user_deleted' => 'User_Deleted',
'newspack_network_woo_membership_updated' => 'Woocommerce_Membership_Updated',
'network_manual_sync_user' => 'User_Manually_Synced',
'network_nodes_synced' => 'Nodes_Synced',
Expand All @@ -53,6 +54,7 @@ class Accepted_Actions {
'donation_new',
'donation_subscription_cancelled',
'network_user_updated',
'network_user_deleted',
'newspack_network_woo_membership_updated',
'network_manual_sync_user',
'network_nodes_synced',
Expand Down
7 changes: 1 addition & 6 deletions includes/class-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,6 @@ public static function enqueue_scripts() {
* @return bool True if experimental auditing features are enabled.
*/
public static function use_experimental_auditing_features() {
$user_slug = defined( 'NEWSPACK_NETWORK_EXPERIMENTAL_AUDITING_USER' ) ? NEWSPACK_NETWORK_EXPERIMENTAL_AUDITING_USER : false;
if ( ! $user_slug ) {
return false;
}
$user = get_user_by( 'login', $user_slug );
return $user && get_current_user_id() === $user->ID;
return defined( 'NEWSPACK_NETWORK_EXPERIMENTAL_AUDITING_FEATURES' ) ? NEWSPACK_NETWORK_EXPERIMENTAL_AUDITING_FEATURES : false;
}
}
21 changes: 21 additions & 0 deletions includes/class-data-listeners.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public static function register_listeners() {
Data_Events::register_listener( 'woocommerce_subscription_status_changed', 'newspack_node_subscription_changed', [ __CLASS__, 'item_changed' ] );
Data_Events::register_listener( 'woocommerce_order_status_changed', 'newspack_node_order_changed', [ __CLASS__, 'item_changed' ] );
Data_Events::register_listener( 'newspack_network_user_updated', 'network_user_updated', [ __CLASS__, 'user_updated' ] );
Data_Events::register_listener( 'delete_user', 'network_user_deleted', [ __CLASS__, 'user_deleted' ] );
Data_Events::register_listener( 'newspack_network_nodes_synced', 'network_nodes_synced', [ __CLASS__, 'nodes_synced' ] );
}

Expand Down Expand Up @@ -87,6 +88,26 @@ public static function user_updated( $user_data ) {
return $user_data;
}

/**
* Filters the user data for the event being triggered
*
* @param int $id ID of the user to delete.
* @param int|null $reassign ID of the user to reassign posts and links to.
* Default null, for no reassignment.
* @param WP_User $user WP_User object of the user to delete.
* @return array
*/
public static function user_deleted( $id, $reassign, $user ) {
$should_delete = apply_filters( 'newspack_network_process_user_deleted', true, $user->user_email );
if ( ! $should_delete ) {
Debugger::log( 'User deletion with email: ' . $user->user_email . ' was skipped due to filter use.' );
return;
}
return [
'email' => $user->user_email,
];
}

/**
* Filters the nodes data for the event being triggered
*
Expand Down
59 changes: 58 additions & 1 deletion includes/class-esp-metadata-sync.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

namespace Newspack_Network;

use Newspack_Network\Utils\Users as User_Utils;

/**
* Class to handle Node settings page
*/
Expand All @@ -21,6 +23,8 @@ public static function init() {
\add_filter( 'newspack_ras_metadata_keys', [ __CLASS__, 'add_custom_metadata_fields' ] );
\add_filter( 'newspack_register_reader_metadata', [ __CLASS__, 'handle_custom_metadata_fields' ], 10, 2 );
\add_filter( 'newspack_data_events_reader_registered_metadata', [ __CLASS__, 'handle_custom_metadata_fields' ], 10, 2 );
\add_action( 'newspack_network_network_reader', [ __CLASS__, 'handle_custom_metadata_for_network_readers' ] );
\add_action( 'newspack_network_new_network_reader', [ __CLASS__, 'handle_custom_metadata_for_network_readers' ] );
}

/**
Expand Down Expand Up @@ -48,9 +52,62 @@ public static function add_custom_metadata_fields( $metadata_fields ) {
*/
public static function handle_custom_metadata_fields( $metadata, $user_id ) {
if ( $user_id ) {
$metadata['network_registration_site'] = \esc_url( \get_site_url() );
$remote_site = \get_user_meta( $user_id, User_Utils::USER_META_REMOTE_SITE, true );
$registration_site = \esc_url( ! empty( \wp_http_validate_url( $remote_site ) ) ? $remote_site : \get_site_url() );
$metadata['network_registration_site'] = $registration_site;
}

return $metadata;
}

/**
* Trigger a reader data sync to the connected ESP.
*
* @param array $contact The contact data to sync.
*/
public static function sync_contact( $contact ) {
// Only if Reader Activation and Newspack Newsletters are available.
if ( ! class_exists( 'Newspack\Reader_Activation' ) || ! method_exists( 'Newspack_Newsletters', 'service_provider' ) ) {
return;
}

// Only if RAS + ESP sync is enabled.
if ( ! \Newspack\Reader_Activation::is_enabled() || ! \Newspack\Reader_Activation::get_setting( 'sync_esp' ) ) {
return;
}

// Only if we have the ESP Data Events connectors.
if ( ! class_exists( 'Newspack\Data_Events\Connectors\Mailchimp' ) || ! class_exists( 'Newspack\Data_Events\Connectors\ActiveCampaign' ) ) {
return;
}

$service_provider = \Newspack_Newsletters::service_provider();
if ( 'mailchimp' === $service_provider ) {
return \Newspack\Data_Events\Connectors\Mailchimp::put( $contact );
} elseif ( 'active_campaign' === $service_provider ) {
return \Newspack\Data_Events\Connectors\ActiveCampaign::put( $contact );
}
}

/**
* Sync custom metadata fields for network readers.
*
* @param WP_User $user The newly created or existing user.
*/
public static function handle_custom_metadata_for_network_readers( $user ) {
if ( ! $user ) {
return;
}
$contact = \Newspack\WooCommerce_Connection::get_contact_from_customer( new \WC_Customer( $user->ID ) );
$metadata = $contact['metadata'] ?? [];

// Ensure email is set as the user probably won't have a billing email.
if ( ! isset( $contact['email'] ) ) {
$contact['email'] = $user->user_email;
}

$contact['metadata'] = self::handle_custom_metadata_fields( $metadata, $user->ID );

self::sync_contact( $contact );
}
}
8 changes: 6 additions & 2 deletions includes/class-initializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ public static function init() {
Hub\Nodes::init();
Hub\Webhook::init();
Hub\Pull_Endpoint::init();
Hub\Network_Data_Endpoint::init();
Hub\Event_Listeners::init();
Hub\Database\Subscriptions::init();
Hub\Database\Orders::init();
// phpcs:disable
// Hub\Database\Subscriptions::init();
// Hub\Database\Orders::init();
// phpcs:enable
Hub\Newspack_Ads_GAM::init();
Hub\Connect_Node::init();
}
Expand All @@ -53,6 +56,7 @@ public static function init() {

Synchronize_All::init();
Data_Backfill::init();
Misc::init();
Membership_Dedupe::init();

Woocommerce_Memberships\Admin::init();
Expand Down
4 changes: 4 additions & 0 deletions includes/class-rest-authenticaton.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ class Rest_Authenticaton {
'endpoint' => '|^/wc/v2/memberships/plans|',
'callback' => [ __CLASS__, 'add_filter_for_woo_read_endpoints' ],
],
'get-woo-memberships' => [
'endpoint' => '|^/wc/v2/memberships|',
'callback' => [ __CLASS__, 'add_filter_for_woo_read_endpoints' ],
],
];

/**
Expand Down
69 changes: 41 additions & 28 deletions includes/class-users.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

namespace Newspack_Network;

use const Newspack_Network\constants\EVENT_LOG_PAGE_SLUG;

/**
* Class to handle the Users admin page
*/
Expand All @@ -17,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' ] );
}

Expand All @@ -28,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' );
}
Expand Down Expand Up @@ -58,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: <code>%s</code><br><a href="%s">%s</a>',
__( '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(
'<a href="%s">%s</a>',
$event_log_url,
__( 'View activity', 'newspack-network' )
);
}

$last_activity = $last_activity[0];

$summary = $last_activity->get_summary();
$event_log_url = add_query_arg(
[
'page' => \Newspack_Network\Hub\Admin\Event_Log::PAGE_SLUG,
'email' => $user->user_email,
],
admin_url( 'admin.php' )
);
return sprintf(
'%s: <code>%s</code><br><a href="%s">%s</a>',
__( 'Last Activity', 'newspack-network' ),
$summary,
$event_log_url,
__( 'View all', 'newspack-network' )
);

}
return $value;
}
Expand All @@ -104,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;
}
}
14 changes: 10 additions & 4 deletions includes/cli/backfillers/class-reader-registered.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function get_events() {
if ( empty( $roles_to_sync ) ) {
WP_CLI::error( 'Incompatible Newspack plugin version or no roles to sync.' );
}
// Get all users registered between this-> and $end.
// Get all users registered between specified dates.
$users = get_users(
[
'role__in' => $roles_to_sync,
Expand All @@ -44,6 +44,7 @@ public function get_events() {
'before' => $this->end,
'inclusive' => true,
],
'orderby' => 'user_registered',
'fields' => [ 'id', 'user_email', 'user_registered' ],
'number' => -1,
'meta_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
Expand All @@ -55,7 +56,9 @@ public function get_events() {
]
);

WP_CLI::line( '' );
WP_CLI::line( sprintf( 'Found %s user(s) eligible for sync.', count( $users ) ) );
WP_CLI::line( '' );

$this->maybe_initialize_progress_bar( 'Processing users', count( $users ) );

Expand All @@ -76,9 +79,12 @@ function( $args ) {
foreach ( $users as $user ) {
$registration_method = get_user_meta( $user->ID, \Newspack\Reader_Activation::REGISTRATION_METHOD, true );
$user_data = [
'user_id' => $user->ID,
'email' => $user->user_email,
'metadata' => [
'user_id' => $user->ID,
'email' => $user->user_email,
'user_registered' => $user->user_registered,
'first_name' => get_user_meta( $user->ID, 'first_name', true ),
'last_name' => get_user_meta( $user->ID, 'last_name', true ),
'meta_input' => [
// 'current_page_url' is not saved, can't be backfilled.
'registration_method' => empty( $registration_method ) ? 'backfill-script' : $registration_method,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ public function get_events() {
$this->maybe_initialize_progress_bar( 'Processing memberships', count( $membership_posts_ids ) );

$events = [];
WP_CLI::line( '' );
WP_CLI::line( sprintf( 'Found %s membership(s) eligible for sync.', count( $membership_posts_ids ) ) );
WP_CLI::line( '' );

foreach ( $membership_posts_ids as $post_id ) {
$membership = new \WC_Memberships_User_Membership( $post_id );
Expand All @@ -77,10 +80,21 @@ public function get_events() {
'membership_id' => $membership->get_id(),
'new_status' => $status,
];
if ( $status === 'active' ) {
$timestamp = null;
switch ( $status ) {
case 'paused':
$timestamp = strtotime( $membership->get_paused_date() );
break;
case 'cancelled':
$timestamp = strtotime( $membership->get_cancelled_date() );
break;
case 'expired':
$timestamp = strtotime( $membership->get_end_date() );
break;
}

if ( ! $timestamp ) {
$timestamp = strtotime( $membership->get_start_date() );
} else {
$timestamp = strtotime( $membership->get_end_date() );
}

$events[] = new \Newspack_Network\Incoming_Events\Woocommerce_Membership_Updated( get_bloginfo( 'url' ), $membership_data, $timestamp );
Expand Down
Loading