PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 1.10.5
Subscriptions for WooCommerce with Stripe Recurring Payments v1.10.5
2.4.0 2.3.0 2.2.0 2.1.0 2.0.0 1.11.2 1.11.1 1.11.0 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.6 1.9.5 trunk 1.3.0 1.3.1 1.3.2 1.4.0 All 65 releases
subscription / includes / Admin / views / subscription-details.php

subscription-details.php in Subscriptions for WooCommerce with Stripe Recurring Payments 1.10.5, at includes/Admin/views/subscription-details.php

1,180 lines 42.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Standalone subscription details page view.
4 *
5 * Rendered by Menu::render_subscription_details_page(). Matches the
6 * WPSubscription admin design system (wpsubs-* components + :root tokens).
7 *
8 * @package SpringDevs\Subscription\Admin
9 *
10 * @var int $subscription_id Subscription post ID.
11 * @var array $subscription_data Structured data from Helper::get_subscription_data().
12 * @var array $rows Filtered info rows from Subscriptions::get_info_rows().
13 * @var \WC_Order $order Related WooCommerce order.
14 * @var mixed $order_item Related order line item.
15 * @var array $actions Allowed action slugs for the current status.
16 * @var array $actions_data Map of action slug => label/value.
17 * @var array $order_histories Related order relation rows.
18 * @var string $list_url Subscriptions list URL.
19 * @var string $form_action URL the status form posts to.
20 */
21
22 if ( ! defined( 'ABSPATH' ) ) {
23 exit;
24 }
25
26 use SpringDevs\Subscription\Illuminate\Helper;
27
28 $product_name = $order_item ? $order_item->get_name() : '-';
29 $product_id = $order_item ? $order_item->get_product_id() : 0;
30 $product_link = $product_id ? get_the_permalink( $product_id ) : '';
31 $product_obj = $product_id ? wc_get_product( $product_id ) : null;
32 $product_image = $product_obj ? $product_obj->get_image( 'thumbnail', array( 'class' => 'subscrpt-plan-img' ) ) : '';
33
34 $subscrpt_status = $subscription_data['status'] ?? get_post_status( $subscription_id );
35 $verbose_status = Helper::get_verbose_status( $subscrpt_status );
36
37 $badge_mod_map = array(
38 'active' => 'active',
39 'pending' => 'pending',
40 'pe_cancelled' => 'pending-cancel',
41 'cancelled' => 'cancelled',
42 'expired' => 'expired',
43 'draft' => 'draft',
44 'trash' => 'trash',
45 );
46 $badge_mod = $badge_mod_map[ $subscrpt_status ] ?? 'expired';
47
48 $is_grace_period = isset( $subscription_data['grace_period'] );
49 $grace_remaining = $subscription_data['grace_period']['remaining_days'] ?? 0;
50 $grace_end_date = $subscription_data['grace_period']['end_date'] ?? '';
51 $grace_end_date = ! empty( $grace_end_date ) ? wp_date( get_option( 'date_format' ) . ' - ' . get_option( 'time_format' ), strtotime( $grace_end_date ) ) : '';
52
53 if ( $is_grace_period && $grace_remaining > 0 ) {
54 $badge_mod = 'active';
55 $verbose_status = __( 'Active', 'subscription' );
56 }
57
58 $customer_id = $order ? $order->get_customer_id() : 0;
59 $customer_name = $order ? $order->get_formatted_billing_full_name() : '-';
60 $customer_email = $order ? $order->get_billing_email() : '';
61 $customer_phone = $order ? $order->get_billing_phone() : '';
62 $customer_url = $customer_id ? admin_url( 'user-edit.php?user_id=' . $customer_id ) : '';
63
64 // Page header: product title + meta description (badge · #id · email · next payment).
65 $header_title = ( $product_name && '-' !== $product_name )
66 ? $product_name
67 /* translators: %s: subscription ID */
68 : sprintf( __( 'Subscription #%s', 'subscription' ), $subscription_id );
69
70 $next_payment_date = $subscription_data['next_date'] ?? '';
71 $next_payment_date = ! empty( $next_payment_date ) ? wp_date( get_option( 'date_format' ), strtotime( $next_payment_date ) ) : '';
72
73 $header_badge = '<span class="wpsubs-badge wpsubs-badge--' . esc_attr( $badge_mod ) . '">' . esc_html( $verbose_status );
74 if ( $is_grace_period && $grace_remaining > 0 ) {
75 /* translators: %d: days remaining */
76 $grace_title = sprintf( __( '%d days remaining in grace period', 'subscription' ), $grace_remaining );
77 $header_badge .= ' <span class="dashicons dashicons-warning" style="font-size:11px;width:11px;height:11px;color:#d97706;" title="' . esc_attr( $grace_title ) . '"></span>';
78 }
79 $header_badge .= '</span>';
80
81 $header_segments = array(
82 $header_badge,
83 '#' . (int) $subscription_id,
84 );
85 if ( $customer_email ) {
86 $header_segments[] = esc_html( $customer_email );
87 }
88 if ( $next_payment_date ) {
89 /* translators: %s: next payment date */
90 $header_segments[] = esc_html( sprintf( __( 'Next payment on %s', 'subscription' ), $next_payment_date ) );
91 }
92 $header_desc_html = implode( '<span class="subscrpt-detail-head__sep">·</span>', $header_segments );
93
94 $rows = is_array( $rows ) ? $rows : array();
95
96 // Top summary tiles — pulled from the info rows so Pro's filtered values stay
97 // in sync. Their keys are marked used so they are not repeated below.
98 $summary_tiles = array();
99 if ( $order && isset( $subscription_data['price'] ) && '' !== $subscription_data['price'] ) {
100 // Build the recurring price from structured data so the price and period can
101 // be styled separately (price prominent, "/ period" small and muted).
102 $timing_per = (int) ( $subscription_data['schedule']['timing_per'] ?? 1 );
103 $timing_option = $subscription_data['schedule']['timing_option'] ?? '';
104 $period_label = '';
105 if ( $timing_option ) {
106 $period_label = $timing_per > 1
107 ? $timing_per . ' ' . ucfirst( $timing_option ) . 's'
108 : ucfirst( $timing_option );
109 }
110
111 $recurring_value = wc_price( (float) $subscription_data['price'], array( 'currency' => $order->get_currency() ) );
112 if ( $period_label ) {
113 $recurring_value .= '<span class="subscrpt-summary__unit"> / ' . esc_html( $period_label ) . '</span>';
114 }
115
116 $summary_tiles[] = array(
117 'label' => __( 'Recurring', 'subscription' ),
118 'value' => $recurring_value,
119 );
120 }
121 if ( ! empty( $subscription_data['start_date'] ) ) {
122 $summary_tiles[] = array(
123 'label' => __( 'Started', 'subscription' ),
124 'value' => esc_html( wp_date( get_option( 'date_format' ), strtotime( $subscription_data['start_date'] ) ) ),
125 );
126 }
127 if ( ! empty( $subscription_data['next_date'] ) ) {
128 $summary_tiles[] = array(
129 'label' => __( 'Next Payment', 'subscription' ),
130 'value' => esc_html( wp_date( get_option( 'date_format' ), strtotime( $subscription_data['next_date'] ) ) ),
131 );
132 }
133 // Total payments is not part of $subscription_data; pull it from the info rows.
134 if ( isset( $rows['total_payments'] ) ) {
135 $summary_tiles[] = array(
136 'label' => __( 'Total Payments', 'subscription' ),
137 'value' => $rows['total_payments']['value'],
138 );
139 }
140
141 // Group the remaining info rows into cards. Unmapped keys — including rows added
142 // through the subscrpt_admin_info_rows filter (Pro) — fall into "Additional
143 // Details" so nothing is dropped. Status is shown as the header badge.
144 $used_keys = array(
145 'cost' => true,
146 'start_date' => true,
147 'next_date' => true,
148 'total_payments' => true,
149 'status' => true,
150 );
151
152 // Plan card: product + qty render side by side; trial/signup fee as extra rows.
153 $plan_product = isset( $rows['product'] ) ? $rows['product']['value'] : '';
154 $plan_qty = isset( $rows['quantity'] ) ? $rows['quantity']['value'] : '';
155 $plan_extra = array();
156 foreach ( array( 'product', 'quantity', 'signup_fee', 'trial', 'trial_period' ) as $plan_key ) {
157 $used_keys[ $plan_key ] = true;
158 if ( in_array( $plan_key, array( 'signup_fee', 'trial', 'trial_period' ), true ) && isset( $rows[ $plan_key ] ) ) {
159 $plan_extra[ $plan_key ] = $rows[ $plan_key ];
160 }
161 }
162
163 $build_sections = function ( array $map ) use ( $rows, &$used_keys ) {
164 $out = array();
165 foreach ( $map as $section ) {
166 $section_rows = array();
167 foreach ( $section['keys'] as $key ) {
168 if ( isset( $rows[ $key ] ) ) {
169 $section_rows[ $key ] = $rows[ $key ];
170 $used_keys[ $key ] = true;
171 }
172 }
173 if ( ! empty( $section_rows ) ) {
174 $out[] = array(
175 'title' => $section['title'],
176 'rows' => $section_rows,
177 );
178 }
179 }
180 return $out;
181 };
182
183 // Payment Method renders next to the Plan card (main column). Shows the gateway
184 // icon + title only — no key/value table. Extra rows (e.g. Stripe auto renewal)
185 // keep their kv layout beneath.
186 $used_keys['payment_method'] = true;
187 $used_keys['stripe_auto_renewal'] = true;
188 $payment_title = $order ? $order->get_payment_method_title() : '';
189 $payment_icon = '';
190 $payment_plugin = '';
191 if ( $order ) {
192 $gateways = WC()->payment_gateways() ? WC()->payment_gateways()->payment_gateways() : array();
193 $gateway_id = $order->get_payment_method();
194 $gateway_obj = isset( $gateways[ $gateway_id ] ) ? $gateways[ $gateway_id ] : null;
195 $payment_icon = $gateway_obj ? $gateway_obj->get_icon() : '';
196
197 // Resolve which plugin registered this gateway (e.g. several Stripe methods
198 // all come from "WooCommerce Stripe Gateway"). Map the gateway class file to
199 // its plugin folder, then read that plugin's header Name.
200 if ( $gateway_obj ) {
201 try {
202 $gateway_file = wp_normalize_path( ( new ReflectionClass( $gateway_obj ) )->getFileName() );
203 $plugins_dir = wp_normalize_path( WP_PLUGIN_DIR );
204 if ( $gateway_file && 0 === strpos( $gateway_file, $plugins_dir ) ) {
205 $gateway_slug = strtok( ltrim( substr( $gateway_file, strlen( $plugins_dir ) ), '/' ), '/' );
206 if ( ! function_exists( 'get_plugins' ) ) {
207 require_once ABSPATH . 'wp-admin/includes/plugin.php';
208 }
209 foreach ( get_plugins() as $plugin_path => $plugin_data ) {
210 if ( strtok( $plugin_path, '/' ) === $gateway_slug ) {
211 $payment_plugin = $plugin_data['Name'];
212 break;
213 }
214 }
215 }
216 } catch ( \Exception $e ) {
217 $payment_plugin = '';
218 }
219 }
220 }
221 // Payment Method card is always shown; the title falls back to '-' when no
222 // gateway is resolved.
223 $has_payment = true;
224
225 // Secondary info → sidebar.
226 $side_sections = $build_sections(
227 array(
228 array(
229 'title' => __( 'Billing & Shipping', 'subscription' ),
230 'keys' => array( 'billing_address', 'shipping_address' ),
231 ),
232 )
233 );
234
235 // Any leftover rows → Additional Details (main column).
236 $main_sections = array();
237 $leftover_rows = array_diff_key( $rows, $used_keys );
238 if ( ! empty( $leftover_rows ) ) {
239 $main_sections[] = array(
240 'title' => __( 'Additional Details', 'subscription' ),
241 'rows' => $leftover_rows,
242 );
243 }
244
245 // Resolve related orders once.
246 $related_rows = array();
247 foreach ( $order_histories as $history ) {
248 $related_order = wc_get_order( $history->order_id );
249 if ( ! $related_order ) {
250 continue;
251 }
252 $related_rows[] = array(
253 'order' => $related_order,
254 'label' => ucfirst( str_replace( '-', ' ', (string) $history->type ) ),
255 );
256 }
257
258 // Reusable per-page + date toolbar for the paginated tables. Uses the admin
259 // advanced-select component; the date options are injected client-side from the
260 // table's date column.
261 $subscrpt_render_table_tools = function () {
262 ?>
263 <div class="subscrpt-card__tools">
264 <?php
265 wpsubs_render_adv_select(
266 array(
267 'name' => 'subscrpt_date_filter',
268 'placeholder' => __( 'All Dates', 'subscription' ),
269 'value' => '',
270 'align' => 'right',
271 'class' => 'subscrpt-filter-date',
272 'options' => array(
273 array(
274 'value' => '',
275 'label' => __( 'All Dates', 'subscription' ),
276 ),
277 ),
278 )
279 );
280 wpsubs_render_adv_select(
281 array(
282 'name' => 'subscrpt_per_page',
283 'value' => '10',
284 'align' => 'right',
285 'class' => 'subscrpt-filter-perpage',
286 'options' => array(
287 array(
288 'value' => '10',
289 'label' => __( '10 / page', 'subscription' ),
290 ),
291 array(
292 'value' => '20',
293 'label' => __( '20 / page', 'subscription' ),
294 ),
295 array(
296 'value' => '50',
297 'label' => __( '50 / page', 'subscription' ),
298 ),
299 array(
300 'value' => '100',
301 'label' => __( '100 / page', 'subscription' ),
302 ),
303 ),
304 )
305 );
306 ?>
307 </div>
308 <?php
309 };
310
311 /**
312 * Shared context passed to every subscription-details extension hook so that
313 * third-party plugins can render extra sections without re-querying.
314 *
315 * @var array $subscrpt_details_ctx {
316 * @type int $subscription_id Subscription post ID.
317 * @type array $subscription_data Structured data from Helper::get_subscription_data().
318 * @type \WC_Order $order Related WooCommerce order (or null).
319 * @type mixed $order_item Related order line item (or null).
320 * @type string $status Subscription post status.
321 * }
322 */
323 $subscrpt_details_ctx = array(
324 'subscription_id' => $subscription_id,
325 'subscription_data' => $subscription_data,
326 'order' => $order,
327 'order_item' => $order_item,
328 'status' => $subscrpt_status,
329 );
330 ?>
331 <div class="wp-subscription-admin-content list-page subscrpt-subs-details">
332
333 <!-- Page header -->
334 <div class="subscrpt-detail-head">
335 <h1 class="subscrpt-detail-head__title"><?php echo esc_html( $header_title ); ?></h1>
336 <div class="subscrpt-detail-head__desc"><?php echo wp_kses_post( $header_desc_html ); ?></div>
337 <div class="subscrpt-detail-head__divider"></div>
338 </div>
339
340 <div class="subscrpt-detail-grid">
341
342 <!-- Main column -->
343 <div class="subscrpt-detail-main">
344
345 <?php
346 /**
347 * Fires at the top of the details main (left) column, before the summary strip.
348 *
349 * Echo a `.subscrpt-card` to add a full-width section here.
350 *
351 * @param array $subscrpt_details_ctx Subscription details context.
352 */
353 do_action( 'subscrpt_details_main_top', $subscrpt_details_ctx );
354 ?>
355
356 <!-- Summary strip -->
357 <?php if ( ! empty( $summary_tiles ) ) : ?>
358 <div class="subscrpt-summary">
359 <?php foreach ( $summary_tiles as $tile ) : ?>
360 <div class="subscrpt-summary__tile">
361 <span class="subscrpt-summary__label"><?php echo esc_html( $tile['label'] ); ?></span>
362 <span class="subscrpt-summary__value"><?php echo wp_kses_post( $tile['value'] ); ?></span>
363 </div>
364 <?php endforeach; ?>
365 </div>
366 <?php endif; ?>
367
368 <?php if ( $is_grace_period ) : ?>
369 <!-- Grace period card -->
370 <div class="subscrpt-card">
371 <div class="subscrpt-card__head"><?php esc_html_e( 'Grace Period', 'subscription' ); ?></div>
372 <div class="subscrpt-card__body">
373 <table class="subscrpt-kv">
374 <tbody>
375 <tr>
376 <th><?php esc_html_e( 'Remaining', 'subscription' ); ?></th>
377 <td><?php echo esc_html( sprintf( /* translators: %d: days */ _n( '%d day', '%d days', (int) $grace_remaining, 'subscription' ), (int) $grace_remaining ) ); ?></td>
378 </tr>
379 <?php if ( $grace_end_date ) : ?>
380 <tr>
381 <th><?php esc_html_e( 'End Date', 'subscription' ); ?></th>
382 <td><?php echo esc_html( $grace_end_date ); ?></td>
383 </tr>
384 <?php endif; ?>
385 </tbody>
386 </table>
387 </div>
388 </div>
389 <?php endif; ?>
390
391 <!-- Plan + Payment Method side by side -->
392 <?php
393 $has_plan = $plan_product || $plan_qty || ! empty( $plan_extra );
394 if ( $has_plan || $has_payment ) :
395 ?>
396 <div class="subscrpt-plan-pay">
397 <?php if ( $has_plan ) : ?>
398 <!-- Plan card (product + qty side by side) -->
399 <div class="subscrpt-card subscrpt-card--plan">
400 <div class="subscrpt-card__head"><?php esc_html_e( 'Product', 'subscription' ); ?></div>
401 <div class="subscrpt-card__body">
402 <div class="subscrpt-plan-row">
403 <div class="subscrpt-plan-thumb">
404 <?php if ( $product_image ) : ?>
405 <?php echo wp_kses_post( $product_image ); ?>
406 <?php else : ?>
407 <span class="subscrpt-plan-thumb__ph dashicons dashicons-format-image"></span>
408 <?php endif; ?>
409 </div>
410 <div class="subscrpt-plan-meta">
411 <span class="subscrpt-plan-name"><?php echo $plan_product ? wp_kses_post( $plan_product ) : '&mdash;'; ?></span>
412 <span class="subscrpt-plan-qty">
413 <?php
414 /* translators: %s: quantity */
415 printf( esc_html__( 'Quantity: %s', 'subscription' ), $plan_qty ? wp_kses_post( $plan_qty ) : '1' );
416 ?>
417 </span>
418 </div>
419 </div>
420 <?php if ( ! empty( $plan_extra ) ) : ?>
421 <table class="subscrpt-kv subscrpt-plan-extra">
422 <tbody>
423 <?php foreach ( $plan_extra as $row ) : ?>
424 <tr>
425 <th><?php echo esc_html( $row['label'] ); ?></th>
426 <td><?php echo wp_kses_post( $row['value'] ); ?></td>
427 </tr>
428 <?php endforeach; ?>
429 </tbody>
430 </table>
431 <?php endif; ?>
432 </div>
433 </div>
434 <?php endif; ?>
435
436 <?php if ( $has_payment ) : ?>
437 <!-- Payment method card (gateway icon + title) -->
438 <div class="subscrpt-card">
439 <div class="subscrpt-card__head"><?php esc_html_e( 'Payment Method', 'subscription' ); ?></div>
440 <div class="subscrpt-card__body">
441 <?php if ( $payment_title ) : ?>
442 <div class="subscrpt-payment">
443 <span class="subscrpt-payment__icon">
444 <?php if ( $payment_icon ) : ?>
445 <?php echo wp_kses_post( $payment_icon ); ?>
446 <?php else : ?>
447 <span class="subscrpt-payment__icon-ph dashicons dashicons-bank"></span>
448 <?php endif; ?>
449 </span>
450 <span class="subscrpt-payment__meta">
451 <span class="subscrpt-payment__title"><?php echo esc_html( $payment_title ); ?></span>
452 <?php if ( $payment_plugin ) : ?>
453 <span class="subscrpt-payment__plugin"><?php echo esc_html( $payment_plugin ); ?></span>
454 <?php endif; ?>
455 </span>
456 </div>
457 <?php else : ?>
458 <p class="subscrpt-muted"><?php esc_html_e( 'No payment method.', 'subscription' ); ?></p>
459 <?php endif; ?>
460 </div>
461 </div>
462 <?php endif; ?>
463 </div>
464 <?php endif; ?>
465
466 <!-- Detail sections -->
467 <?php foreach ( $main_sections as $section ) : ?>
468 <div class="subscrpt-card">
469 <div class="subscrpt-card__head"><?php echo esc_html( $section['title'] ); ?></div>
470 <div class="subscrpt-card__body">
471 <table class="subscrpt-kv">
472 <tbody>
473 <?php foreach ( $section['rows'] as $row ) : ?>
474 <tr>
475 <th><?php echo esc_html( $row['label'] ); ?></th>
476 <td><?php echo wp_kses_post( $row['value'] ); ?></td>
477 </tr>
478 <?php endforeach; ?>
479 </tbody>
480 </table>
481 </div>
482 </div>
483 <?php endforeach; ?>
484
485 <?php if ( empty( $summary_tiles ) && empty( $main_sections ) && empty( $side_sections ) && ! $has_payment && ! $plan_product && ! $plan_qty && empty( $plan_extra ) && ! $is_grace_period ) : ?>
486 <div class="subscrpt-card">
487 <div class="subscrpt-card__head"><?php esc_html_e( 'Subscription Details', 'subscription' ); ?></div>
488 <div class="subscrpt-card__body">
489 <p class="subscrpt-muted"><?php esc_html_e( 'No subscription details available.', 'subscription' ); ?></p>
490 </div>
491 </div>
492 <?php endif; ?>
493
494 <!-- Related orders card -->
495 <div class="subscrpt-card" data-subscrpt-paginate data-per-page="10" data-date-col="2">
496 <div class="subscrpt-card__head subscrpt-card__head--toolbar">
497 <span class="subscrpt-card__title"><?php esc_html_e( 'Related Orders', 'subscription' ); ?></span>
498 <?php if ( ! empty( $related_rows ) ) : ?>
499 <?php $subscrpt_render_table_tools(); ?>
500 <?php endif; ?>
501 </div>
502 <?php if ( ! empty( $related_rows ) ) : ?>
503 <table class="wpsubs-table subscrpt-table--compact">
504 <thead>
505 <tr>
506 <th><?php esc_html_e( 'Order', 'subscription' ); ?></th>
507 <th><?php esc_html_e( 'Type', 'subscription' ); ?></th>
508 <th><?php esc_html_e( 'Date', 'subscription' ); ?></th>
509 <th><?php esc_html_e( 'Status', 'subscription' ); ?></th>
510 <th><?php esc_html_e( 'Items', 'subscription' ); ?></th>
511 <th><?php esc_html_e( 'Payment', 'subscription' ); ?></th>
512 <th><?php esc_html_e( 'Total', 'subscription' ); ?></th>
513 </tr>
514 </thead>
515 <tbody>
516 <?php
517 // Map WC order statuses onto the shared subscription badge
518 // modifiers so they get the same bg/border/color treatment.
519 $order_badge_map = array(
520 'completed' => 'active', // green
521 'pending' => 'pending', // blue
522 'processing' => 'pending-cancel', // yellow
523 'on-hold' => 'pending-cancel', // yellow
524 'failed' => 'expired', // red
525 'refunded' => 'expired', // red
526 'trash' => 'trash', // red
527 'cancelled' => 'cancelled', // gray
528 );
529 foreach ( $related_rows as $related ) :
530 $related_order = $related['order'];
531 $related_status = $related_order->get_status();
532 $badge_class = $order_badge_map[ $related_status ] ?? 'draft';
533 ?>
534 <tr>
535 <td>
536 <a href="<?php echo esc_url( $related_order->get_edit_order_url() ); ?>" target="_blank" class="wpsubs-cell-title">
537 #<?php echo esc_html( $related_order->get_order_number() ); ?>
538 </a>
539 </td>
540 <td><?php echo esc_html( $related['label'] ); ?></td>
541 <td><?php echo esc_html( $related_order->get_date_created()->date( get_option( 'date_format' ) . ' - ' . get_option( 'time_format' ) ) ); ?></td>
542 <td><span class="wpsubs-badge wpsubs-badge--<?php echo esc_attr( $badge_class ); ?>"><?php echo esc_html( wc_get_order_status_name( $related_status ) ); ?></span></td>
543 <td><?php echo esc_html( number_format_i18n( $related_order->get_item_count() ) ); ?></td>
544 <td><?php echo $related_order->get_payment_method_title() ? esc_html( $related_order->get_payment_method_title() ) : '&mdash;'; ?></td>
545 <td><?php echo wp_kses_post( $related_order->get_formatted_order_total() ); ?></td>
546 </tr>
547 <?php endforeach; ?>
548 </tbody>
549 </table>
550 <div class="subscrpt-pager wpsubs-pagination" hidden></div>
551 <?php else : ?>
552 <div class="subscrpt-card__body"><p class="subscrpt-muted"><?php esc_html_e( 'No related orders found.', 'subscription' ); ?></p></div>
553 <?php endif; ?>
554 </div>
555
556 <!-- Activities card -->
557 <?php $subscrpt_pro_on = function_exists( 'subscrpt_pro_activated' ) && subscrpt_pro_activated(); ?>
558 <div class="subscrpt-card" data-subscrpt-paginate data-per-page="10" data-date-col="2">
559 <div class="subscrpt-card__head subscrpt-card__head--toolbar">
560 <span class="subscrpt-card__title"><?php esc_html_e( 'Subscription Activities', 'subscription' ); ?></span>
561 <?php if ( $subscrpt_pro_on ) : ?>
562 <?php $subscrpt_render_table_tools(); ?>
563 <?php endif; ?>
564 </div>
565 <?php if ( $subscrpt_pro_on ) : ?>
566 <div class="subscrpt-activities">
567 <?php
568 /**
569 * Fires inside the subscription details activities card.
570 *
571 * The Pro plugin renders the activity table here.
572 *
573 * @param int $subscription_id Subscription post ID.
574 */
575 do_action( 'subscrpt_order_activities', $subscription_id );
576 ?>
577 </div>
578 <div class="subscrpt-pager wpsubs-pagination" hidden></div>
579 <?php else : ?>
580 <div class="subscrpt-card__body">
581 <div class="subscrpt-upgrade-banner">
582 <div>
583 <strong><?php esc_html_e( 'Upgrade to WPSubscription Pro', 'subscription' ); ?></strong>
584 <p><?php esc_html_e( 'Track subscription activity history, automation, and more.', 'subscription' ); ?></p>
585 </div>
586 <a href="https://wpsubscription.co/" target="_blank" class="wpsubs-btn wpsubs-btn--primary wpsubs-btn--sm" rel="noreferrer noopener">
587 <?php esc_html_e( 'Upgrade to Pro', 'subscription' ); ?>
588 </a>
589 </div>
590 </div>
591 <?php endif; ?>
592 </div>
593
594 <?php
595 /**
596 * Fires at the bottom of the details main (left) column, after the activities card.
597 *
598 * Echo a `.subscrpt-card` to add a full-width section here.
599 *
600 * @param array $subscrpt_details_ctx Subscription details context.
601 */
602 do_action( 'subscrpt_details_main_bottom', $subscrpt_details_ctx );
603 ?>
604
605 </div><!-- /.subscrpt-detail-main -->
606
607 <!-- Sidebar column -->
608 <div class="subscrpt-detail-side">
609
610 <?php
611 /**
612 * Fires at the top of the details sidebar (right) column, before the action card.
613 *
614 * Echo a `.subscrpt-card` to add a section here.
615 *
616 * @param array $subscrpt_details_ctx Subscription details context.
617 */
618 do_action( 'subscrpt_details_side_top', $subscrpt_details_ctx );
619 ?>
620
621 <!-- Status action card -->
622 <div class="subscrpt-card subscrpt-card--overflow">
623 <div class="subscrpt-card__head"><?php esc_html_e( 'Subscription Action', 'subscription' ); ?></div>
624 <div class="subscrpt-card__body">
625 <?php if ( ! empty( $actions ) ) : ?>
626 <form method="post" action="<?php echo esc_url( $form_action ); ?>" class="subscrpt-action-form">
627 <?php wp_nonce_field( 'subscrpt_order_action_nonce', 'subscrpt_order_action_nonce_field' ); ?>
628 <?php
629 $action_options = array();
630 foreach ( $actions as $action_slug ) {
631 if ( isset( $actions_data[ $action_slug ] ) ) {
632 $action_options[] = array(
633 'value' => $actions_data[ $action_slug ]['value'],
634 'label' => $actions_data[ $action_slug ]['label'],
635 );
636 }
637 }
638 wpsubs_render_adv_select(
639 array(
640 'name' => 'subscrpt_order_action',
641 'placeholder' => __( 'Choose Action', 'subscription' ),
642 'value' => '',
643 'options' => $action_options,
644 'class' => 'subscrpt-action-select',
645 )
646 );
647 ?>
648 <button type="submit" class="wpsubs-btn wpsubs-btn--primary" style="width:100%;justify-content:center;margin-top:10px;">
649 <?php esc_html_e( 'Process', 'subscription' ); ?>
650 </button>
651 </form>
652 <?php else : ?>
653 <p class="subscrpt-muted"><?php esc_html_e( 'No actions available for this status.', 'subscription' ); ?></p>
654 <?php endif; ?>
655 </div>
656 </div>
657
658 <!-- Customer card -->
659 <div class="subscrpt-card">
660 <div class="subscrpt-card__head"><?php esc_html_e( 'Customer Details', 'subscription' ); ?></div>
661 <div class="subscrpt-card__body">
662 <?php if ( $order ) : ?>
663 <div class="subscrpt-customer-name">
664 <?php if ( $customer_url ) : ?>
665 <a href="<?php echo esc_url( $customer_url ); ?>" target="_blank"><?php echo esc_html( $customer_name ); ?></a>
666 <?php else : ?>
667 <?php echo esc_html( $customer_name ); ?>
668 <?php endif; ?>
669 </div>
670 <?php if ( $customer_email ) : ?>
671 <div class="subscrpt-customer-line"><a href="mailto:<?php echo esc_attr( $customer_email ); ?>"><?php echo esc_html( $customer_email ); ?></a></div>
672 <?php endif; ?>
673 <?php if ( $customer_phone ) : ?>
674 <div class="subscrpt-customer-line"><a href="tel:<?php echo esc_attr( $customer_phone ); ?>"><?php echo esc_html( $customer_phone ); ?></a></div>
675 <?php endif; ?>
676
677 <?php else : ?>
678 <p class="subscrpt-muted"><?php esc_html_e( 'Order not found.', 'subscription' ); ?></p>
679 <?php endif; ?>
680 </div>
681 </div>
682
683 <!-- Secondary info cards (payment method, addresses) -->
684 <?php foreach ( $side_sections as $section ) : ?>
685 <div class="subscrpt-card">
686 <div class="subscrpt-card__head"><?php echo esc_html( $section['title'] ); ?></div>
687 <div class="subscrpt-card__body">
688 <table class="subscrpt-kv">
689 <tbody>
690 <?php foreach ( $section['rows'] as $row ) : ?>
691 <tr>
692 <th><?php echo esc_html( $row['label'] ); ?></th>
693 <td><?php echo wp_kses_post( $row['value'] ); ?></td>
694 </tr>
695 <?php endforeach; ?>
696 </tbody>
697 </table>
698 </div>
699 </div>
700 <?php endforeach; ?>
701
702 <?php
703 /**
704 * Fires at the bottom of the details sidebar (right) column, after the secondary info cards.
705 *
706 * Echo a `.subscrpt-card` to add a section here.
707 *
708 * @param array $subscrpt_details_ctx Subscription details context.
709 */
710 do_action( 'subscrpt_details_side_bottom', $subscrpt_details_ctx );
711 ?>
712
713 </div><!-- /.subscrpt-detail-side -->
714
715 </div><!-- /.subscrpt-detail-grid -->
716
717 </div>
718
719 <style>
720 /* Page sits on WP admin gray; the cards are the white surfaces (matches list/health pages). */
721 .wp-subscription-admin-content.list-page.subscrpt-subs-details {
722 background: transparent;
723 padding: 0;
724 border-radius: 0;
725 margin-top: 24px;
726 margin-bottom: 32px;
727 }
728 .subscrpt-subs-details .subscrpt-detail-head { margin-bottom: 20px; }
729 .subscrpt-detail-head__title {
730 font-size: 1.375rem;
731 font-weight: 700;
732 color: var(--wpsubs-text);
733 line-height: 1.2;
734 margin: 0 0 8px;
735 padding: 0;
736 }
737 .subscrpt-detail-head__desc {
738 display: flex;
739 align-items: center;
740 flex-wrap: wrap;
741 gap: 8px;
742 font-size: 13px;
743 color: var(--wpsubs-text-muted);
744 margin: 0 0 12px;
745 line-height: 1.5;
746 }
747 .subscrpt-detail-head__sep { color: var(--wpsubs-text-subtle); }
748 .subscrpt-detail-head__divider { border-top: 1px dashed #d0d3d7; }
749
750 /* Summary metric strip */
751 .subscrpt-summary {
752 display: grid;
753 grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
754 gap: 20px;
755 }
756 .subscrpt-summary__tile {
757 background: var(--wpsubs-surface);
758 border: 1px solid var(--wpsubs-border);
759 border-radius: var(--wpsubs-radius);
760 padding: 16px 18px;
761 display: flex;
762 flex-direction: column;
763 gap: 6px;
764 }
765 .subscrpt-summary__label {
766 font-size: 11px;
767 font-weight: 600;
768 text-transform: uppercase;
769 letter-spacing: 0.06em;
770 color: var(--wpsubs-text-muted);
771 }
772 .subscrpt-summary__value {
773 font-size: 18px;
774 font-weight: 700;
775 color: #111827;
776 line-height: 1.3;
777 }
778 .subscrpt-summary__value .subscrpt-summary__unit {
779 font-size: 13px;
780 font-weight: 500;
781 color: var(--wpsubs-text-muted);
782 }
783
784 .subscrpt-detail-grid {
785 display: grid;
786 grid-template-columns: minmax(0, 1fr) 340px;
787 gap: 20px;
788 align-items: start;
789 }
790 @media (max-width: 960px) {
791 .subscrpt-detail-grid { grid-template-columns: 1fr; }
792 }
793 .subscrpt-detail-main, .subscrpt-detail-side {
794 display: flex;
795 flex-direction: column;
796 gap: 20px;
797 min-width: 0;
798 }
799
800 .subscrpt-card {
801 background: var(--wpsubs-surface);
802 border: 1px solid var(--wpsubs-border);
803 border-radius: var(--wpsubs-radius);
804 box-shadow: none;
805 overflow: hidden;
806 }
807 .subscrpt-card__head {
808 padding: 14px 18px;
809 font-size: 14px;
810 font-weight: 600;
811 color: #111827;
812 border-bottom: 1px solid var(--wpsubs-border);
813 background: var(--wpsubs-surface);
814 }
815 .subscrpt-card__body { padding: 16px 18px; }
816 /* Cards holding an adv-select dropdown must not clip the popover. Without
817 overflow:hidden the head no longer inherits the card's rounded corners, so
818 round the head (and body) explicitly. */
819 .subscrpt-card--overflow { overflow: visible; }
820 .subscrpt-card--overflow .subscrpt-card__head {
821 border-top-left-radius: var(--wpsubs-radius);
822 border-top-right-radius: var(--wpsubs-radius);
823 }
824 .subscrpt-card--overflow .subscrpt-card__body:last-child {
825 border-bottom-left-radius: var(--wpsubs-radius);
826 border-bottom-right-radius: var(--wpsubs-radius);
827 }
828
829 /* Card head with right-aligned filter tools. */
830 .subscrpt-card__head--toolbar {
831 display: flex;
832 align-items: center;
833 justify-content: space-between;
834 gap: 12px;
835 flex-wrap: wrap;
836 font-weight: 400;
837 }
838 .subscrpt-card__title { font-size: 14px; font-weight: 600; color: #111827; }
839 /* Filter dropdown items must not inherit the card-head bold weight. */
840 .subscrpt-card__tools .wpsubs-adv-select__label,
841 .subscrpt-card__tools .wpsubs-adv-select__item { font-weight: 400; }
842 .subscrpt-card__tools {
843 display: flex;
844 align-items: center;
845 gap: 8px;
846 flex-wrap: wrap;
847 }
848 .subscrpt-card__tools .wpsubs-adv-select__trigger {
849 height: 32px;
850 padding-top: 0;
851 padding-bottom: 0;
852 }
853
854 /* Pager footer (uses .wpsubs-pagination layout). */
855 .subscrpt-pager .wpsubs-pagination__info { font-size: 12px; color: var(--wpsubs-text-muted); }
856 .subscrpt-pager .wpsubs-pagination__nav { display: flex; align-items: center; gap: 4px; }
857 .subscrpt-empty-row td { color: var(--wpsubs-text-muted); font-size: 13px; }
858
859 /* Tables render flush to the card edges (the table thead is the column strip). */
860 .subscrpt-card .wpsubs-table { border: 0; }
861 .subscrpt-card .wpsubs-table tbody tr:last-child td { border-bottom: 0; }
862 /* Match the page's 13px body text (default wpsubs-table cells run larger). */
863 .subscrpt-card .wpsubs-table th,
864 .subscrpt-card .wpsubs-table td,
865 .subscrpt-activities .wpsubs-table th,
866 .subscrpt-activities .wpsubs-table td { font-size: 13px; }
867 /* Tighter rows for the data tables. */
868 .subscrpt-table--compact th,
869 .subscrpt-table--compact td,
870 .subscrpt-activities .wpsubs-table th,
871 .subscrpt-activities .wpsubs-table td { padding-top: 12px; padding-bottom: 12px; }
872 .subscrpt-table--compact .wpsubs-badge { font-size: 11px; }
873 .subscrpt-activities .widefat,
874 .subscrpt-activities .wpsubs-table { border: 0; margin: 0; }
875 .subscrpt-card__actions {
876 display: flex;
877 flex-wrap: wrap;
878 gap: 8px;
879 margin-top: 14px;
880 }
881
882 .subscrpt-kv { width: 100%; border-collapse: collapse; }
883 .subscrpt-kv th, .subscrpt-kv td {
884 text-align: left;
885 padding: 9px 0;
886 font-size: 13px;
887 vertical-align: top;
888 border-bottom: 1px solid var(--wpsubs-border);
889 }
890 .subscrpt-kv tr:last-child th, .subscrpt-kv tr:last-child td { border-bottom: 0; }
891 .subscrpt-kv th {
892 width: 40%;
893 font-weight: 500;
894 color: var(--wpsubs-text-muted);
895 padding-right: 12px;
896 }
897 .subscrpt-kv td { color: var(--wpsubs-text); }
898 .subscrpt-kv a { color: var(--wpsubs-brand); text-decoration: none; }
899 .subscrpt-kv a:hover { text-decoration: underline; }
900
901 /* Plan + Payment Method row: Plan wider (2-col content), Payment narrower. */
902 .subscrpt-plan-pay {
903 display: grid;
904 grid-template-columns: repeat(3, minmax(0, 1fr));
905 gap: 20px;
906 align-items: stretch;
907 }
908 .subscrpt-plan-pay > .subscrpt-card { margin: 0; }
909 /* Plan spans 2 of the 3 columns so its right edge aligns with the summary's
910 2nd/3rd column boundary; the gateway card fills the remaining column. */
911 .subscrpt-plan-pay > .subscrpt-card--plan { grid-column: span 2; }
912 @media (max-width: 782px) {
913 .subscrpt-plan-pay { grid-template-columns: 1fr; }
914 }
915
916 /* Plan card: product image (1:1) + name/qty stacked. */
917 .subscrpt-plan-row {
918 display: grid;
919 grid-template-columns: 64px 1fr;
920 gap: 16px;
921 align-items: center;
922 }
923 .subscrpt-plan-thumb {
924 width: 64px;
925 height: 64px;
926 border: 1px solid var(--wpsubs-border);
927 border-radius: var(--wpsubs-radius-sm);
928 overflow: hidden;
929 background: var(--wpsubs-surface-alt, #f6f7f7);
930 display: flex;
931 align-items: center;
932 justify-content: center;
933 }
934 .subscrpt-plan-thumb img,
935 .subscrpt-plan-thumb .subscrpt-plan-img {
936 width: 100%;
937 height: 100%;
938 object-fit: cover;
939 display: block;
940 }
941 .subscrpt-plan-thumb__ph {
942 font-size: 28px;
943 width: 28px;
944 height: 28px;
945 color: var(--wpsubs-text-subtle, #a7aaad);
946 }
947 .subscrpt-plan-meta { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
948 .subscrpt-plan-name { font-size: 14px; font-weight: 600; color: var(--wpsubs-text); line-height: 1.3; }
949 .subscrpt-plan-name a { color: var(--wpsubs-text); text-decoration: none; }
950 .subscrpt-plan-name a:hover { color: var(--wpsubs-brand); }
951 .subscrpt-plan-qty { font-size: 13px; color: var(--wpsubs-text-muted); }
952 .subscrpt-plan-col { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
953 .subscrpt-plan-label {
954 font-size: 11px;
955 font-weight: 600;
956 text-transform: uppercase;
957 letter-spacing: 0.05em;
958 color: var(--wpsubs-text-muted);
959 }
960 .subscrpt-plan-value { font-size: 13px; color: var(--wpsubs-text); }
961 .subscrpt-plan-value a { color: var(--wpsubs-brand); text-decoration: none; }
962 .subscrpt-plan-value a:hover { text-decoration: underline; }
963 .subscrpt-plan-extra { margin-top: 14px; }
964
965 /* Payment method: gateway icon + (gateway title / source plugin). Mirrors the
966 plan-row layout so the two cards line up. */
967 .subscrpt-payment { display: grid; grid-template-columns: 40px 1fr; gap: 12px; align-items: center; }
968 .subscrpt-payment__icon {
969 width: 40px;
970 height: 40px;
971 display: flex;
972 align-items: center;
973 justify-content: center;
974 border: 1px solid var(--wpsubs-border);
975 border-radius: var(--wpsubs-radius-sm);
976 background: var(--wpsubs-surface-alt, #f6f7f7);
977 overflow: hidden;
978 }
979 .subscrpt-payment__icon img { width: 100%; height: 100%; object-fit: cover; display: block; }
980 .subscrpt-payment__icon-ph { font-size: 22px; width: 22px; height: 22px; color: var(--wpsubs-text-muted); }
981 .subscrpt-payment__meta { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
982 .subscrpt-payment__title { font-size: 14px; font-weight: 600; color: var(--wpsubs-text); line-height: 1.3; }
983 .subscrpt-payment__plugin { font-size: 13px; color: var(--wpsubs-text-muted); }
984
985 /* Plan + Payment cards stretch to equal height; bodies center their content. */
986 .subscrpt-plan-pay > .subscrpt-card { display: flex; flex-direction: column; }
987 .subscrpt-plan-pay > .subscrpt-card .subscrpt-card__body { flex: 1; display: flex; flex-direction: column; justify-content: center; }
988
989 .subscrpt-customer-name { font-size: 14px; font-weight: 600; color: var(--wpsubs-text); }
990 .subscrpt-customer-name a { color: var(--wpsubs-text); text-decoration: none; }
991 .subscrpt-customer-name a:hover { color: var(--wpsubs-brand); }
992 .subscrpt-customer-line { font-size: 13px; margin-top: 3px; }
993 .subscrpt-customer-line a { color: var(--wpsubs-brand); text-decoration: none; }
994 .subscrpt-customer-line a:hover { text-decoration: underline; }
995
996 .subscrpt-subs-details .subscrpt-muted { color: var(--wpsubs-text-muted); font-size: 13px; margin: 0; }
997 .subscrpt-action-form .wpsubs-adv-select { width: 100%; }
998 .subscrpt-action-form .wpsubs-adv-select__trigger { width: 100%; }
999
1000 .subscrpt-upgrade-banner {
1001 display: flex;
1002 align-items: center;
1003 justify-content: space-between;
1004 gap: 16px;
1005 flex-wrap: wrap;
1006 padding: 16px;
1007 border: 1px solid var(--wpsubs-brand);
1008 border-radius: var(--wpsubs-radius-sm);
1009 background: var(--wpsubs-brand-light);
1010 }
1011 .subscrpt-upgrade-banner strong { color: var(--wpsubs-text); font-size: 14px; }
1012 .subscrpt-upgrade-banner p { margin: 4px 0 0; font-size: 13px; color: var(--wpsubs-text-muted); }
1013 </style>
1014
1015 <script>
1016 ( function () {
1017 'use strict';
1018
1019 var MONTHS = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
1020
1021 // Derive a "YYYY-MM" key from a row's date column.
1022 function monthKey( tr, dateCol ) {
1023 var cell = tr.children[ dateCol ];
1024 if ( ! cell ) {
1025 return '';
1026 }
1027 var d = new Date( cell.textContent.replace( ' - ', ' ' ).trim() );
1028 if ( isNaN( d.getTime() ) ) {
1029 return '';
1030 }
1031 return d.getFullYear() + '-' + ( '0' + ( d.getMonth() + 1 ) ).slice( -2 );
1032 }
1033
1034 function initBox( box ) {
1035 var tools = box.querySelector( '.subscrpt-card__tools' );
1036 var table = box.querySelector( 'table' );
1037 var tbody = table ? table.querySelector( 'tbody' ) : null;
1038 var rows = tbody ? Array.prototype.slice.call( tbody.querySelectorAll( 'tr' ) ) : [];
1039
1040 if ( ! rows.length ) {
1041 if ( tools ) {
1042 tools.style.display = 'none';
1043 }
1044 return;
1045 }
1046
1047 var dateCol = box.hasAttribute( 'data-date-col' ) ? parseInt( box.getAttribute( 'data-date-col' ), 10 ) : -1;
1048 var dateAdv = box.querySelector( '.subscrpt-filter-date' );
1049 var ppAdv = box.querySelector( '.subscrpt-filter-perpage' );
1050 var pager = box.querySelector( '.subscrpt-pager' );
1051 var colSpan = ( table.querySelectorAll( 'thead th' ).length ) || 1;
1052 var page = 1;
1053 var emptyRow = null;
1054
1055 function advValue( adv ) {
1056 var input = adv ? adv.querySelector( 'input[type="hidden"]' ) : null;
1057 return input ? input.value : '';
1058 }
1059
1060 var perPage = parseInt( advValue( ppAdv ), 10 ) || parseInt( box.getAttribute( 'data-per-page' ), 10 ) || 10;
1061
1062 // Inject month options into the date advanced-select (built from the data).
1063 if ( dateAdv && dateCol >= 0 ) {
1064 var menu = dateAdv.querySelector( '.wpsubs-adv-select__menu' );
1065 var seen = {};
1066 var months = [];
1067 rows.forEach( function ( tr ) {
1068 var key = monthKey( tr, dateCol );
1069 if ( key && ! seen[ key ] ) {
1070 seen[ key ] = true;
1071 months.push( key );
1072 }
1073 } );
1074 months.sort().reverse().forEach( function ( key ) {
1075 var parts = key.split( '-' );
1076 var btn = document.createElement( 'button' );
1077 btn.type = 'button';
1078 btn.className = 'wpsubs-adv-select__item';
1079 btn.setAttribute( 'data-value', key );
1080 btn.setAttribute( 'role', 'option' );
1081 var span = document.createElement( 'span' );
1082 span.className = 'wpsubs-adv-select__item-label';
1083 span.textContent = MONTHS[ parseInt( parts[1], 10 ) - 1 ] + ' ' + parts[0];
1084 btn.appendChild( span );
1085 if ( menu ) {
1086 menu.appendChild( btn );
1087 }
1088 } );
1089 }
1090
1091 function filtered() {
1092 var dm = advValue( dateAdv );
1093 if ( ! dm || dateCol < 0 ) {
1094 return rows;
1095 }
1096 return rows.filter( function ( tr ) { return monthKey( tr, dateCol ) === dm; } );
1097 }
1098
1099 function makeBtn( label, target, inert, active, disabled ) {
1100 var el = document.createElement( inert ? 'span' : 'button' );
1101 el.className = 'wpsubs-pagination__btn'
1102 + ( active ? ' wpsubs-pagination__btn--active' : '' )
1103 + ( disabled ? ' wpsubs-pagination__btn--disabled' : '' );
1104 el.innerHTML = label;
1105 if ( ! inert ) {
1106 el.type = 'button';
1107 el.addEventListener( 'click', function () { page = target; render(); } );
1108 }
1109 return el;
1110 }
1111
1112 // Pagination is always shown — a single page renders just "1".
1113 function renderPager( total, pages, start, end ) {
1114 if ( ! pager ) {
1115 return;
1116 }
1117 pager.hidden = false;
1118 pager.innerHTML = '';
1119
1120 var info = document.createElement( 'span' );
1121 info.className = 'wpsubs-pagination__info';
1122 info.textContent = 'Showing ' + ( total ? start + 1 : 0 ) + '–' + Math.min( end, total ) + ' of ' + total;
1123 pager.appendChild( info );
1124
1125 var nav = document.createElement( 'div' );
1126 nav.className = 'wpsubs-pagination__nav';
1127 nav.appendChild( makeBtn( '‹', page - 1, page <= 1, false, page <= 1 ) );
1128 for ( var p = 1; p <= pages; p++ ) {
1129 nav.appendChild( makeBtn( String( p ), p, p === page, p === page, false ) );
1130 }
1131 nav.appendChild( makeBtn( '›', page + 1, page >= pages, false, page >= pages ) );
1132 pager.appendChild( nav );
1133 }
1134
1135 function render() {
1136 var list = filtered();
1137 var pages = Math.max( 1, Math.ceil( list.length / perPage ) );
1138 if ( page > pages ) {
1139 page = pages;
1140 }
1141 var start = ( page - 1 ) * perPage;
1142 var end = start + perPage;
1143
1144 rows.forEach( function ( tr ) { tr.style.display = 'none'; } );
1145 list.slice( start, end ).forEach( function ( tr ) { tr.style.display = ''; } );
1146
1147 if ( ! list.length ) {
1148 if ( ! emptyRow ) {
1149 emptyRow = document.createElement( 'tr' );
1150 emptyRow.className = 'subscrpt-empty-row';
1151 var td = document.createElement( 'td' );
1152 td.colSpan = colSpan;
1153 td.style.textAlign = 'center';
1154 td.style.padding = '18px';
1155 td.textContent = 'No matching records.';
1156 emptyRow.appendChild( td );
1157 tbody.appendChild( emptyRow );
1158 }
1159 emptyRow.style.display = '';
1160 } else if ( emptyRow ) {
1161 emptyRow.style.display = 'none';
1162 }
1163
1164 renderPager( list.length, pages, start, end );
1165 }
1166
1167 if ( dateAdv ) {
1168 dateAdv.addEventListener( 'wpsubs:select', function () { page = 1; render(); } );
1169 }
1170 if ( ppAdv ) {
1171 ppAdv.addEventListener( 'wpsubs:select', function () { perPage = parseInt( advValue( ppAdv ), 10 ) || 10; page = 1; render(); } );
1172 }
1173
1174 render();
1175 }
1176
1177 document.querySelectorAll( '[data-subscrpt-paginate]' ).forEach( initBox );
1178 }() );
1179 </script>
1180