label/value. * @var array $order_histories Related order relation rows. * @var string $list_url Subscriptions list URL. * @var string $form_action URL the status form posts to. */ if ( ! defined( 'ABSPATH' ) ) { exit; } use SpringDevs\Subscription\Illuminate\Helper; $product_name = $order_item ? $order_item->get_name() : '-'; $product_id = $order_item ? $order_item->get_product_id() : 0; $product_link = $product_id ? get_the_permalink( $product_id ) : ''; $product_obj = $product_id ? wc_get_product( $product_id ) : null; $product_image = $product_obj ? $product_obj->get_image( 'thumbnail', array( 'class' => 'subscrpt-plan-img' ) ) : ''; $subscrpt_status = $subscription_data['status'] ?? get_post_status( $subscription_id ); $verbose_status = Helper::get_verbose_status( $subscrpt_status ); $badge_mod_map = array( 'active' => 'active', 'pending' => 'pending', 'pe_cancelled' => 'pending-cancel', 'cancelled' => 'cancelled', 'expired' => 'expired', 'completed' => 'completed', 'draft' => 'draft', 'trash' => 'trash', ); $badge_mod = $badge_mod_map[ $subscrpt_status ] ?? 'expired'; $is_grace_period = isset( $subscription_data['grace_period'] ); $grace_remaining = $subscription_data['grace_period']['remaining_days'] ?? 0; $grace_end_date = $subscription_data['grace_period']['end_date'] ?? ''; $grace_end_date = ! empty( $grace_end_date ) ? wp_date( get_option( 'date_format' ) . ' - ' . get_option( 'time_format' ), strtotime( $grace_end_date ) ) : ''; if ( $is_grace_period && $grace_remaining > 0 ) { $badge_mod = 'active'; $verbose_status = __( 'Active', 'subscription' ); } $customer_id = $order ? $order->get_customer_id() : 0; $customer_name = $order ? $order->get_formatted_billing_full_name() : '-'; $customer_email = $order ? $order->get_billing_email() : ''; $customer_phone = $order ? $order->get_billing_phone() : ''; $customer_url = $customer_id ? admin_url( 'user-edit.php?user_id=' . $customer_id ) : ''; // Page header: product title + meta description (badge · #id · email · next payment). $header_title = ( $product_name && '-' !== $product_name ) ? $product_name /* translators: %s: Subscription ID */ : sprintf( __( 'Subscription #%s', 'subscription' ), $subscription_id ); $next_payment_date = $subscription_data['next_date'] ?? ''; $next_payment_date = ! empty( $next_payment_date ) && Helper::has_next_payment( $subscription_data['status'] ?? '' ) ? wp_date( get_option( 'date_format' ), strtotime( $next_payment_date ) ) : ''; $header_badge = '' . esc_html( $verbose_status ); if ( $is_grace_period && $grace_remaining > 0 ) { /* translators: %d: days remaining */ $grace_title = sprintf( __( '%d days remaining in grace period', 'subscription' ), $grace_remaining ); $header_badge .= ' '; } $header_badge .= ''; $header_segments = array( $header_badge, '#' . (int) $subscription_id, ); if ( $customer_email ) { $header_segments[] = esc_html( $customer_email ); } if ( $next_payment_date ) { /* translators: %s: next payment date */ $header_segments[] = esc_html( sprintf( __( 'Next payment on %s', 'subscription' ), $next_payment_date ) ); } $header_desc_html = implode( '·', $header_segments ); $rows = is_array( $rows ) ? $rows : array(); // Top summary tiles — pulled from the info rows so Pro's filtered values stay // in sync. Their keys are marked used so they are not repeated below. $summary_tiles = array(); if ( $order && isset( $subscription_data['price'] ) && '' !== $subscription_data['price'] ) { // Build the recurring price from structured data so the price and period can // be styled separately (price prominent, "/ period" small and muted). $timing_per = (int) ( $subscription_data['schedule']['timing_per'] ?? 1 ); $timing_option = $subscription_data['schedule']['timing_option'] ?? ''; $period_label = ''; if ( $timing_option ) { $timing_unit = ucfirst( Helper::get_typos( $timing_per, $timing_option ) ); $period_label = $timing_per > 1 ? $timing_per . ' ' . $timing_unit : $timing_unit; } // Net of any discount that carries into renewals, with the original struck through. $recurring_totals = Helper::get_subscription_display_totals( $subscription_id, $order_item ); $recurring_full = $recurring_totals['full_excl'] + $recurring_totals['tax'] + $recurring_totals['discount_tax']; $recurring_value = wc_price( (float) $recurring_totals['total'], array( 'currency' => $order->get_currency() ) ); if ( $recurring_totals['has_discount'] ) { $recurring_value = ' ' . $recurring_value; } if ( $period_label ) { $recurring_value .= ' / ' . esc_html( $period_label ) . ''; } $summary_tiles[] = array( 'label' => __( 'Recurring', 'subscription' ), 'value' => $recurring_value, ); } if ( ! empty( $subscription_data['start_date'] ) ) { $summary_tiles[] = array( 'label' => __( 'Started', 'subscription' ), 'value' => esc_html( wp_date( get_option( 'date_format' ), strtotime( $subscription_data['start_date'] ) ) ), ); } // Always show the Next Payment tile; dash when there is no next date. $summary_tiles[] = array( 'label' => __( 'Next Payment', 'subscription' ), 'value' => $next_payment_date ? esc_html( $next_payment_date ) : '-', ); // Total payments is not part of $subscription_data; pull it from the info rows. if ( isset( $rows['total_payments'] ) ) { $summary_tiles[] = array( 'label' => __( 'Total Payments', 'subscription' ), 'value' => $rows['total_payments']['value'], ); } // Group the remaining info rows into cards. Unmapped keys — including rows added // through the subscrpt_admin_info_rows filter (Pro) — fall into "Additional // Details" so nothing is dropped. Status is shown as the header badge. $used_keys = array( 'cost' => true, 'start_date' => true, 'next_date' => true, 'total_payments' => true, 'status' => true, ); // Plan card: product + qty render side by side; trial/signup fee as extra rows. $plan_product = isset( $rows['product'] ) ? $rows['product']['value'] : ''; $plan_qty = isset( $rows['quantity'] ) ? $rows['quantity']['value'] : ''; $plan_label = function_exists( 'subscrpt_get_subscription_plan_label' ) ? subscrpt_get_subscription_plan_label( $subscription_id ) : ''; $plan_extra = array(); foreach ( array( 'product', 'quantity', 'signup_fee', 'trial', 'trial_period' ) as $plan_key ) { $used_keys[ $plan_key ] = true; if ( in_array( $plan_key, array( 'signup_fee', 'trial', 'trial_period' ), true ) && isset( $rows[ $plan_key ] ) ) { $plan_extra[ $plan_key ] = $rows[ $plan_key ]; } } $build_sections = function ( array $map ) use ( $rows, &$used_keys ) { $out = array(); foreach ( $map as $section ) { $section_rows = array(); foreach ( $section['keys'] as $key ) { if ( isset( $rows[ $key ] ) ) { $section_rows[ $key ] = $rows[ $key ]; $used_keys[ $key ] = true; } } if ( ! empty( $section_rows ) ) { $out[] = array( 'title' => $section['title'], 'rows' => $section_rows, ); } } return $out; }; // Payment Method renders next to the Plan card (main column). Shows the gateway // icon + title only — no key/value table. Extra rows (e.g. Stripe auto renewal) // keep their kv layout beneath. $used_keys['payment_method'] = true; $used_keys['stripe_auto_renewal'] = true; $payment_title = $order ? $order->get_payment_method_title() : ''; $payment_icon = ''; $payment_plugin = ''; if ( $order ) { $gateways = WC()->payment_gateways() ? WC()->payment_gateways()->payment_gateways() : array(); $gateway_id = $order->get_payment_method(); $gateway_obj = isset( $gateways[ $gateway_id ] ) ? $gateways[ $gateway_id ] : null; $payment_icon = $gateway_obj ? $gateway_obj->get_icon() : ''; // Resolve which plugin registered this gateway (e.g. several Stripe methods // all come from "WooCommerce Stripe Gateway"). Map the gateway class file to // its plugin folder, then read that plugin's header Name. if ( $gateway_obj ) { try { $gateway_file = wp_normalize_path( ( new ReflectionClass( $gateway_obj ) )->getFileName() ); $plugins_dir = wp_normalize_path( WP_PLUGIN_DIR ); if ( $gateway_file && 0 === strpos( $gateway_file, $plugins_dir ) ) { $gateway_slug = strtok( ltrim( substr( $gateway_file, strlen( $plugins_dir ) ), '/' ), '/' ); if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } foreach ( get_plugins() as $plugin_path => $plugin_data ) { if ( strtok( $plugin_path, '/' ) === $gateway_slug ) { $payment_plugin = $plugin_data['Name']; break; } } } } catch ( \Exception $e ) { $payment_plugin = ''; } } } // Payment Method card is always shown; the title falls back to '-' when no // gateway is resolved. $has_payment = true; // Secondary info → sidebar. $side_sections = $build_sections( array( array( 'title' => __( 'Billing & Shipping', 'subscription' ), 'keys' => array( 'billing_address', 'shipping_address' ), ), ) ); // Any leftover rows → Additional Details (main column). $main_sections = array(); $leftover_rows = array_diff_key( $rows, $used_keys ); if ( ! empty( $leftover_rows ) ) { $main_sections[] = array( 'title' => __( 'Additional Details', 'subscription' ), 'rows' => $leftover_rows, ); } // Resolve related orders once. $related_rows = array(); foreach ( $order_histories as $history ) { $related_order = wc_get_order( $history->order_id ); if ( ! $related_order ) { continue; } $related_rows[] = array( 'order' => $related_order, 'label' => ucfirst( str_replace( '-', ' ', (string) $history->type ) ), ); } /* * Sequence number within this subscription: 1 = first (parent) order, then each * renewal in order. Helper::get_related_orders() returns newest-first, so the * sequence counts down across the rendered rows. */ $related_count = count( $related_rows ); foreach ( array_keys( $related_rows ) as $related_index ) { $related_rows[ $related_index ]['seq'] = $related_count - $related_index; } // Reusable per-page + date toolbar for the paginated tables. Uses the admin // advanced-select component; the date options are injected client-side from the // table's date column. $subscrpt_render_table_tools = function () { ?>
'subscrpt_date_filter', 'placeholder' => __( 'All Dates', 'subscription' ), 'value' => '', 'align' => 'right', 'class' => 'subscrpt-filter-date', 'options' => array( array( 'value' => '', 'label' => __( 'All Dates', 'subscription' ), ), ), ) ); wpsubs_render_per_page_select( array( 'name' => 'subscrpt_per_page', 'align' => 'right', 'class' => 'subscrpt-filter-perpage', ) ); ?>
$subscription_id, 'subscription_data' => $subscription_data, 'order' => $order, 'order_item' => $order_item, 'status' => $subscrpt_status, ); ?>

