set_token( Credentials\get_token( $user_id ) ); } $profile = $api->get_user(); // Nothing valid came back, so we have no data to store. if ( is_wp_error( $profile ) ) { return array(); } // Retrieve only the necessary items. return array( 'user_id' => sanitize_text_field( $profile->id ), 'first_name' => sanitize_text_field( $profile->first_name ), 'last_name' => sanitize_text_field( $profile->last_name ), 'email' => sanitize_text_field( $profile->email ), ); } /** * Build a target array for Airstory. * * @param int $user_id The ID of the WordPress user associated with the target. * @return array An array that will serve as a the post body for the target, containing four keys: * identifier (user ID), name (blog name), url (webhook URL), and the type of * connection ("wordpress"). */ function get_target( $user_id ) { return array( 'identifier' => (string) $user_id, // Airstory expects a string. 'name' => get_bloginfo( 'name' ), 'url' => get_rest_url( null, '/airstory/v1/webhook', 'https' ), 'type' => 'wordpress', ); } /** * Does the given user have a connection to Airstory? * * Note that this function does not check the validity of a connection, only whether or not one * exists in the database. * * @param int $user_id The user ID to inspect. * @return bool True if there's an _airstory_target for the given user, false otherwise. */ function has_connection( $user_id ) { return (bool) get_user_option( '_airstory_target', $user_id ); } /** * Once a user has provided their token, authenticate with Airstory and save information locally. * * This information will include the Airstory user's first/last name, email, and user_id, which * are used to connect WordPress to Airstory. * * After storing the profile information, a new "target" will be registered within the user's * Airstory account, and the target ID stored. * * @param int $user_id The ID of the user who has connected. * @return string|WP_Error Either the GUID for the connection ID if registered successfully, or a * WP_Error object explaining what went wrong. */ function register_connection( $user_id ) { if ( has_connection( $user_id ) ) { return; } $profile = get_user_profile( $user_id ); if ( empty( $profile ) ) { add_action( 'user_profile_update_errors', 'Airstory\Settings\profile_error_save_token' ); return new WP_Error( 'airstory-empty-profile', __( 'User does not have an Airstory profile registered within WordPress.', 'airstory' ) ); } $target = get_target( $user_id ); $api = new Airstory\API; $connection_id = $api->post_target( $profile['email'], $target ); if ( is_wp_error( $connection_id ) ) { add_action( 'user_profile_update_errors', 'Airstory\Settings\profile_error_save_token' ); return $connection_id; } // Store the profile and connection ID for the user. Settings\set_user_data( $user_id, 'profile', $profile ); update_user_option( $user_id, '_airstory_target', sanitize_text_field( $connection_id ) ); /** * A connection between WordPress and Airstory has been established successfully. * * @param int $user_id The ID of the user that has connected. * @param string $connection_id The UUID of the connection within Airstory. * @param array $target The information sent to create the target within Airstory: site * name, callback URL, and the WordPress user ID. */ do_action( 'airstory_register_connection', $user_id, $connection_id, $target ); return $connection_id; } add_action( 'airstory_user_connect', __NAMESPACE__ . '\register_connection' ); /** * Update an existing connection for an Airstory user. * * @param int $user_id The ID of the user whose connection should be updated. */ function update_connection( $user_id ) { $profile = get_user_profile( $user_id ); if ( empty( $profile ) ) { return; } // Overwrite the existing target info for $connection_id. $connection_id = get_user_option( '_airstory_target', $user_id ); $target = get_target( $user_id ); $api = new Airstory\API; $response = $api->put_target( $profile['email'], $connection_id, $target ); if ( is_wp_error( $response ) ) { return; } /** * A connection between WordPress and Airstory has been updated successfully. * * @param int $user_id The ID of the user that has connected. * @param string $connection_id The UUID of the connection within Airstory. * @param array $target The information sent to create the target within Airstory: site * name, callback URL, and the WordPress user ID. */ do_action( 'airstory_update_connection', $user_id, $connection_id, $target ); return $connection_id; } /** * If a user disconnects from Airstory, the corresponding connection should be removed as well. * * This function will remove the target within the user's Airstory profile, then remove any stored * Airstory profile information. * * @param int $user_id The ID of the user who has disconnected. */ function remove_connection( $user_id ) { $profile = Settings\get_user_data( $user_id, 'profile', array() ); $connection_id = get_user_option( '_airstory_target', $user_id ); if ( ! empty( $profile['email'] ) && ! empty( $connection_id ) ) { $api = new Airstory\API; $api->delete_target( $profile['email'], $connection_id ); } // Clean up the user meta. delete_user_option( $user_id, '_airstory_target' ); /** * A connection between WordPress and Airstory has been closed successfully. * * @param int $user_id The ID of the user that has disconnected. * @param string $connection_id The UUID of the connection within Airstory. */ do_action( 'airstory_remove_connection', $user_id, $connection_id ); } add_action( 'airstory_user_disconnect', __NAMESPACE__ . '\remove_connection' ); /** * Given a user ID and an array of blog IDs, connect or disconnect users from Airstory. * * @param int $user_id The user ID to update. * @param array $blog_ids An array of blog IDs where the user should be connected. Any blog *not* * in the array will be disconnected. */ function set_connected_blogs( $user_id, $blog_ids ) { if ( ! is_multisite() ) { return; } $available_blogs = Settings\get_available_blogs( $user_id ); $blog_ids = array_map( 'absint', (array) $blog_ids ); foreach ( $available_blogs as $blog ) { switch_to_blog( $blog['id'] ); if ( in_array( $blog['id'], $blog_ids, true ) ) { register_connection( $user_id ); } else { remove_connection( $user_id ); } restore_current_blog(); } } /** * Triggers an asynchronous regeneration of all connections. * * Certain events, like renaming the site or changing the URL, should cause connections within * Airstory to be updated. * * @param mixed $old_value The previous value for the option. * @param mixed $new_value The new value for the option. */ function trigger_connection_refresh( $old_value, $new_value ) { if ( $old_value === $new_value ) { return; } /** * Cause Airstory to update all known connections. */ do_action( 'airstory_update_all_connections' ); } add_action( 'update_option_blogname', __NAMESPACE__ . '\trigger_connection_refresh', 10, 2 ); add_action( 'update_option_siteurl', __NAMESPACE__ . '\trigger_connection_refresh', 10, 2 ); add_action( 'update_option_home', __NAMESPACE__ . '\trigger_connection_refresh', 10, 2 ); /** * Update connection details for all currently-connected users. * * Under certain conditions, it may be necessary to update the connection details within Airstory. * For example, if the site URL changes, this would impact webhook URL. */ function update_all_connections() { $user_args = array( 'fields' => 'ID', 'number' => 100, 'paged' => 1, 'meta_query' => array( array( 'key' => '_airstory_target', 'compare' => 'EXISTS', ), ), ); $connected_users = new \WP_User_Query( $user_args ); $user_ids = $connected_users->results; while ( ! empty( $user_ids ) ) { $user_id = array_shift( $user_ids ); update_connection( $user_id ); // If we've reached the end, get the next page. if ( empty( $user_ids ) ) { $user_args['paged']++; $connected_users = new \WP_User_Query( $user_args ); $user_ids = $connected_users->results; } } } add_action( 'wp_async_airstory_update_all_connections', __NAMESPACE__ . '\update_all_connections' );