rawurlencode( site_url() ), 'return' => rawurlencode( add_query_arg( array( 'action' => 'useclick_connect', '_wpnonce' => wp_create_nonce( 'useclick_connect' ), ), admin_url( 'admin-post.php' ) ) ), 'state' => $state, ), '/dashboard/connect/wordpress' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- checked above with check_admin_referer(). $signup = ! empty( $_GET['signup'] ); $target = $signup ? add_query_arg( array( 'ref' => 'wordpress-plugin', 'redirect' => rawurlencode( $approve ), ), USECLICK_APP_URL . '/register' ) : USECLICK_APP_URL . $approve; wp_safe_redirect( $target ); exit; } /** * Step 2: back from useclick.io with a one-time code. Trade it (server to server) for an * API key named "WordPress: " and save it. The key never passes through the browser. */ public static function handle_connect_return() { if ( ! current_user_can( UseClick_Admin::capability() ) ) { wp_die( esc_html__( 'You are not allowed to change these settings.', 'useclick' ), 403 ); } // Two checks: the WordPress nonce on the return address, and a one-time random state kept for this user. $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : ''; $valid = false !== wp_verify_nonce( $nonce, 'useclick_connect' ); $state = $valid && isset( $_GET['state'] ) ? sanitize_text_field( wp_unslash( $_GET['state'] ) ) : ''; $code = $valid && isset( $_GET['code'] ) ? sanitize_text_field( wp_unslash( $_GET['code'] ) ) : ''; $saved = (string) get_transient( 'useclick_connect_state_' . get_current_user_id() ); delete_transient( 'useclick_connect_state_' . get_current_user_id() ); if ( ! $valid || '' === $saved || '' === $state || ! hash_equals( $saved, $state ) || '' === $code ) { self::connect_failed( __( 'The connection could not be confirmed (the link expired or was opened in another browser). Click Connect with useClick.io again.', 'useclick' ) ); } $exchange = preg_replace( '#/v1$#', '', UseClick_API::base_url() ) . '/integrations/wordpress/exchange'; $response = wp_remote_post( $exchange, array( 'timeout' => 15, 'user-agent' => 'UseClick-WordPress/' . USECLICK_VERSION, 'headers' => array( 'Content-Type' => 'application/json', 'Accept' => 'application/json', ), 'body' => wp_json_encode( array( 'code' => $code, 'site' => site_url(), ) ), ) ); if ( is_wp_error( $response ) ) { /* translators: %s: error message from the HTTP library */ self::connect_failed( sprintf( __( 'Could not reach UseClick.io: %s', 'useclick' ), $response->get_error_message() ) ); } $body = json_decode( wp_remote_retrieve_body( $response ), true ); $key = is_array( $body ) && isset( $body['api_key'] ) ? (string) $body['api_key'] : ''; if ( ! preg_match( '/^uc_live_[A-Za-z0-9_-]{8,}$/', $key ) ) { $message = is_array( $body ) && isset( $body['error']['message'] ) ? (string) $body['error']['message'] : __( 'UseClick.io did not return an API key. Please try again.', 'useclick' ); self::connect_failed( $message ); } $api = new UseClick_API( $key ); $result = $api->verify_key(); UseClick_API::flush_cache(); update_option( 'useclick_api_key', $key, false ); if ( ! is_wp_error( $result ) ) { set_transient( UseClick_API::ACCOUNT_CACHE_PREFIX . md5( $key ), $result, 5 * MINUTE_IN_SECONDS ); } self::release_foreign_site( $api ); self::after_connect(); } /** * Right after connecting: start website analytics for this site, then show its reports. */ private static function after_connect() { $started = UseClick_Analytics::auto_start(); UseClick_Admin::set_flash( 'useclick-analytics', $started ? 'connected' : 'connected_no_tracking' ); wp_safe_redirect( admin_url( 'admin.php?page=useclick-analytics' ) ); exit; } /** * Back to Settings with an error message for one page view. * * @param string $message Message. */ private static function connect_failed( $message ) { set_transient( 'useclick_connect_error_' . get_current_user_id(), $message, 5 * MINUTE_IN_SECONDS ); wp_safe_redirect( self::page_url( 'connect_failed' ) ); exit; } /** * "Connect your account" notice, only on the Plugins screen, until the plugin is connected * or the notice is dismissed (per user, for good). */ public static function setup_notice() { if ( UseClick_Admin::has_api_key() || ! current_user_can( UseClick_Admin::capability() ) ) { return; } $screen = get_current_screen(); if ( ! $screen || 'plugins' !== $screen->id ) { return; } if ( get_user_meta( get_current_user_id(), 'useclick_setup_dismissed', true ) ) { return; } $dismiss = wp_nonce_url( add_query_arg( 'action', 'useclick_dismiss_setup', admin_url( 'admin-post.php' ) ), 'useclick_dismiss_setup' ); ?>

verify_key(); if ( is_wp_error( $result ) ) { $data = $result->get_error_data(); $status = is_array( $data ) && isset( $data['status'] ) ? (int) $data['status'] : 0; if ( 401 === $status ) { wp_safe_redirect( self::page_url( 'rejected' ) ); exit; } // The API could not be reached or had a temporary error: keep the key, warn the user. UseClick_API::flush_cache(); update_option( 'useclick_api_key', $key, false ); wp_safe_redirect( self::page_url( 'unverified' ) ); exit; } UseClick_API::flush_cache(); update_option( 'useclick_api_key', $key, false ); set_transient( UseClick_API::ACCOUNT_CACHE_PREFIX . md5( $key ), $result, 5 * MINUTE_IN_SECONDS ); self::release_foreign_site( $api ); self::after_connect(); } /** * After a key change: if the saved analytics website is not in the new * account, turn tracking off so this site does not report to a website * the account cannot see. * * @param UseClick_API $api Client for the new key. * @return bool True when tracking was turned off. */ private static function release_foreign_site( UseClick_API $api ) { $site_id = (string) get_option( UseClick_Analytics::OPT_SITE_ID, '' ); if ( '' === $site_id ) { return false; } $websites = $api->get_websites(); if ( is_wp_error( $websites ) || ! isset( $websites['data'] ) || ! is_array( $websites['data'] ) ) { return false; } foreach ( $websites['data'] as $site ) { if ( isset( $site['id'] ) && (string) $site['id'] === $site_id ) { return false; } } $was_enabled = (bool) get_option( UseClick_Analytics::OPT_ENABLED, false ); update_option( UseClick_Analytics::OPT_SITE_ID, '' ); update_option( UseClick_Analytics::OPT_SITE_DOMAIN, '' ); update_option( UseClick_Analytics::OPT_ENABLED, 0 ); return $was_enabled; } /** * Remove the saved key. */ public static function handle_disconnect() { if ( ! current_user_can( UseClick_Admin::capability() ) ) { wp_die( esc_html__( 'You are not allowed to change these settings.', 'useclick' ), 403 ); } check_admin_referer( 'useclick_disconnect' ); UseClick_API::flush_cache(); update_option( 'useclick_api_key', '', false ); wp_safe_redirect( self::page_url( 'disconnected' ) ); exit; } /** * Masked form of a key for display: uc_live_••••abcd. * * @param string $key API key. * @return string */ public static function mask_key( $key ) { $key = (string) $key; if ( strlen( $key ) <= 12 ) { return str_repeat( '•', 8 ); } return substr( $key, 0, 8 ) . str_repeat( '•', 8 ) . substr( $key, -4 ); } /** * Notice for the status flag set by the save/disconnect handlers. * * @return array|null { type, message } */ public static function status_notice() { $status = UseClick_Admin::take_flash( 'useclick-settings' ); $notices = array( 'connect_failed' => array( 'error', '' ), 'connected' => array( 'success', __( 'Connected. Your UseClick.io account is linked to this site.', 'useclick' ) ), 'disconnected' => array( 'success', __( 'Disconnected. The API key was removed from this site. Links you created stay in your UseClick.io account.', 'useclick' ) ), 'empty' => array( 'error', __( 'Paste an API key to connect.', 'useclick' ) ), 'format' => array( 'error', __( 'That does not look like a UseClick.io API key. Keys start with uc_live_.', 'useclick' ) ), 'rejected' => array( 'error', __( 'UseClick.io did not accept this API key, so it was not saved. Check that it is correct and still active.', 'useclick' ) ), 'unverified' => array( 'warning', __( 'The key was saved, but UseClick.io could not be reached to check it. The connection status updates once the API responds.', 'useclick' ) ), ); if ( ! isset( $notices[ $status ] ) ) { return null; } if ( 'connect_failed' === $status ) { $message = (string) get_transient( 'useclick_connect_error_' . get_current_user_id() ); delete_transient( 'useclick_connect_error_' . get_current_user_id() ); $notices[ $status ][1] = '' !== $message ? $message : __( 'Could not connect to UseClick.io. Please try again.', 'useclick' ); } return array( 'type' => $notices[ $status ][0], 'message' => $notices[ $status ][1], ); } /** * Render the settings page. */ public static function render_page() { if ( ! current_user_can( UseClick_Admin::capability() ) ) { return; } include USECLICK_PLUGIN_DIR . 'templates/settings.php'; } }