id, 'citolume-wizard' ) ) { $css .= '#adminmenumain,#adminmenuback,#adminmenuwrap,#wpfooter,#screen-meta-links{display:none}#wpcontent,#wpfooter{margin-left:0}'; } wp_register_style( 'citolume-menu', false, array(), CITOLUME_VERSION ); wp_enqueue_style( 'citolume-menu' ); wp_add_inline_style( 'citolume-menu', $css ); } add_action( 'admin_init', 'citolume_maybe_redirect_to_wizard' ); /** * First-activation redirect: once, never on update, never during bulk * activation, and never again after the wizard was seen or skipped. * * @return void */ function citolume_maybe_redirect_to_wizard() { $activated = (int) get_option( 'citolume_activation_redirect', 0 ); if ( ! $activated ) { return; } // Not consumed by a background request (heartbeat, AJAX activation) or // by somebody who cannot run the wizard — the next real admin page load // of an administrator gets it. if ( wp_doing_ajax() || wp_doing_cron() || is_network_admin() || ! current_user_can( 'manage_options' ) ) { return; } delete_option( 'citolume_activation_redirect' ); // Stale after a day: an activation nobody followed up is not a reason to // hijack some later, unrelated visit to wp-admin. if ( time() - $activated > DAY_IN_SECONDS ) { return; } if ( get_option( 'citolume_wizard_done' ) || '' !== citolume_license_key() ) { return; } wp_safe_redirect( admin_url( 'admin.php?page=citolume-wizard' ) ); exit; } add_action( 'admin_init', 'citolume_handle_wizard_post' ); /** * The wizard's three mutations: connect, save questions, skip. * * @return void */ function citolume_handle_wizard_post() { if ( ! isset( $_POST['citolume_wizard_action'] ) || ! current_user_can( 'manage_options' ) ) { return; } check_admin_referer( 'citolume_wizard' ); $action = sanitize_key( wp_unslash( $_POST['citolume_wizard_action'] ) ); if ( 'connect' === $action ) { // This click is the consent (§8). The first outbound call happens here. // // The address is read back from the form, not from the session, so // what gets sent is exactly what the person could see and change on // the step before. An empty field is a deliberate opt-out and // registers the site with no account, as it always did. $owner = array(); $email = isset( $_POST['citolume_owner_email'] ) ? sanitize_email( wp_unslash( $_POST['citolume_owner_email'] ) ) : ''; if ( '' !== $email && is_email( $email ) ) { $user = wp_get_current_user(); $owner = array( 'email' => $email, 'first_name' => $user ? $user->first_name : '', 'last_name' => $user ? $user->last_name : '', ); } $result = citolume_register_free_site( $owner ); if ( is_wp_error( $result ) ) { add_settings_error( 'citolume', 'citolume_connect', $result->get_error_message() ); return; } // A reinstall reconnected to its kept site (ticket 146): the questions // and the first scan already happened, so the wizard is finished and // the dashboard — with the history — is the next thing to see. if ( is_array( $result ) && ! empty( $result['reconnected'] ) ) { update_option( 'citolume_wizard_done', 1, false ); set_transient( 'citolume_reconnected', $result, 10 * MINUTE_IN_SECONDS ); wp_safe_redirect( admin_url( 'admin.php?page=citolume' ) ); exit; } // What the service did with the address, said once, on the next step. if ( is_array( $result ) && 'none' !== ( $result['owner_status'] ?? 'none' ) ) { set_transient( 'citolume_owner_outcome', $result, 10 * MINUTE_IN_SECONDS ); } wp_safe_redirect( admin_url( 'admin.php?page=citolume-wizard&step=3' ) ); exit; } if ( 'questions' === $action ) { $picked = isset( $_POST['citolume_wizard_questions'] ) ? (array) wp_unslash( $_POST['citolume_wizard_questions'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- each item sanitised below. $custom = isset( $_POST['citolume_wizard_custom'] ) ? sanitize_text_field( wp_unslash( $_POST['citolume_wizard_custom'] ) ) : ''; if ( '' !== $custom ) { $picked[] = $custom; } // More than the plan has room for is refused whole, with the reason, // rather than saving the first few and dropping the rest unseen. $picked = array_values( array_filter( array_map( 'sanitize_text_field', $picked ) ) ); $status = citolume_site_status( true ); $room = citolume_wizard_room( $status ); if ( count( $picked ) > $room ) { add_settings_error( 'citolume', 'citolume_wizard_questions', sprintf( /* translators: 1: questions chosen, 2: how many the plan has room for. */ _n( 'You chose %1$d questions, but your plan has room for %2$d. Untick some and save again.', 'You chose %1$d questions, but your plan has room for %2$d. Untick some and save again.', $room, 'citolume' ), count( $picked ), $room ) ); return; } $added = 0; foreach ( $picked as $text ) { $text = sanitize_text_field( $text ); if ( '' === $text ) { continue; } $result = citolume_add_query( $text ); if ( ! is_wp_error( $result ) ) { ++$added; } elseif ( 'citolume_http_error' === $result->get_error_code() && false !== strpos( $result->get_error_message(), 'Upgrade' ) ) { // The plan is full — everything up to here stuck; stop quietly. break; } } if ( $added > 0 ) { citolume_clear_status_cache(); // The instant first scan — once per this domain, ever. The API // holds the latch (a reinstall must not re-scan); its answer is // stored so the Done step tells the truth either way. An error // is stored as its own state: rendering it as "already scanned" // would be a false statement on a fresh domain whose scan simply // failed to start — the schedule catches those. $scan = citolume_start_first_scan(); update_option( 'citolume_first_scan_outcome', is_array( $scan ) ? $scan : array( 'error' => true ), false ); } else { // Nothing new was added (a re-run wizard whose questions already // exist) — no scan was requested, so no stale "queued" copy. update_option( 'citolume_first_scan_outcome', array( 'scanning' => false ), false ); } wp_safe_redirect( admin_url( 'admin.php?page=citolume-wizard&step=4' ) ); exit; } if ( 'skip' === $action ) { update_option( 'citolume_wizard_done', 1, false ); wp_safe_redirect( admin_url( 'admin.php?page=citolume' ) ); exit; } } /** * Renders the wizard shell and the current step. * * @return void */ function citolume_render_wizard() { if ( ! current_user_can( 'manage_options' ) ) { return; } $connected = '' !== citolume_license_key(); $step = isset( $_GET['step'] ) ? max( 1, min( 4, absint( $_GET['step'] ) ) ) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display routing only. // A step that assumes a connection cannot run without one, and vice versa. if ( $step >= 3 && ! $connected ) { $step = 2; } if ( 4 === $step ) { update_option( 'citolume_wizard_done', 1, false ); } $labels = array( 1 => __( 'What this does', 'citolume' ), 2 => __( 'Connect', 'citolume' ), 3 => __( 'Your questions', 'citolume' ), 4 => __( 'Done', 'citolume' ), ); ?>
    $label ) : ?>