'active', // green 'pending' => 'pending', // blue 'processing' => 'pending-cancel', // yellow 'on-hold' => 'pending-cancel', // yellow 'failed' => 'expired', // red 'refunded' => 'expired', // red 'trash' => 'trash', // red 'cancelled' => 'cancelled', // gray ); foreach ( $related_rows as $related ) : $related_order = $related['order']; $related_status = $related_order->get_status(); $badge_class = $order_badge_map[ $related_status ] ?? 'draft'; ?>
get_order_number() ); ?> #get_order_number() ); ?> get_date_created()->date( get_option( 'date_format' ) . ' - ' . get_option( 'time_format' ) ) ); ?> get_payment_method_title() ? esc_html( $related_order->get_payment_method_title() ) : '—'; ?> get_formatted_order_total() ); ?>
1, 'total' => max( 1, (int) ceil( $related_total / 10 ) ), 'info' => true, 'per_page' => 10, 'item_count' => $related_total, 'link_mode' => 'cb', 'class' => 'subscrpt-pager', // translators: %1$s: first item number, %2$s: last item number, %3$s: total items 'info_format' => __( 'Showing %1$s–%2$s of %3$s related orders', 'subscription' ), 'context' => 'subscription-details-related-orders', ) ); ?>

1, 'total' => 1, 'info' => true, 'per_page' => 10, 'item_count' => 0, 'link_mode' => 'cb', 'class' => 'subscrpt-pager', // translators: %1$s: first item number, %2$s: last item number, %3$s: total items 'info_format' => __( 'Showing %1$s–%2$s of %3$s activities', 'subscription' ), 'context' => 'subscription-details-activities', ) ); ?>
$actions_data[ $action_slug ]['value'], 'label' => $actions_data[ $action_slug ]['label'], ); } } wpsubs_render_adv_select( array( 'name' => 'subscrpt_order_action', 'placeholder' => __( 'Choose Action', 'subscription' ), 'value' => '', 'options' => $action_options, 'class' => 'subscrpt-action-select', ) ); ?>