| 1 |
<?php |
| 2 |
/** |
| 3 |
* Subscription admin list view. |
| 4 |
* |
| 5 |
* @package SpringDevs\Subscription\Admin |
| 6 |
*/ |
| 7 |
|
| 8 |
if ( ! defined( 'ABSPATH' ) ) { |
| 9 |
exit; |
| 10 |
} |
| 11 |
|
| 12 |
if ( ! isset( $date_filter ) ) { |
| 13 |
$date_filter = ''; |
| 14 |
} |
| 15 |
|
| 16 |
$filters_active = ! empty( $status ) || ! empty( $date_filter ) || ! empty( $search ); |
| 17 |
$months = array(); |
| 18 |
for ( $i = 0; $i < 12; $i++ ) { |
| 19 |
$month = strtotime( "-$i month" ); |
| 20 |
$months[ date( 'Y-m', $month ) ] = date( 'F Y', $month ); |
| 21 |
} |
| 22 |
?> |
| 23 |
<div class="wp-subscription-admin-content list-page subscrpt-subs-list"> |
| 24 |
|
| 25 |
<!-- Page header --> |
| 26 |
<div style="margin-bottom:20px;"> |
| 27 |
<h1 style="font-size:1.375rem;font-weight:700;color:var(--wpsubs-text);margin:0 0 6px;line-height:1.2;"><?php esc_html_e( 'Subscriptions', 'subscription' ); ?></h1> |
| 28 |
<p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0 0 12px;line-height:1.5;"><?php esc_html_e( 'Manage all your WooCommerce subscriptions.', 'subscription' ); ?></p> |
| 29 |
<div style="border-top:1px dashed #d0d3d7;"></div> |
| 30 |
</div> |
| 31 |
|
| 32 |
<form method="post" id="subscriptions-form"> |
| 33 |
<?php wp_nonce_field( 'subscrpt_list_action' ); ?> |
| 34 |
<input type="hidden" name="page" value="wp-subscription" /> |
| 35 |
|
| 36 |
<!-- Toolbar --> |
| 37 |
<div class="wpsubs-toolbar"> |
| 38 |
|
| 39 |
<?php |
| 40 |
wpsubs_render_adv_select( |
| 41 |
array( |
| 42 |
'name' => 'subscrpt_status', |
| 43 |
'placeholder' => __( 'All Status', 'subscription' ), |
| 44 |
'value' => $status, |
| 45 |
'options' => array( |
| 46 |
array( |
| 47 |
'value' => 'active', |
| 48 |
'label' => __( 'Active', 'subscription' ), |
| 49 |
), |
| 50 |
array( |
| 51 |
'value' => 'pending', |
| 52 |
'label' => __( 'Pending', 'subscription' ), |
| 53 |
), |
| 54 |
array( |
| 55 |
'value' => 'cancelled', |
| 56 |
'label' => __( 'Cancelled', 'subscription' ), |
| 57 |
), |
| 58 |
array( |
| 59 |
'value' => 'expired', |
| 60 |
'label' => __( 'Expired', 'subscription' ), |
| 61 |
), |
| 62 |
array( |
| 63 |
'value' => 'draft', |
| 64 |
'label' => __( 'Draft', 'subscription' ), |
| 65 |
), |
| 66 |
array( |
| 67 |
'value' => 'trash', |
| 68 |
'label' => __( 'Trash', 'subscription' ), |
| 69 |
), |
| 70 |
), |
| 71 |
) |
| 72 |
); |
| 73 |
?> |
| 74 |
|
| 75 |
<?php |
| 76 |
$date_options = array(); |
| 77 |
foreach ( $months as $val => $label ) { |
| 78 |
$date_options[] = array( |
| 79 |
'value' => $val, |
| 80 |
'label' => $label, |
| 81 |
); |
| 82 |
} |
| 83 |
wpsubs_render_adv_select( |
| 84 |
array( |
| 85 |
'name' => 'date_filter', |
| 86 |
'placeholder' => __( 'All Dates', 'subscription' ), |
| 87 |
'value' => $date_filter, |
| 88 |
'options' => $date_options, |
| 89 |
) |
| 90 |
); |
| 91 |
?> |
| 92 |
|
| 93 |
<div class="wpsubs-search"> |
| 94 |
<div class="wpsubs-input-wrap wpsubs-input-wrap--icon-l"> |
| 95 |
<svg class="wpsubs-input-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z"/></svg> |
| 96 |
<input type="search" name="s" value="<?php echo esc_attr( $search ); ?>" placeholder="<?php esc_attr_e( 'Search subscriptions...', 'subscription' ); ?>" class="wpsubs-input" /> |
| 97 |
</div> |
| 98 |
</div> |
| 99 |
|
| 100 |
<button type="submit" name="filter_action" value="filter" class="wpsubs-btn wpsubs-btn--outline"> |
| 101 |
<?php esc_html_e( 'Filter', 'subscription' ); ?> |
| 102 |
</button> |
| 103 |
|
| 104 |
<?php if ( $filters_active ) : ?> |
| 105 |
<a href="<?php echo esc_url( remove_query_arg( array( 'subscrpt_status', 'date_filter', 's', 'title', 'paged' ) ) ); ?>" class="wpsubs-btn wpsubs-btn--outline"> |
| 106 |
<?php esc_html_e( 'Clear', 'subscription' ); ?> |
| 107 |
</a> |
| 108 |
<?php endif; ?> |
| 109 |
|
| 110 |
<div class="wpsubs-toolbar__spacer"></div> |
| 111 |
|
| 112 |
<?php |
| 113 |
$current_per_page = isset( $_GET['per_page'] ) ? intval( wp_unslash( $_GET['per_page'] ) ) : 20; |
| 114 |
wpsubs_render_adv_select( |
| 115 |
array( |
| 116 |
'name' => 'per_page', |
| 117 |
'value' => (string) $current_per_page, |
| 118 |
'options' => array( |
| 119 |
array( |
| 120 |
'value' => '10', |
| 121 |
'label' => __( '10 / page', 'subscription' ), |
| 122 |
), |
| 123 |
array( |
| 124 |
'value' => '20', |
| 125 |
'label' => __( '20 / page', 'subscription' ), |
| 126 |
), |
| 127 |
array( |
| 128 |
'value' => '50', |
| 129 |
'label' => __( '50 / page', 'subscription' ), |
| 130 |
), |
| 131 |
array( |
| 132 |
'value' => '100', |
| 133 |
'label' => __( '100 / page', 'subscription' ), |
| 134 |
), |
| 135 |
), |
| 136 |
'align' => 'right', |
| 137 |
'id' => 'wpsubs-per-page-select', |
| 138 |
) |
| 139 |
); |
| 140 |
?> |
| 141 |
|
| 142 |
<?php |
| 143 |
// Bulk action advanced-select + hidden submit. |
| 144 |
$bulk_options = 'trash' === $status |
| 145 |
? array( |
| 146 |
array( |
| 147 |
'value' => 'restore', |
| 148 |
'label' => __( 'Restore selected', 'subscription' ), |
| 149 |
), |
| 150 |
array( |
| 151 |
'value' => 'delete', |
| 152 |
'label' => __( 'Delete permanently', 'subscription' ), |
| 153 |
'danger' => true, |
| 154 |
'confirm' => __( 'Delete selected subscriptions permanently? This cannot be undone.', 'subscription' ), |
| 155 |
), |
| 156 |
) |
| 157 |
: array( |
| 158 |
array( |
| 159 |
'value' => 'trash', |
| 160 |
'label' => __( 'Move to trash', 'subscription' ), |
| 161 |
'danger' => true, |
| 162 |
'confirm' => __( 'Move selected subscriptions to trash?', 'subscription' ), |
| 163 |
), |
| 164 |
); |
| 165 |
|
| 166 |
wpsubs_render_adv_select( |
| 167 |
array( |
| 168 |
'name' => 'action', |
| 169 |
'placeholder' => __( 'Select Action', 'subscription' ), |
| 170 |
'value' => '-1', |
| 171 |
'options' => $bulk_options, |
| 172 |
'id' => 'wpsubs-bulk-action-select', |
| 173 |
'align' => 'right', |
| 174 |
) |
| 175 |
); |
| 176 |
?> |
| 177 |
<button type="submit" name="bulk_action" value="1" id="wpsubs-bulk-action-submit" style="display:none;" aria-hidden="true"></button> |
| 178 |
|
| 179 |
<?php |
| 180 |
if ( 'trash' === $status && ! empty( $subscriptions ) ) : |
| 181 |
$empty_trash_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription&action=clean_trash&sub_id=all' ), 'wpsubs_action_clean_trash' ); |
| 182 |
?> |
| 183 |
<a href="<?php echo esc_url( $empty_trash_url ); ?>" class="wpsubs-btn wpsubs-btn--danger" onclick="return confirm('<?php esc_attr_e( 'Permanently delete all trash items? This cannot be undone.', 'subscription' ); ?>')"> |
| 184 |
<?php esc_html_e( 'Empty Trash', 'subscription' ); ?> |
| 185 |
</a> |
| 186 |
<?php endif; ?> |
| 187 |
|
| 188 |
</div><!-- /.wpsubs-toolbar --> |
| 189 |
|
| 190 |
<!-- Table card --> |
| 191 |
<h2 class="screen-reader-text"><?php esc_html_e( 'Subscriptions list', 'subscription' ); ?></h2> |
| 192 |
<div class="wpsubs-table-card"> |
| 193 |
<table class="wpsubs-table"> |
| 194 |
<thead> |
| 195 |
<tr> |
| 196 |
<th class="wpsubs-col--check"> |
| 197 |
<input type="checkbox" id="cb-select-all" class="wpsubs-checkbox"> |
| 198 |
</th> |
| 199 |
<th style="width:1%;white-space:nowrap;"><?php esc_html_e( 'Status', 'subscription' ); ?></th> |
| 200 |
<th><?php esc_html_e( 'Subscription', 'subscription' ); ?></th> |
| 201 |
<th><?php esc_html_e( 'Customer', 'subscription' ); ?></th> |
| 202 |
<th><?php esc_html_e( 'Amount', 'subscription' ); ?></th> |
| 203 |
<th><?php esc_html_e( 'Started', 'subscription' ); ?></th> |
| 204 |
<th><?php esc_html_e( 'Next Renewal', 'subscription' ); ?></th> |
| 205 |
<th class="wpsubs-col--actions"><?php esc_html_e( 'Actions', 'subscription' ); ?></th> |
| 206 |
</tr> |
| 207 |
</thead> |
| 208 |
<tbody> |
| 209 |
|
| 210 |
<?php if ( ! empty( $subscriptions ) ) : ?> |
| 211 |
<?php |
| 212 |
foreach ( $subscriptions as $subscription ) : |
| 213 |
$subscription_id = $subscription->ID; |
| 214 |
$subscription_data = SpringDevs\Subscription\Illuminate\Helper::get_subscription_data( $subscription_id ); |
| 215 |
|
| 216 |
$subscrpt_status = $subscription_data['status'] ?? ''; |
| 217 |
$subscrpt_status = empty( $subscrpt_status ) ? get_post_status( $subscription_id ) : $subscrpt_status; |
| 218 |
|
| 219 |
$order_id = $subscription_data['order']['order_id'] ?? 0; |
| 220 |
$order_item_id = $subscription_data['order']['order_item_id'] ?? 0; |
| 221 |
$order = $order_id ? wc_get_order( $order_id ) : null; |
| 222 |
$order_item = $order ? $order->get_item( $order_item_id ) : null; |
| 223 |
$product_name = $order_item ? $order_item->get_name() : '-'; |
| 224 |
|
| 225 |
$product_id = $subscription_data['product']['variation_id'] ?: ( $subscription_data['product']['product_id'] ?? 0 ); |
| 226 |
$product_url = $product_id ? get_edit_post_link( $product_id ) : ''; |
| 227 |
|
| 228 |
$customer = $order ? $order->get_formatted_billing_full_name() : '-'; |
| 229 |
$customer_id = $order ? $order->get_customer_id() : 0; |
| 230 |
$customer_url = $customer_id ? admin_url( 'user-edit.php?user_id=' . $customer_id ) : ''; |
| 231 |
$customer_email = $order ? $order->get_billing_email() : ''; |
| 232 |
|
| 233 |
$start_date = $subscription_data['start_date'] ? strtotime( $subscription_data['start_date'] ) : 0; |
| 234 |
$renewal_date = $subscription_data['next_date'] ? strtotime( $subscription_data['next_date'] ) : 0; |
| 235 |
|
| 236 |
$is_trash = 'trash' === $subscription->post_status; |
| 237 |
$is_grace_period = isset( $subscription_data['grace_period'] ); |
| 238 |
$grace_remaining = $subscription_data['grace_period']['remaining_days'] ?? 0; |
| 239 |
|
| 240 |
// Amount + timing |
| 241 |
$quantity = $order_item ? (int) $order_item->get_quantity() : 1; |
| 242 |
$price = '' !== ( $subscription_data['price'] ?? '' ) |
| 243 |
? (float) $subscription_data['price'] * max( 1, $quantity ) |
| 244 |
: ''; |
| 245 |
$timing_per = $subscription_data['schedule']['timing_per'] ?? ''; |
| 246 |
$timing_option = $subscription_data['schedule']['timing_option'] ?? ''; |
| 247 |
$timing_label = ''; |
| 248 |
if ( $timing_per && $timing_option ) { |
| 249 |
$timing_label = ( (int) $timing_per > 1 ) |
| 250 |
? $timing_per . ' ' . $timing_option . 's' |
| 251 |
: $timing_option; |
| 252 |
} |
| 253 |
|
| 254 |
// Avatar |
| 255 |
$name_parts = array_values( array_filter( explode( ' ', trim( $customer ) ) ) ); |
| 256 |
$initials = '?'; |
| 257 |
if ( $name_parts ) { |
| 258 |
$initials = ''; |
| 259 |
foreach ( array_slice( $name_parts, 0, 2 ) as $part ) { |
| 260 |
$initials .= strtoupper( $part[0] ); |
| 261 |
} |
| 262 |
} |
| 263 |
$color_slot = ord( strtolower( $initials[0] ?? 'a' ) ) % 8; |
| 264 |
|
| 265 |
// Action URLs |
| 266 |
$nonce_action = 'wpsubs_action_' . $subscription->ID; |
| 267 |
$view_url = get_edit_post_link( $subscription->ID ); |
| 268 |
$trash_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription&action=trash&sub_id=' . $subscription->ID ), $nonce_action ); |
| 269 |
$delete_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription&action=delete&sub_id=' . $subscription->ID ), $nonce_action ); |
| 270 |
$restore_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription&action=restore&sub_id=' . $subscription->ID ), $nonce_action ); |
| 271 |
|
| 272 |
// Status badge |
| 273 |
$badge_mod_map = array( |
| 274 |
'active' => 'active', |
| 275 |
'pending' => 'pending', |
| 276 |
'pe_cancelled' => 'pending-cancel', |
| 277 |
'cancelled' => 'cancelled', |
| 278 |
'expired' => 'expired', |
| 279 |
'draft' => 'draft', |
| 280 |
'trash' => 'trash', |
| 281 |
); |
| 282 |
$badge_mod = $badge_mod_map[ $subscrpt_status ] ?? 'expired'; |
| 283 |
$verbose_status = SpringDevs\Subscription\Illuminate\Helper::get_verbose_status( $subscrpt_status ); |
| 284 |
|
| 285 |
if ( $is_grace_period && $grace_remaining > 0 ) { |
| 286 |
$badge_mod = 'active'; |
| 287 |
$verbose_status = __( 'Active', 'subscription' ); |
| 288 |
} |
| 289 |
?> |
| 290 |
<tr> |
| 291 |
<!-- Checkbox --> |
| 292 |
<td class="wpsubs-col--check"> |
| 293 |
<input type="checkbox" name="subscription_ids[]" value="<?php echo esc_attr( $subscription->ID ); ?>" class="wpsubs-checkbox wpsubs-row-check"> |
| 294 |
</td> |
| 295 |
|
| 296 |
<!-- Status badge --> |
| 297 |
<td style="white-space:nowrap;"> |
| 298 |
<span class="wpsubs-badge wpsubs-badge--<?php echo esc_attr( $badge_mod ); ?>"> |
| 299 |
<?php echo esc_html( $verbose_status ); ?> |
| 300 |
<?php if ( $is_grace_period && $grace_remaining > 0 ) : ?> |
| 301 |
<span class="dashicons dashicons-warning" style="font-size:11px;width:11px;height:11px;color:#d97706;" title="<?php echo esc_attr( sprintf( __( '%d days remaining in grace period', 'subscription' ), $grace_remaining ) ); ?>"></span> |
| 302 |
<?php endif; ?> |
| 303 |
</span> |
| 304 |
</td> |
| 305 |
|
| 306 |
<!-- Subscription: product name + subscription ID --> |
| 307 |
<td> |
| 308 |
<a href="<?php echo esc_url( $view_url ); ?>" class="wpsubs-cell-title" style="font-weight:600;"><?php echo esc_html( $product_name ); ?></a> |
| 309 |
<span class="wpsubs-cell-id">#<?php echo (int) $subscription->ID; ?></span> |
| 310 |
</td> |
| 311 |
|
| 312 |
<!-- Customer avatar + name + email --> |
| 313 |
<td> |
| 314 |
<div class="wpsubs-customer"> |
| 315 |
<div class="wpsubs-avatar" data-color="<?php echo (int) $color_slot; ?>"> |
| 316 |
<?php echo esc_html( $initials ); ?> |
| 317 |
</div> |
| 318 |
<div class="wpsubs-customer__info"> |
| 319 |
<?php if ( $customer_url ) : ?> |
| 320 |
<a href="<?php echo esc_url( $customer_url ); ?>" class="wpsubs-customer__name"><?php echo esc_html( $customer ); ?></a> |
| 321 |
<?php else : ?> |
| 322 |
<span class="wpsubs-customer__name"><?php echo esc_html( $customer ); ?></span> |
| 323 |
<?php endif; ?> |
| 324 |
<?php if ( $customer_email ) : ?> |
| 325 |
<span class="wpsubs-customer__sub"><?php echo esc_html( $customer_email ); ?></span> |
| 326 |
<?php endif; ?> |
| 327 |
</div> |
| 328 |
</div> |
| 329 |
</td> |
| 330 |
|
| 331 |
<!-- Subscription amount + timing --> |
| 332 |
<td style="white-space:nowrap;"> |
| 333 |
<?php if ( '' !== $price ) : ?> |
| 334 |
<span class="wpsubs-cell-title" style="font-variant-numeric:tabular-nums;"><?php echo wp_kses_post( wc_price( $price ) ); ?></span> |
| 335 |
<?php if ( $quantity > 1 ) : ?> |
| 336 |
<span class="wpsubs-cell-id"><?php echo wp_kses_post( wc_price( (float) $subscription_data['price'] ) ); ?> × <?php echo (int) $quantity; ?></span> |
| 337 |
<?php endif; ?> |
| 338 |
<?php if ( $timing_label ) : ?> |
| 339 |
<span class="wpsubs-cell-id">/ <?php echo esc_html( $timing_label ); ?></span> |
| 340 |
<?php endif; ?> |
| 341 |
<?php else : ?> |
| 342 |
<span class="wpsubs-cell--muted">—</span> |
| 343 |
<?php endif; ?> |
| 344 |
</td> |
| 345 |
|
| 346 |
<!-- Start date --> |
| 347 |
<td class="wpsubs-col--nowrap"> |
| 348 |
<?php if ( $start_date ) : ?> |
| 349 |
<span class="wpsubs-cell-title"><?php echo esc_html( wp_date( 'n/j/Y', $start_date ) ); ?></span> |
| 350 |
<span class="wpsubs-cell-id"><?php echo esc_html( wp_date( 'g:i a', $start_date ) ); ?></span> |
| 351 |
<?php else : ?> |
| 352 |
<span class="wpsubs-cell--muted">—</span> |
| 353 |
<?php endif; ?> |
| 354 |
</td> |
| 355 |
|
| 356 |
<!-- Renewal date --> |
| 357 |
<td class="wpsubs-col--nowrap"> |
| 358 |
<?php if ( $renewal_date ) : ?> |
| 359 |
<span class="wpsubs-cell-title"><?php echo esc_html( wp_date( 'n/j/Y', $renewal_date ) ); ?></span> |
| 360 |
<span class="wpsubs-cell-id"><?php echo esc_html( wp_date( 'g:i a', $renewal_date ) ); ?></span> |
| 361 |
<?php else : ?> |
| 362 |
<span class="wpsubs-cell--muted">—</span> |
| 363 |
<?php endif; ?> |
| 364 |
</td> |
| 365 |
|
| 366 |
<!-- Actions --> |
| 367 |
<td class="wpsubs-cell--actions"> |
| 368 |
<div class="wpsubs-row-actions"> |
| 369 |
<button type="button" class="wpsubs-row-actions__trigger" aria-label="<?php esc_attr_e( 'Row actions', 'subscription' ); ?>">···</button> |
| 370 |
<div class="wpsubs-dropdown"> |
| 371 |
<a href="<?php echo esc_url( $view_url ); ?>" class="wpsubs-dropdown__item"><?php esc_html_e( 'View / Edit', 'subscription' ); ?></a> |
| 372 |
<div class="wpsubs-dropdown__divider"></div> |
| 373 |
<?php if ( ! $is_trash ) : ?> |
| 374 |
<a href="<?php echo esc_url( $trash_url ); ?>" class="wpsubs-dropdown__item wpsubs-dropdown__item--danger" onclick="return confirm('<?php esc_attr_e( 'Move this subscription to trash?', 'subscription' ); ?>')"><?php esc_html_e( 'Trash', 'subscription' ); ?></a> |
| 375 |
<?php else : ?> |
| 376 |
<a href="<?php echo esc_url( $restore_url ); ?>" class="wpsubs-dropdown__item"><?php esc_html_e( 'Restore', 'subscription' ); ?></a> |
| 377 |
<a href="<?php echo esc_url( $delete_url ); ?>" class="wpsubs-dropdown__item wpsubs-dropdown__item--danger" onclick="return confirm('<?php esc_attr_e( 'Delete permanently? This cannot be undone.', 'subscription' ); ?>')"><?php esc_html_e( 'Delete Permanently', 'subscription' ); ?></a> |
| 378 |
<?php endif; ?> |
| 379 |
</div> |
| 380 |
</div> |
| 381 |
</td> |
| 382 |
</tr> |
| 383 |
<?php endforeach; ?> |
| 384 |
|
| 385 |
<?php else : ?> |
| 386 |
<tr> |
| 387 |
<td colspan="8"> |
| 388 |
<div class="wpsubs-empty"> |
| 389 |
<div class="wpsubs-empty__icon">📋</div> |
| 390 |
<div class="wpsubs-empty__title"><?php esc_html_e( 'No subscriptions found', 'subscription' ); ?></div> |
| 391 |
<div class="wpsubs-empty__desc"><?php esc_html_e( 'Subscriptions will appear here once customers complete checkout.', 'subscription' ); ?></div> |
| 392 |
</div> |
| 393 |
</td> |
| 394 |
</tr> |
| 395 |
<?php endif; ?> |
| 396 |
|
| 397 |
</tbody> |
| 398 |
</table> |
| 399 |
|
| 400 |
<!-- Pagination inside table card --> |
| 401 |
<?php |
| 402 |
$start_item = $total > 0 ? ( ( $paged - 1 ) * $per_page ) + 1 : 0; |
| 403 |
$end_item = min( $paged * $per_page, $total ); |
| 404 |
$base_url = remove_query_arg( 'paged' ); |
| 405 |
$total_pages = max( 1, $max_num_pages ); |
| 406 |
|
| 407 |
// Build page range: first, last, current ± 1 neighbours, ellipsis for gaps. |
| 408 |
$nearby = array(); |
| 409 |
for ( $i = 1; $i <= $total_pages; $i++ ) { |
| 410 |
if ( $i === 1 || $i === $total_pages || abs( $i - $paged ) <= 1 ) { |
| 411 |
$nearby[] = $i; |
| 412 |
} |
| 413 |
} |
| 414 |
$page_range = array(); |
| 415 |
$prev_page = null; |
| 416 |
foreach ( $nearby as $p ) { |
| 417 |
if ( null !== $prev_page ) { |
| 418 |
$gap = $p - $prev_page; |
| 419 |
if ( $gap === 2 ) { |
| 420 |
$page_range[] = $prev_page + 1; // single hidden page — show it directly |
| 421 |
} elseif ( $gap > 2 ) { |
| 422 |
$page_range[] = null; // ellipsis |
| 423 |
} |
| 424 |
} |
| 425 |
$page_range[] = $p; |
| 426 |
$prev_page = $p; |
| 427 |
} |
| 428 |
?> |
| 429 |
<div class="wpsubs-pagination"> |
| 430 |
<span class="wpsubs-pagination__info"> |
| 431 |
<?php |
| 432 |
echo esc_html( |
| 433 |
sprintf( |
| 434 |
/* translators: 1: first item number, 2: last item number, 3: total count */ |
| 435 |
__( 'Showing %1$s–%2$s of %3$s subscriptions', 'subscription' ), |
| 436 |
number_format_i18n( $start_item ), |
| 437 |
number_format_i18n( $end_item ), |
| 438 |
number_format_i18n( $total ) |
| 439 |
) |
| 440 |
); |
| 441 |
?> |
| 442 |
</span> |
| 443 |
<div class="wpsubs-pagination__nav"> |
| 444 |
|
| 445 |
<?php // Previous button ?> |
| 446 |
<?php if ( $paged > 1 ) : ?> |
| 447 |
<a href=" |
| 448 |
<?php |
| 449 |
echo esc_url( |
| 450 |
add_query_arg( |
| 451 |
array( |
| 452 |
'paged' => $paged - 1, |
| 453 |
'per_page' => $per_page, |
| 454 |
), |
| 455 |
$base_url |
| 456 |
) |
| 457 |
); |
| 458 |
?> |
| 459 |
" class="wpsubs-pagination__btn" aria-label="<?php esc_attr_e( 'Previous page', 'subscription' ); ?>">‹</a> |
| 460 |
<?php else : ?> |
| 461 |
<span class="wpsubs-pagination__btn wpsubs-pagination__btn--disabled" aria-hidden="true">‹</span> |
| 462 |
<?php endif; ?> |
| 463 |
|
| 464 |
<?php // Numbered pages + ellipsis ?> |
| 465 |
<?php foreach ( $page_range as $p ) : ?> |
| 466 |
<?php if ( null === $p ) : ?> |
| 467 |
<span class="wpsubs-pagination__ellipsis" aria-hidden="true">…</span> |
| 468 |
<?php elseif ( $p === $paged ) : ?> |
| 469 |
<span class="wpsubs-pagination__btn wpsubs-pagination__btn--active" aria-current="page"><?php echo (int) $p; ?></span> |
| 470 |
<?php else : ?> |
| 471 |
<a href=" |
| 472 |
<?php |
| 473 |
echo esc_url( |
| 474 |
add_query_arg( |
| 475 |
array( |
| 476 |
'paged' => $p, |
| 477 |
'per_page' => $per_page, |
| 478 |
), |
| 479 |
$base_url |
| 480 |
) |
| 481 |
); |
| 482 |
?> |
| 483 |
" class="wpsubs-pagination__btn"><?php echo (int) $p; ?></a> |
| 484 |
<?php endif; ?> |
| 485 |
<?php endforeach; ?> |
| 486 |
|
| 487 |
<?php // Next button ?> |
| 488 |
<?php if ( $paged < $total_pages ) : ?> |
| 489 |
<a href=" |
| 490 |
<?php |
| 491 |
echo esc_url( |
| 492 |
add_query_arg( |
| 493 |
array( |
| 494 |
'paged' => $paged + 1, |
| 495 |
'per_page' => $per_page, |
| 496 |
), |
| 497 |
$base_url |
| 498 |
) |
| 499 |
); |
| 500 |
?> |
| 501 |
" class="wpsubs-pagination__btn" aria-label="<?php esc_attr_e( 'Next page', 'subscription' ); ?>">›</a> |
| 502 |
<?php else : ?> |
| 503 |
<span class="wpsubs-pagination__btn wpsubs-pagination__btn--disabled" aria-hidden="true">›</span> |
| 504 |
<?php endif; ?> |
| 505 |
|
| 506 |
</div> |
| 507 |
</div> |
| 508 |
|
| 509 |
</div><!-- /.wpsubs-table-card --> |
| 510 |
|
| 511 |
</form> |
| 512 |
|
| 513 |
</div> |
| 514 |
|
| 515 |
<script> |
| 516 |
( function () { |
| 517 |
var selectAll = document.getElementById( 'cb-select-all' ); |
| 518 |
var form = document.getElementById( 'subscriptions-form' ); |
| 519 |
|
| 520 |
function rowChecks() { |
| 521 |
return form ? Array.from( form.querySelectorAll( '.wpsubs-row-check' ) ) : []; |
| 522 |
} |
| 523 |
|
| 524 |
function syncSelectAll() { |
| 525 |
if ( ! selectAll ) return; |
| 526 |
var checks = rowChecks(); |
| 527 |
var checked = checks.filter( function ( cb ) { return cb.checked; } ); |
| 528 |
checks.forEach( function ( cb ) { |
| 529 |
var row = cb.closest( 'tr' ); |
| 530 |
if ( row ) row.classList.toggle( 'wpsubs-row--selected', cb.checked ); |
| 531 |
} ); |
| 532 |
selectAll.indeterminate = checked.length > 0 && checked.length < checks.length; |
| 533 |
selectAll.checked = checks.length > 0 && checked.length === checks.length; |
| 534 |
} |
| 535 |
|
| 536 |
if ( selectAll ) { |
| 537 |
selectAll.addEventListener( 'change', function () { |
| 538 |
rowChecks().forEach( function ( cb ) { cb.checked = selectAll.checked; } ); |
| 539 |
syncSelectAll(); |
| 540 |
} ); |
| 541 |
} |
| 542 |
|
| 543 |
if ( form ) { |
| 544 |
form.addEventListener( 'change', function ( e ) { |
| 545 |
if ( e.target.classList.contains( 'wpsubs-row-check' ) ) syncSelectAll(); |
| 546 |
} ); |
| 547 |
} |
| 548 |
|
| 549 |
// ── Advanced-select event handlers ────────────────────────── |
| 550 |
var bulkSubmit = document.getElementById( 'wpsubs-bulk-action-submit' ); |
| 551 |
document.addEventListener( 'wpsubs:select', function ( e ) { |
| 552 |
var id = e.target && e.target.id; |
| 553 |
|
| 554 |
// Bulk action: submit via hidden button |
| 555 |
if ( id === 'wpsubs-bulk-action-select' && bulkSubmit ) { |
| 556 |
bulkSubmit.click(); |
| 557 |
return; |
| 558 |
} |
| 559 |
|
| 560 |
// Per-page: auto-submit as a filter action |
| 561 |
if ( id === 'wpsubs-per-page-select' && form ) { |
| 562 |
var fi = document.createElement( 'input' ); |
| 563 |
fi.type = 'hidden'; |
| 564 |
fi.name = 'filter_action'; |
| 565 |
fi.value = 'filter'; |
| 566 |
form.appendChild( fi ); |
| 567 |
form.submit(); |
| 568 |
} |
| 569 |
} ); |
| 570 |
|
| 571 |
// ── Row action dropdowns ───────────────────────────────────── |
| 572 |
function closeAllRowMenus() { |
| 573 |
document.querySelectorAll( '.wpsubs-row-actions--open' ).forEach( function ( el ) { |
| 574 |
el.classList.remove( 'wpsubs-row-actions--open' ); |
| 575 |
var d = el.querySelector( '.wpsubs-dropdown' ); |
| 576 |
if ( d ) d.classList.remove( 'wpsubs-dropdown--open' ); |
| 577 |
} ); |
| 578 |
} |
| 579 |
|
| 580 |
document.addEventListener( 'click', function ( e ) { |
| 581 |
var trigger = e.target.closest( '.wpsubs-row-actions__trigger' ); |
| 582 |
|
| 583 |
document.querySelectorAll( '.wpsubs-row-actions--open' ).forEach( function ( el ) { |
| 584 |
if ( el !== ( trigger && trigger.closest( '.wpsubs-row-actions' ) ) ) { |
| 585 |
el.classList.remove( 'wpsubs-row-actions--open' ); |
| 586 |
var d = el.querySelector( '.wpsubs-dropdown' ); |
| 587 |
if ( d ) d.classList.remove( 'wpsubs-dropdown--open' ); |
| 588 |
} |
| 589 |
} ); |
| 590 |
|
| 591 |
if ( ! trigger ) return; |
| 592 |
|
| 593 |
e.stopPropagation(); |
| 594 |
var wrapper = trigger.closest( '.wpsubs-row-actions' ); |
| 595 |
var dropdown = wrapper && wrapper.querySelector( '.wpsubs-dropdown' ); |
| 596 |
if ( ! wrapper || ! dropdown ) return; |
| 597 |
|
| 598 |
var isOpen = wrapper.classList.contains( 'wpsubs-row-actions--open' ); |
| 599 |
wrapper.classList.toggle( 'wpsubs-row-actions--open', ! isOpen ); |
| 600 |
dropdown.classList.toggle( 'wpsubs-dropdown--open', ! isOpen ); |
| 601 |
} ); |
| 602 |
|
| 603 |
document.addEventListener( 'keydown', function ( e ) { |
| 604 |
if ( e.key === 'Escape' ) closeAllRowMenus(); |
| 605 |
} ); |
| 606 |
|
| 607 |
syncSelectAll(); |
| 608 |
}() ); |
| 609 |
</script> |
| 610 |
|