·

' . esc_html( $host ) . '' ); ?>

user_email : ''; ?>


|WP_Error|null $status Site status. * @return int */ function citolume_wizard_room( $status ) { $limit = is_array( $status ) ? (int) ( $status['max_queries'] ?? 2 ) : 2; $used = is_array( $status ) ? (int) ( $status['queries_used'] ?? 0 ) : 0; return max( 0, $limit - $used ); } /** * Step 3: choose questions — never an empty box, hard-to-win flagged inline. * * @return void */ function citolume_render_wizard_step_questions() { $status = citolume_site_status( true ); $limit = is_array( $status ) ? (int) ( $status['max_queries'] ?? 2 ) : 2; $room = citolume_wizard_room( $status ); $suggestions = citolume_question_suggestions( 4 ); // The shape check shares its vocabulary with the API (ticket 007); a dead // service degrades to no flags, never to a blocked wizard. $flags = array(); $check = citolume_api_send( '/v1/questions/check', 'POST', array( 'texts' => $suggestions ) ); if ( ! is_wp_error( $check ) ) { foreach ( (array) ( $check['checks'] ?? array() ) as $row ) { if ( ! empty( $row['flagged'] ) ) { $flags[ (string) $row['text'] ] = (string) $row['reason']; } } } $alternative = ''; foreach ( $suggestions as $suggestion ) { if ( ! isset( $flags[ $suggestion ] ) ) { $alternative = $suggestion; break; } } ?>

' . esc_html( (string) $owner['owner_email'] ) . '' ); ?>

' . esc_html( preg_replace( '#^https?://#', '', citolume_site_url() ) ) . '' ); ?>

' . esc_html( $code ) . '', '' . esc_html( preg_replace( '#^https?://#', '', citolume_site_url() ) . '/account/claim' ) . '' ); ?>

' . esc_html__( 'Settings → Connection', 'citolume' ) . '' ); ?>