403 ) ); } ); } /** Fixed goal copy; recommendations do not enable Pro services or purchase anything. */ public static function goals(): array { return array( 'balanced' => array( 'title' => __( 'Understand my site', 'logged-ai' ), 'description' => __( 'A clear picture of the changes that matter.', 'logged-ai' ), 'icon' => 'overview', 'pro_title' => __( 'Keep the investigation ready for the next reviewer.', 'logged-ai' ), 'pro_intro' => __( 'Bring related evidence, notes and decisions together instead of rebuilding the story each time.', 'logged-ai' ), 'workflow' => 'investigate', 'benefits' => array( __( 'Connected cases', 'logged-ai' ) => __( 'Bring related observations into one review.', 'logged-ai' ), __( 'Explained priorities', 'logged-ai' ) => __( 'See what contributed to a stored ranking.', 'logged-ai' ), __( 'Saved searches', 'logged-ai' ) => __( 'Keep a useful view ready for next time.', 'logged-ai' ) ) ), 'security' => array( 'title' => __( 'Review access & changes', 'logged-ai' ), 'description' => __( 'Understand sensitive changes and who made them.', 'logged-ai' ), 'icon' => 'lock', 'pro_title' => __( 'Send selected access alerts to your team.', 'logged-ai' ), 'pro_intro' => __( 'Configure supported rules and an email, Slack or webhook destination, then verify delivery.', 'logged-ai' ), 'workflow' => 'alert', 'benefits' => array( __( 'Explained priorities', 'logged-ai' ) => __( 'Inspect the reasons behind experimental local scores.', 'logged-ai' ), __( 'Investigation cases', 'logged-ai' ) => __( 'Review a sequence and record a decision.', 'logged-ai' ), __( 'Advanced alerts', 'logged-ai' ) => __( 'Configure focused rules and delivery destinations.', 'logged-ai' ) ) ), 'troubleshooting' => array( 'title' => __( 'Find what changed', 'logged-ai' ), 'description' => __( 'Trace changes around a problem or an update.', 'logged-ai' ), 'icon' => 'activity', 'pro_title' => __( 'Hand over the review with its evidence.', 'logged-ai' ), 'pro_intro' => __( 'Keep the related changes, your notes and the decision in one case so another reviewer can continue.', 'logged-ai' ), 'workflow' => 'investigate', 'benefits' => array( __( 'Connected evidence', 'logged-ai' ) => __( 'Review supported associations without assuming causality.', 'logged-ai' ), __( 'Case history', 'logged-ai' ) => __( 'Keep the evidence and your decisions together.', 'logged-ai' ), __( 'Saved searches', 'logged-ai' ) => __( 'Return to the same diagnostic view.', 'logged-ai' ) ) ), 'woocommerce' => array( 'title' => __( 'Keep my store accountable', 'logged-ai' ), 'description' => __( 'Review supported order and staff activity.', 'logged-ai' ), 'icon' => 'requests', 'pro_title' => __( 'Make store reviews easier to repeat.', 'logged-ai' ), 'pro_intro' => __( 'Bring supported commerce activity into an investigation or a regular report.', 'logged-ai' ), 'workflow' => 'report', 'benefits' => array( __( 'Commerce cases', 'logged-ai' ) => __( 'Review supported staff-refund patterns in context.', 'logged-ai' ), __( 'Scheduled reports', 'logged-ai' ) => __( 'Build a repeatable review of retained evidence.', 'logged-ai' ), __( 'Local summaries', 'logged-ai' ) => __( 'Get a structured account of the selected records.', 'logged-ai' ) ) ), 'agency' => array( 'title' => __( 'Manage client websites', 'logged-ai' ), 'description' => __( 'Keep consistent evidence and repeatable reviews.', 'logged-ai' ), 'icon' => 'reports', 'pro_title' => __( 'Stop rebuilding the same client report.', 'logged-ai' ), 'pro_intro' => __( 'Save your review filters and generate private reports on a configured schedule.', 'logged-ai' ), 'workflow' => 'report', 'benefits' => array( __( 'Saved searches', 'logged-ai' ) => __( 'Reuse the views that make your reviews useful.', 'logged-ai' ), __( 'Scheduled reports', 'logged-ai' ) => __( 'Generate private reports on a configured schedule.', 'logged-ai' ), __( 'Advanced alerts', 'logged-ai' ) => __( 'Route selected findings to configured destinations.', 'logged-ai' ) ) ), ); } /** Strict finite setup protocol; no implicit retention or collection changes. */ public static function proposal( array $input ): array|\WP_Error { $allowed = array( 'action', '_wpnonce', '_wp_http_referer', 'complete', 'goal', 'experience', 'appearance', 'plan', 'revision', 'settings_revision', 'confirmed', 'confirm_shorter' ); if ( array_diff( array_keys( $input ), $allowed ) || '1' !== ( $input['complete'] ?? null ) ) { return new \WP_Error( 'input', 'Incomplete or unsupported setup form.' ); } foreach ( array( 'goal' => array( 'balanced', 'security', 'troubleshooting', 'woocommerce', 'agency' ), 'experience' => array( 'essentials', 'operator', 'expert' ), 'appearance' => array( 'light', 'dark' ), 'plan' => array( 'keep', 'apply' ) ) as $field => $values ) { if ( ! is_string( $input[ $field ] ?? null ) || ! in_array( $input[ $field ], $values, true ) ) { return new \WP_Error( 'input', 'Choose a supported ' . $field . '.' ); } } foreach ( array( 'revision', 'settings_revision' ) as $field ) { if ( ! is_string( $input[ $field ] ?? null ) || ! preg_match( '/\A[1-9][0-9]{0,9}\z/D', $input[ $field ] ) || (int) $input[ $field ] >= 2147483647 ) { return new \WP_Error( 'input', 'Reload the current settings before completing setup.' ); } } foreach ( array( 'confirmed', 'confirm_shorter' ) as $field ) { if ( isset( $input[ $field ] ) && '1' !== $input[ $field ] ) { return new \WP_Error( 'input', 'Use the displayed confirmation controls.' ); } } if ( 'apply' === $input['plan'] && '1' !== ( $input['confirmed'] ?? null ) ) { return new \WP_Error( 'input', 'Review and confirm the collection changes before applying this preset.' ); } return array_intersect_key( $input, array_fill_keys( array( 'goal', 'experience', 'appearance', 'plan', 'revision', 'settings_revision', 'confirmed', 'confirm_shorter' ), true ) ); } /** The only setup write delegates to Collection's revision-bound, audited writer. */ public static function apply( Scope $scope, array $proposal ): array|\WP_Error { if ( ! $scope->valid_for( 'logged_ai_manage_settings' ) ) { return new \WP_Error( 'forbidden', 'Setup access changed.', array( 'status' => 403 ) ); } $validated = self::proposal( $proposal + array( 'complete' => '1' ) ); if ( is_wp_error( $validated ) ) { return $validated; } if ( 'keep' === $validated['plan'] ) { return $validated; } $result = Collection::apply_preset( $scope, $validated['goal'], (int) $validated['revision'], (int) $validated['settings_revision'], true, '1' === ( $validated['confirm_shorter'] ?? null ) ); return is_wp_error( $result ) ? $result : $validated; } private static function receipt_action( Scope $scope, array $choice ): string { return 'logged_ai_setup:' . $scope->network_id . ':' . $scope->site_id . ':' . implode( ':', array( $choice['goal'], $choice['experience'], $choice['appearance'], $choice['plan'] ) ); } /** Preferences apply only after a valid, user/session/site-bound completion receipt. */ public static function receipt( Scope $scope, array $query ): ?array { $choice = array(); foreach ( array( 'goal' => array( 'balanced', 'security', 'troubleshooting', 'woocommerce', 'agency' ), 'experience' => array( 'essentials', 'operator', 'expert' ), 'appearance' => array( 'light', 'dark' ), 'plan' => array( 'keep', 'apply' ) ) as $field => $values ) { $value = $query[ 'setup_' . $field ] ?? null; if ( ! is_string( $value ) || ! in_array( $value, $values, true ) ) { return null; } $choice[ $field ] = $value; } $nonce = $query['_setup_nonce'] ?? null; if ( ! is_string( $nonce ) || '' === $nonce || strlen( $nonce ) > 128 ) { return null; } // The query is unslashed once at page(); do not repair altered receipt bytes. $clean_nonce = sanitize_text_field( $nonce ); return $clean_nonce === $nonce && wp_verify_nonce( $clean_nonce, self::receipt_action( $scope, $choice ) ) ? $choice : null; } public static function submit(): void { $request_form = \LoggedAI\AdminInput::form(); $scope = Scope::current( 'logged_ai_manage_settings' ); if ( is_wp_error( $scope ) || 'POST' !== ( isset( $_SERVER['REQUEST_METHOD'] ) && is_string( $_SERVER['REQUEST_METHOD'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '' ) ) { wp_die( esc_html__( 'Setup access denied.', 'logged-ai' ), '', array( 'response' => 403 ) ); } check_admin_referer( 'logged_ai_setup' ); $proposal = self::proposal( $request_form ); $result = is_wp_error( $proposal ) ? $proposal : self::apply( $scope, $proposal ); if ( is_wp_error( $result ) ) { wp_die( esc_html( PresentationTranslations::text( $result->get_error_message() ) ), esc_html__( 'Setup needs a review', 'logged-ai' ), array( 'response' => (int) ( $result->get_error_data()['status'] ?? 400 ), 'back_link' => true ) ); } if ( ! $scope->valid_for( 'logged_ai_manage_settings' ) ) { wp_die( esc_html__( 'Setup access changed. Reload the current settings.', 'logged-ai' ), '', array( 'response' => 403 ) ); } $choice = array(); $query = array( 'page' => 'logged-ai-setup' ); foreach ( array( 'goal', 'experience', 'appearance', 'plan' ) as $field ) { $choice[ $field ] = $result[ $field ]; $query[ 'setup_' . $field ] = $result[ $field ]; } $query['_setup_nonce'] = wp_create_nonce( self::receipt_action( $scope, $choice ) ); wp_safe_redirect( add_query_arg( $query, admin_url( 'admin.php' ) ) ); exit; } public static function page(): void { $request_query = \LoggedAI\AdminInput::query(); $scope = Scope::current( 'logged_ai_manage_settings' ); if ( is_wp_error( $scope ) ) { wp_die( esc_html__( 'Setup access denied.', 'logged-ai' ), '', array( 'response' => 403 ) ); } nocache_headers(); $collection = Collection::get(); if ( is_wp_error( $collection ) ) { echo ( '

' . esc_html__( 'Let us check the current settings first.', 'logged-ai' ) . '

' ) . esc_html( PresentationTranslations::text( $collection->get_error_message() ) ) . '

' . esc_html__( 'Review collection settings', 'logged-ai' ) . '
' ); return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only setup receipt; receipt() verifies the nonce against the current scope and every allowlisted choice before rendering confirmation. self::render( $collection, Settings::get(), defined( 'LOGGED_AI_PRO_FILE' ), self::receipt( $scope, $request_query ) ); } /** Rendered separately so previews and checks exercise the native wizard itself. */ public static function render( array $collection, array $settings, bool $pro, ?array $receipt = null ): void { $goals = self::goals(); $config = array( 'goals' => $goals, 'presets' => Collection::presets(), 'current' => array( 'preset' => $collection['preset'], 'disabled' => $collection['disabled'], 'days' => $settings['event_days'] ), 'pro' => $pro ); echo '
'; if ( null !== $receipt ) { self::complete( $collection, $settings, $pro, $receipt ); echo '
'; return; } echo ( '

' . esc_html__( 'WELCOME TO YOUR NEXT CLEARER MOMENT', 'logged-ai' ) . '

' . esc_html__( 'A little setup.', 'logged-ai' ) . '
' . esc_html__( 'A lot more clarity.', 'logged-ai' ) . '

' . esc_html__( 'Make Logged.ai feel like it was built for you.', 'logged-ai' ) . '

' ) . wp_kses( Design::icon( 'lock' ), Design::allowed_markup() ) . ( '' . esc_html__( 'On your site.', 'logged-ai' ) . '
' . esc_html__( 'On your terms.', 'logged-ai' ) . '
' ); echo '
'; wp_nonce_field( 'logged_ai_setup' ); echo ( '
'; echo ( '
' . esc_html__( '01 / THE FOCUS', 'logged-ai' ) . '

' . esc_html__( 'What brings you here?', 'logged-ai' ) . '

' . esc_html__( 'Choose the work you want to make easier. You can revisit this anytime.', 'logged-ai' ) . '

' . esc_html__( 'Your main goal', 'logged-ai' ) . '' ); foreach ( $goals as $key => $goal ) { echo ''; } echo ( '

' . esc_html__( 'This personalises the recommendations. Collection changes are a separate choice on the final step.', 'logged-ai' ) . '

' ); echo ( '
' . esc_html__( '02 / THE FEEL', 'logged-ai' ) . '

' . esc_html__( 'How deep do you like to go?', 'logged-ai' ) . '

' . esc_html__( 'Same evidence. A different amount of detail. Watch the preview change.', 'logged-ai' ) . '

' . esc_html__( 'Interface experience', 'logged-ai' ) . '' ); foreach ( array( 'essentials' => array( __( 'Essentials', 'logged-ai' ), __( 'Keep it simple.', 'logged-ai' ), __( 'Readable event stories and one step at a time.', 'logged-ai' ) ), 'operator' => array( __( 'Operator', 'logged-ai' ), __( 'Give me the controls.', 'logged-ai' ), __( 'Working tables, charts and expanded filters.', 'logged-ai' ) ), 'expert' => array( __( 'Expert', 'logged-ai' ), __( 'Show me the evidence.', 'logged-ai' ), __( 'Source codes, precise timestamps and diagnostics.', 'logged-ai' ) ) ) as $mode => $copy ) { echo ''; } echo ( '
' . esc_html__( 'YOUR INTERFACE PREVIEW', 'logged-ai' ) . '' . esc_html__( 'Illustrative sample', 'logged-ai' ) . '
' ) . wp_kses( Design::icon( 'lock' ), Design::allowed_markup() ) . ( '
' . esc_html__( 'A recorded change', 'logged-ai' ) . '' . esc_html__( 'User permissions changed', 'logged-ai' ) . '

' . esc_html__( 'Confirm that this access change was intentional.', 'logged-ai' ) . '

→
' . esc_html__( 'All changes', 'logged-ai' ) . '' . esc_html__( 'Category: Users', 'logged-ai' ) . '' . esc_html__( 'Filter +', 'logged-ai' ) . '
' . esc_html__( 'Event', 'logged-ai' ) . '' . esc_html__( 'Actor', 'logged-ai' ) . '' . esc_html__( 'Result', 'logged-ai' ) . '' . esc_html__( 'User permissions changed', 'logged-ai' ) . '' . esc_html__( 'User #7', 'logged-ai' ) . '' . esc_html__( 'Review', 'logged-ai' ) . '
' . 'identity.roles_changed' . '' . 'actor_id: 7   subject_id: 12' . '' . 'outcome: stored_change' . '' . 'occurred_at: 2026-10-01 11:45:00.000000 UTC' . '' . 'roles: editor → administrator' . '
' . esc_html__( 'Make it feel right.', 'logged-ai' ) . '' . esc_html__( 'Appearance and experience stay in this browser.', 'logged-ai' ) . '
' ); echo ( '
' . esc_html__( '03 / YOUR TOOLKIT', 'logged-ai' ) . '

' . esc_html__( 'A good fit beats more features.', 'logged-ai' ) . '

' . esc_html__( 'Here is what fits the way you want to work.', 'logged-ai' ) . '

' ) . wp_kses( Design::icon( 'overview' ), Design::allowed_markup() ) . ( '
' . esc_html__( 'Your foundation is already included.', 'logged-ai' ) . '

' . esc_html__( 'Activity history, the live map, sample analysis, private exports and critical security and failure notices.', 'logged-ai' ) . '

' . esc_html__( 'FREE', 'logged-ai' ) . '
' ) . ( $pro ? esc_html__( 'YOUR PRO TOOLKIT', 'logged-ai' ) : esc_html__( 'RECOMMENDED FOR YOUR GOAL / PRO', 'logged-ai' ) ) . ( '

' . esc_html__( 'Keep the investigation ready for the next reviewer.', 'logged-ai' ) . '

' . esc_html__( 'Keep evidence and decisions together, configure team alerts and repeat useful reports.', 'logged-ai' ) . '

' . ( $pro ? ( esc_html__( 'Pro add-on detected.', 'logged-ai' ) . '
' . esc_html__( 'Configure these tools after setup.', 'logged-ai' ) ) : ( esc_html__( 'Keep going with Free.', 'logged-ai' ) . '
' . esc_html__( 'Upgrade when this work becomes routine.', 'logged-ai' ) ) ) . ( '

' . esc_html__( 'These are recommended workflows. Setup does not create cases, schedules, alert destinations or response policies.', 'logged-ai' ) . '

' ); echo ( '
' . esc_html__( '04 / READY WHEN YOU ARE', 'logged-ai' ) . '

' . esc_html__( 'Your workspace. Your call.', 'logged-ai' ) . '

' . esc_html__( 'Check the choices below. Nothing changes on the site until you submit.', 'logged-ai' ) . '

' . esc_html__( 'Collection settings', 'logged-ai' ) . '

' . esc_html__( 'What will change', 'logged-ai' ) . '

' . esc_html__( 'Setting', 'logged-ai' ) . '' . esc_html__( 'Current', 'logged-ai' ) . '' . esc_html__( 'Proposed', 'logged-ai' ) . '
' . esc_html__( 'Administrative retention', 'logged-ai' ) . '' ) . esc_html( sprintf( /* translators: %d: number of days. */ _n( '%d day', '%d days', $settings['event_days'], 'logged-ai' ), $settings['event_days'] ) ) . ( '' . '' . esc_html__( 'See the preset reference below.', 'logged-ai' ) . '
' . esc_html__( 'Excluded new events', 'logged-ai' ) . '' ) . esc_html( $collection['disabled'] ? implode( ', ', $collection['disabled'] ) : __( 'None', 'logged-ai' ) ) . ( '' . esc_html__( 'See the preset reference below.', 'logged-ai' ) . '

' . esc_html__( 'Identity, configuration and privacy collection stay enabled. Excluded future events cannot be recovered later.', 'logged-ai' ) . '

' . esc_html__( 'Presets affect administrative retention and collection categories only. IP privacy, other retention, alerts, response policies and maintenance labels retain their current settings.', 'logged-ai' ) . '

' ); echo ( '' ); echo ( '
' . esc_html__( 'Do this later', 'logged-ai' ) . '' . esc_html__( 'Step 1 of 4', 'logged-ai' ) . '
' ); echo '
' ); } private static function complete( array $collection, array $settings, bool $pro, array $receipt ): void { echo '
' . wp_kses( Design::mark(), Design::allowed_markup() ) . ( '

' . esc_html__( 'MADE TO FEEL LIKE YOURS', 'logged-ai' ) . '

' . wp_kses( __( 'Welcome to
your clearer picture.', 'logged-ai' ), array( 'br' => array(), 'em' => array() ) ) . '

' . esc_html( sprintf( /* translators: %s: selected display detail level. */ __( 'Your %s workspace is ready to explore.', 'logged-ai' ), array( 'essentials' => __( 'Essentials', 'logged-ai' ), 'operator' => __( 'Operator', 'logged-ai' ), 'expert' => __( 'Expert', 'logged-ai' ) )[ $receipt['experience'] ] ) ) . '

' . esc_html__( 'YOUR FOCUS', 'logged-ai' ) . '' ) . esc_html( self::goals()[ $receipt['goal'] ]['title'] ) . ( '
' . esc_html__( 'CURRENT RETENTION', 'logged-ai' ) . '' ) . esc_html( sprintf( /* translators: %d: number of days. */ _n( '%d day', '%d days', $settings['event_days'], 'logged-ai' ), $settings['event_days'] ) ) . ( '' . '
' . esc_html__( 'EXCLUDED NEW EVENTS', 'logged-ai' ) . '' ) . esc_html( $collection['disabled'] ? implode( ', ', $collection['disabled'] ) : __( 'None', 'logged-ai' ) ) . '
' . esc_html__( 'Open my workspace', 'logged-ai' ) . ' ' ) . wp_kses( Design::icon( 'arrow' ), Design::allowed_markup() ) . '' . esc_html__( 'Revisit setup', 'logged-ai' ) . '

' . esc_html__( 'Next, open a real recorded change and review its evidence. This setup is not a security scan.', 'logged-ai' ) . '

' ); } }