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( PresentationTranslations::text( $collection->get_error_message() ) ) . '
' . esc_html__( 'Review collection settings', 'logged-ai' ) . '' . esc_html__( 'WELCOME TO YOUR NEXT CLEARER MOMENT', 'logged-ai' ) . '
' . esc_html__( 'Make Logged.ai feel like it was built for you.', 'logged-ai' ) . '
' . esc_html__( 'MADE TO FEEL LIKE YOURS', 'logged-ai' ) . '
' . 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__( 'Next, open a real recorded change and review its evidence. This setup is not a security scan.', 'logged-ai' ) . '