false, 'error' => __( 'Scheduler not ready.', 'marginbeacon' ), ); $scheduler = Plugin::instance()->scheduler(); if ( $scheduler ) { $result = ( new PreflightJobService( $scheduler ) )->enqueue( $coupon_id ); } if ( empty( $result['success'] ) ) { add_settings_error( 'aksmb_preflight', 'fail', isset( $result['error'] ) ? $result['error'] : __( 'Preflight failed.', 'marginbeacon' ), 'error' ); return; } ( new FreshnessService() )->refresh_summary_cache(); wp_safe_redirect( Tabs::url( Tabs::PREFLIGHT, array( 'scan_id' => (int) $result['scan_id'], 'queued' => 1, ) ) ); exit; } /** * @return void */ public function render() { if ( ! Capabilities::current_user_can_manage() ) { wp_die( esc_html__( 'You do not have permission to access MarginBeacon.', 'marginbeacon' ) ); } settings_errors( 'aksmb_preflight' ); $this->render_flash_notice(); $repo = new FindingsRepository(); $scan_id = isset( $_GET['scan_id'] ) ? absint( $_GET['scan_id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $scan = $scan_id ? $repo->get_scan( $scan_id ) : null; $findings = $scan ? $repo->get_findings_for_scan( $scan_id ) : array(); $recent = $repo->recent_preflight_scans( 10 ); $search = isset( $_GET['aksmb_coupon_s'] ) ? sanitize_text_field( wp_unslash( $_GET['aksmb_coupon_s'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $status = isset( $_GET['aksmb_coupon_status'] ) ? sanitize_key( wp_unslash( $_GET['aksmb_coupon_status'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $type = isset( $_GET['aksmb_coupon_type'] ) ? sanitize_key( wp_unslash( $_GET['aksmb_coupon_type'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( strlen( $search ) > 64 ) { $search = substr( $search, 0, 64 ); } $inventory = $this->build_inventory( $repo, $search, $status, $type ); $coupons = array(); foreach ( $inventory as $row ) { if ( 'publish' === $row['status'] ) { $coupons[] = array( 'id' => $row['id'], 'code' => $row['code'], 'type' => $row['type'], 'amount' => $row['amount'], ); } } ?>


— — —

details_from( $f ); $reason = ! empty( $f['rule_id'] ) ? (string) $f['rule_id'] : '—'; if ( ! empty( $details['reason_code'] ) ) { $reason = (string) $details['reason_code']; } $fidelity = ! empty( $details['calculation_fidelity'] ) ? $details['calculation_fidelity'] : '—'; $gp_class = $this->money_polarity_class( isset( $f['gross_profit'] ) ? $f['gross_profit'] : null ); $sev_label = Severity::label( (string) $f['severity'] ); if ( 'safe' === $f['severity'] && ! empty( $details['safe_display_label'] ) ) { $sev_label = (string) $details['safe_display_label']; } elseif ( 'safe' === $f['severity'] && ! empty( $details['shipping_cost_excluded'] ) ) { $sev_label = __( 'Product margin Safe — shipping cost excluded', 'marginbeacon' ); } $fresh_badge = $this->freshness_badge_for_finding( $f ); $fresh = $fresh_badge['key']; $currency = isset( $f['currency'] ) ? (string) $f['currency'] : null; ?>
product_cell_html( $f, $details ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- presenter escapes. ?> coupon_cell_html( $details ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- presenter escapes. ?> coupon_type_cell( $details ) ); ?> coupon_discount_cell( $details, $currency ) ); ?> — /

#
$fresh, 'label' => Freshness::label( $fresh ), ); } /** * Render findings table body HTML for a scan (testable without full admin bootstrap). * * @param array $scan Scan row (unused except for presence checks). * @param array $findings Finding rows. * @return string HTML fragment. */ public function render_findings_rows_html( array $scan, array $findings ) { unset( $scan ); if ( empty( $findings ) ) { return '' . esc_html__( 'No eligible products for this coupon.', 'marginbeacon' ) . ''; } $presenter = new FindingResultPresenter(); $html = ''; foreach ( $findings as $f ) { if ( ! is_array( $f ) ) { continue; } $details = $presenter->details_from( $f ); $badge = $this->freshness_badge_for_finding( $f ); $sev = isset( $f['severity'] ) ? (string) $f['severity'] : 'unknown'; $currency = isset( $f['currency'] ) ? (string) $f['currency'] : null; $html .= '' . '' . $presenter->product_cell_html( $f, $details ) . '' . '' . $presenter->coupon_cell_html( $details ) . '' . '' . esc_html( $presenter->coupon_type_cell( $details ) ) . '' . '' . esc_html( $presenter->coupon_discount_cell( $details, $currency ) ) . '' . '' . esc_html( Severity::label( $sev ) ) . '' . '' . esc_html( $badge['label'] ) . '' . ''; } return $html; } /** * One-shot notice after Pro actions (pairwise redirect). * * @return void */ private function render_flash_notice() { if ( ! isset( $_GET['aksmb_ack'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } $ack = wp_unslash( $_GET['aksmb_ack'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Exact equality gate only. $ok = is_string( $ack ) && '1' === $ack; $msg = isset( $_GET['aksmb_msg'] ) ? sanitize_text_field( rawurldecode( wp_unslash( $_GET['aksmb_msg'] ) ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( '' === $msg ) { return; } $class = $ok ? 'notice-success' : 'notice-error'; printf( '

%2$s

', esc_attr( $class ), esc_html( $msg ) ); } /** * @param string $status Post status. * @return string */ private function coupon_status_label( $status ) { switch ( (string) $status ) { case 'publish': return __( 'Published', 'marginbeacon' ); case 'draft': return __( 'Draft', 'marginbeacon' ); case 'pending': return __( 'Pending', 'marginbeacon' ); case 'private': return __( 'Private', 'marginbeacon' ); default: return (string) $status; } } /** * Native coupon inventory rows for Free (FR-020). * * @param FindingsRepository $repo Findings repo. * @param string $search Code search (bounded). * @param string $status Post status filter. * @param string $type Discount type filter. * @return array> */ private function build_inventory( FindingsRepository $repo, $search, $status, $type ) { $post_status = array( 'publish', 'draft', 'pending', 'private' ); if ( $status && in_array( $status, $post_status, true ) ) { $post_status = array( $status ); } $query = array( 'post_type' => 'shop_coupon', 'post_status' => $post_status, 'posts_per_page' => 100, 'orderby' => 'title', 'order' => 'ASC', 'no_found_rows' => true, 'update_post_meta_cache' => true, 'update_post_term_cache' => false, ); if ( '' !== $search ) { $query['s'] = $search; } $posts = get_posts( $query ); $ids = array(); foreach ( (array) $posts as $post ) { $ids[] = (int) $post->ID; } $latest = $repo->latest_selected_by_coupon_ids( $ids ); $out = array(); foreach ( (array) $posts as $post ) { $c = new \WC_Coupon( (int) $post->ID ); if ( ! $c->get_id() ) { continue; } $discount_type = $c->get_discount_type(); if ( $type && $type !== $discount_type ) { continue; } $expires = $c->get_date_expires(); $expiry = $expires ? $expires->date_i18n( get_option( 'date_format' ) ) : __( 'None', 'marginbeacon' ); $scan_row = isset( $latest[ (int) $post->ID ] ) ? $latest[ (int) $post->ID ] : null; $counts = $scan_row && isset( $scan_row['counts'] ) && is_array( $scan_row['counts'] ) ? $scan_row['counts'] : array(); $fidelity = isset( $counts['calculation_fidelity'] ) ? (string) $counts['calculation_fidelity'] : ''; $affected = isset( $counts['affected'] ) ? (int) $counts['affected'] : ( isset( $counts['processed'] ) ? (int) $counts['processed'] : null ); $out[] = array( 'id' => (int) $post->ID, 'code' => $c->get_code(), 'status' => $post->post_status, 'status_label' => $this->coupon_status_label( $post->post_status ), 'type' => $discount_type, 'amount' => (string) $c->get_amount(), 'expiry' => $expiry, 'scan_id' => $scan_row ? (int) $scan_row['id'] : 0, 'last_preflight' => $scan_row && ! empty( $scan_row['completed_at_gmt'] ) ? (string) $scan_row['completed_at_gmt'] . ' UTC' : ( $scan_row ? (string) $scan_row['status'] : '' ), 'severity' => $scan_row && ! empty( $scan_row['worst_severity'] ) ? (string) $scan_row['worst_severity'] : null, 'freshness' => $scan_row && ! empty( $scan_row['worst_freshness'] ) ? (string) $scan_row['worst_freshness'] : null, 'coverage' => $fidelity ? $fidelity : '—', 'affected' => $affected, ); } return $out; } /** * CSS class for signed money display (gross profit polarity). * * @param string|null $value Decimal string or null. * @return string */ private function money_polarity_class( $value ) { if ( null === $value || '' === $value ) { return 'aksmb-money'; } if ( ! is_numeric( $value ) ) { return 'aksmb-money'; } $cmp = function_exists( 'bccomp' ) ? bccomp( (string) $value, '0', 8 ) : ( preg_match( '/^-/', trim( (string) $value ) ) ? -1 : ( preg_match( '/^[1-9]|^0\.\d*[1-9]/', ltrim( trim( (string) $value ), '0' ) ) ? 1 : 0 ) ); if ( $cmp < 0 ) { return 'aksmb-money aksmb-money--negative'; } if ( $cmp > 0 ) { return 'aksmb-money aksmb-money--positive'; } return 'aksmb-money aksmb-money--zero'; } }