| @@ -1,140 +1,557 @@ | ||
| 1 | -<?php if (!isset($date_filter)) { $date_filter = ''; } ?> | |
| 2 | 1 | <?php |
| 3 | -// Determine if filters are active | |
| 4 | -$filters_active = !empty($status) || !empty($date_filter) || !empty($search); | |
| 5 | -$months = []; | |
| 6 | -for ($i = 0; $i < 12; $i++) { | |
| 7 | - $month = strtotime("-$i month"); | |
| 8 | - $months[date('Y-m', $month)] = date('F Y', $month); | |
| 2 | +/** | |
| 3 | + * Subscription admin list view. | |
| 4 | + * | |
| 5 | + * @package SpringDevs\Subscription\Admin | |
| 6 | + */ | |
| 7 | + | |
| 8 | +if ( ! defined( 'ABSPATH' ) ) { | |
| 9 | + exit; | |
| 9 | 10 | } |
| 11 | + | |
| 12 | +if ( ! isset( $date_filter ) ) { | |
| 13 | + $date_filter = ''; | |
| 14 | +} | |
| 15 | +if ( ! isset( $renewal_due ) ) { | |
| 16 | + $renewal_due = 0; | |
| 17 | +} | |
| 18 | + | |
| 19 | +$filters_active = ! empty( $status ) || ! empty( $date_filter ) || ! empty( $search ) || ! empty( $renewal_due ); | |
| 20 | + | |
| 21 | +// The last twelve of the store's months, counted back from the 1st: stepping | |
| 22 | +// back from today skips a month on the 29th–31st (31 Oct − 1 month is 1 Oct). | |
| 23 | +// The store's timezone, because the list filters on the local post date. | |
| 24 | +$months = array(); | |
| 25 | +$this_month = ( new DateTimeImmutable( 'now', wp_timezone() ) )->modify( 'first day of this month' )->setTime( 0, 0 ); | |
| 26 | +for ( $i = 0; $i < 12; $i++ ) { | |
| 27 | + $month = strtotime( "-$i month" ); | |
| 28 | + $months[ gmdate( 'Y-m', $month ) ] = wp_date( 'F Y', $month, new \DateTimeZone( 'UTC' ) ); | |
| 29 | +} | |
| 10 | 30 | ?> |
| 11 | 31 | <div class="wp-subscription-admin-content list-page"> |
| 12 | - <div class="wp-subscription-list-title"><h1 class="wp-heading-inline">Subscriptions</h1></div> | |
| 13 | - <div class="wp-subscription-list-header"> | |
| 14 | - <form method="get"> | |
| 15 | - <input type="hidden" name="page" value="wp-subscription" /> | |
| 16 | - <select name="subscrpt_status" value="<?php echo esc_attr($status); ?>"> | |
| 17 | - <option value=""><?php esc_html_e('All Status', 'wp_subscription'); ?></option> | |
| 18 | - <option value="active" <?php selected($status, 'active'); ?>><?php esc_html_e('Active', 'wp_subscription'); ?></option> | |
| 19 | - <option value="cancelled" <?php selected($status, 'cancelled'); ?>><?php esc_html_e('Cancelled', 'wp_subscription'); ?></option> | |
| 20 | - <option value="draft" <?php selected($status, 'draft'); ?>><?php esc_html_e('Draft', 'wp_subscription'); ?></option> | |
| 21 | - </select> | |
| 22 | - <select name="date_filter" value="<?php echo esc_attr($date_filter); ?>"> | |
| 23 | - <option value=""><?php esc_html_e('All Dates', 'wp_subscription'); ?></option> | |
| 24 | - <?php foreach ($months as $val => $label): ?> | |
| 25 | - <option value="<?php echo esc_attr($val); ?>" <?php selected($date_filter, $val); ?>><?php echo esc_html($label); ?></option> | |
| 26 | - <?php endforeach; ?> | |
| 27 | - </select> | |
| 28 | - <input type="search" name="s" value="<?php echo esc_attr($search); ?>" placeholder="<?php esc_attr_e('Search by subscription ID...', 'wp_subscription'); ?>" /> | |
| 29 | - <select name="per_page"> | |
| 30 | - <?php foreach ([10, 20, 50, 100] as $n): ?> | |
| 31 | - <option value="<?php echo $n; ?>" <?php selected(isset($_GET['per_page']) ? intval($_GET['per_page']) : 20, $n); ?>><?php echo $n; ?> per page</option> | |
| 32 | - <?php endforeach; ?> | |
| 33 | - </select> | |
| 34 | - <button type="submit" class="button">Search</button> | |
| 35 | - <?php if ($filters_active): ?> | |
| 36 | - <a href="<?php echo esc_url(remove_query_arg(['subscrpt_status','date_filter','s','title','paged'])); ?>" class="button">Reset</a> | |
| 37 | - <?php endif; ?> | |
| 38 | - <?php if ($filters_active): ?> | |
| 39 | - <span>Filters applied</span> | |
| 40 | - <?php endif; ?> | |
| 41 | - </form> | |
| 42 | - </div> | |
| 43 | - <h2 class="screen-reader-text">Subscriptions list</h2> | |
| 44 | - <table class="wp-list-table widefat fixed striped wp-subscription-modern-table"> | |
| 45 | - <thead> | |
| 46 | - <tr> | |
| 47 | - <th style="width:200px;">ID</th> | |
| 48 | - <th style="min-width:320px;">Title</th> | |
| 49 | - <th style="width:200px;">Customer</th> | |
| 50 | - <th style="width:100px;">Start Date</th> | |
| 51 | - <th style="width:100px;">Renewal Date</th> | |
| 52 | - <th style="width:80px;">Status</th> | |
| 53 | - <th style="width:80px;">Actions</th> | |
| 54 | - </tr> | |
| 55 | - </thead> | |
| 56 | - <tbody> | |
| 57 | - <?php if (!empty($subscriptions)) : ?> | |
| 58 | - <?php foreach ($subscriptions as $subscription) : | |
| 59 | - $order_id = get_post_meta($subscription->ID, '_subscrpt_order_id', true); | |
| 60 | - $order = wc_get_order($order_id); | |
| 61 | - $order_item_id = get_post_meta($subscription->ID, '_subscrpt_order_item_id', true); | |
| 62 | - $order_item = $order ? $order->get_item($order_item_id) : null; | |
| 63 | - $product_name = $order_item ? $order_item->get_name() : '-'; | |
| 64 | - $customer = $order ? $order->get_formatted_billing_full_name() : '-'; | |
| 65 | - $customer_id = $order ? $order->get_customer_id() : 0; | |
| 66 | - $customer_url = $customer_id ? admin_url('user-edit.php?user_id=' . $customer_id) : ''; | |
| 67 | - $customer_email = $order ? $order->get_billing_email() : ''; | |
| 68 | - $start_date = get_post_meta($subscription->ID, '_subscrpt_start_date', true); | |
| 69 | - $renewal_date = get_post_meta($subscription->ID, '_subscrpt_next_date', true); | |
| 70 | - $status_obj = get_post_status_object(get_post_status($subscription->ID)); | |
| 71 | - ?> | |
| 72 | - <tr> | |
| 73 | - <td> | |
| 74 | - <a href="<?php echo esc_url(get_edit_post_link($subscription->ID)); ?>" class="subscrpt-id-link"> | |
| 75 | - #<?php echo esc_html(get_the_title($subscription->ID)); ?> | |
| 76 | - </a> | |
| 77 | - </td> | |
| 78 | - <td style="min-width:320px;"> | |
| 79 | - <div class="wp-subscription-title-wrap"> | |
| 80 | - <span><?php echo esc_html($product_name); ?></span> | |
| 81 | - <div class="wp-subscription-row-actions"> | |
| 82 | - <a href="<?php echo esc_url(get_edit_post_link($subscription->ID)); ?>">View</a> | |
| 83 | - <a href="<?php echo esc_url(admin_url('admin.php?page=wp-subscription&action=duplicate&sub_id=' . $subscription->ID)); ?>">Duplicate</a> | |
| 84 | - <a href="<?php echo esc_url(get_delete_post_link($subscription->ID)); ?>">Delete</a> | |
| 85 | - </div> | |
| 86 | - </div> | |
| 87 | - </td> | |
| 88 | - <td> | |
| 89 | - <?php if ($customer_url): ?> | |
| 90 | - <a href="<?php echo esc_url($customer_url); ?>" target="_blank"><?php echo esc_html($customer); ?></a> | |
| 91 | - <?php else: ?> | |
| 92 | - <?php echo esc_html($customer); ?> | |
| 93 | - <?php endif; ?> | |
| 94 | - <?php if ($customer_email): ?> | |
| 95 | - <div class="wp-subscription-customer-email"><?php echo esc_html($customer_email); ?></div> | |
| 96 | - <?php endif; ?> | |
| 97 | - </td> | |
| 98 | - <td><?php echo $start_date ? esc_html(gmdate('F d, Y', $start_date)) : '-'; ?></td> | |
| 99 | - <td><?php echo $renewal_date ? esc_html(gmdate('F d, Y', $renewal_date)) : '-'; ?></td> | |
| 100 | - <td> | |
| 101 | - <span class="subscrpt-status-badge subscrpt-status-<?php echo esc_attr($status_obj->name); ?>"> | |
| 102 | - <?php echo esc_html($status_obj->label); ?> | |
| 103 | - </span> | |
| 104 | - </td> | |
| 105 | - <td> | |
| 106 | - <a href="<?php echo esc_url(get_edit_post_link($subscription->ID)); ?>" class="button button-small"><?php esc_html_e('Edit', 'wp_subscription'); ?></a> | |
| 107 | - </td> | |
| 108 | - </tr> | |
| 109 | - <?php endforeach; ?> | |
| 110 | - <?php else : ?> | |
| 111 | - <tr> | |
| 112 | - <td colspan="8" class="wp-subscription-list-empty"> | |
| 113 | - <?php esc_html_e('No subscriptions found.', 'wp_subscription'); ?> | |
| 114 | - </td> | |
| 115 | - </tr> | |
| 116 | - <?php endif; ?> | |
| 117 | - </tbody> | |
| 118 | - </table> | |
| 119 | - <?php if ($max_num_pages > 1): ?> | |
| 120 | - <div class="wp-subscription-pagination"> | |
| 121 | - <span class="total">Total <?php echo intval($total); ?></span> | |
| 122 | - <?php | |
| 123 | - $base_url = remove_query_arg('paged'); | |
| 124 | - $show_pages = $max_num_pages > 1 || $max_num_pages == 1; | |
| 125 | - for ($i = 1; $i <= $max_num_pages; $i++): | |
| 126 | - $url = add_query_arg(['paged' => $i, 'per_page' => $per_page], $base_url); | |
| 127 | - $is_current = $i == $paged; | |
| 128 | - ?> | |
| 129 | - <a href="<?php echo esc_url($url); ?>" class="button<?php if ($is_current) echo ' button-primary'; ?>" <?php if ($is_current) echo 'disabled'; ?>><?php echo $i; ?></a> | |
| 130 | - <?php endfor; ?> | |
| 131 | - <span class="goto-label">Go to</span> | |
| 132 | - <form method="get"> | |
| 133 | - <input type="hidden" name="page" value="wp-subscription" /> | |
| 134 | - <input type="number" name="paged" min="1" max="<?php echo $max_num_pages; ?>" value="<?php echo $paged; ?>" /> | |
| 135 | - <input type="hidden" name="per_page" value="<?php echo $per_page; ?>" /> | |
| 136 | - <button type="submit" class="button">OK</button> | |
| 137 | - </form> | |
| 138 | - </div> | |
| 139 | - <?php endif; ?> | |
| 32 | + | |
| 33 | + <?php | |
| 34 | + // Getting started ("Welcome to WPSubscription") card hidden for now — re-enable later. | |
| 35 | + // require __DIR__ . '/subscription-gsc.php'; | |
| 36 | + ?> | |
| 37 | + | |
| 38 | + <?php | |
| 39 | + wpsubs_render_page_header( | |
| 40 | + array( | |
| 41 | + 'title' => __( 'Subscriptions', 'subscription' ), | |
| 42 | + 'description' => __( 'Manage all your WooCommerce subscriptions.', 'subscription' ), | |
| 43 | + ) | |
| 44 | + ); | |
| 45 | + ?> | |
| 46 | + | |
| 47 | + <form method="post" id="subscriptions-form"> | |
| 48 | + <?php wp_nonce_field( 'subscrpt_list_action' ); ?> | |
| 49 | + <input type="hidden" name="page" value="wp-subscription-list" /> | |
| 50 | + | |
| 51 | + <!-- Toolbar --> | |
| 52 | + <div class="wpsubs-toolbar"> | |
| 53 | + | |
| 54 | + <?php | |
| 55 | + wpsubs_render_adv_select( | |
| 56 | + array( | |
| 57 | + 'name' => 'subscrpt_status', | |
| 58 | + 'placeholder' => __( 'All Status', 'subscription' ), | |
| 59 | + 'value' => $status, | |
| 60 | + 'options' => array( | |
| 61 | + array( | |
| 62 | + 'value' => 'active', | |
| 63 | + 'label' => __( 'Active', 'subscription' ), | |
| 64 | + ), | |
| 65 | + array( | |
| 66 | + 'value' => 'on_hold', | |
| 67 | + 'label' => __( 'On Hold', 'subscription' ), | |
| 68 | + ), | |
| 69 | + array( | |
| 70 | + 'value' => 'pending', | |
| 71 | + 'label' => __( 'Pending', 'subscription' ), | |
| 72 | + ), | |
| 73 | + array( | |
| 74 | + 'value' => 'pe_cancelled', | |
| 75 | + 'label' => __( 'Pending Cancellation', 'subscription' ), | |
| 76 | + ), | |
| 77 | + array( | |
| 78 | + 'value' => 'cancelled', | |
| 79 | + 'label' => __( 'Cancelled', 'subscription' ), | |
| 80 | + ), | |
| 81 | + array( | |
| 82 | + 'value' => 'expired', | |
| 83 | + 'label' => __( 'Expired', 'subscription' ), | |
| 84 | + ), | |
| 85 | + array( | |
| 86 | + 'value' => 'completed', | |
| 87 | + 'label' => __( 'Completed', 'subscription' ), | |
| 88 | + ), | |
| 89 | + array( | |
| 90 | + 'value' => 'trash', | |
| 91 | + 'label' => __( 'Trash', 'subscription' ), | |
| 92 | + ), | |
| 93 | + ), | |
| 94 | + ) | |
| 95 | + ); | |
| 96 | + ?> | |
| 97 | + | |
| 98 | + <?php | |
| 99 | + $date_options = array(); | |
| 100 | + foreach ( $months as $val => $label ) { | |
| 101 | + $date_options[] = array( | |
| 102 | + 'value' => $val, | |
| 103 | + 'label' => $label, | |
| 104 | + ); | |
| 105 | + } | |
| 106 | + wpsubs_render_adv_select( | |
| 107 | + array( | |
| 108 | + 'name' => 'date_filter', | |
| 109 | + 'placeholder' => __( 'All Dates', 'subscription' ), | |
| 110 | + 'value' => $date_filter, | |
| 111 | + 'options' => $date_options, | |
| 112 | + ) | |
| 113 | + ); | |
| 114 | + ?> | |
| 115 | + | |
| 116 | + <?php | |
| 117 | + // Next-renewal window. The Overview's "Renewals due" figure opens the | |
| 118 | + // list with 7 chosen; a window reached by URL is offered too, so the | |
| 119 | + // dropdown always shows what is applied. | |
| 120 | + $renewal_windows = array_unique( array_merge( array( 7, 14, 30 ), $renewal_due ? array( $renewal_due ) : array() ) ); | |
| 121 | + sort( $renewal_windows ); | |
| 122 | + $renewal_options = array(); | |
| 123 | + foreach ( $renewal_windows as $days ) { | |
| 124 | + $renewal_options[] = array( | |
| 125 | + 'value' => (string) $days, | |
| 126 | + /* translators: %d: number of days ahead. */ | |
| 127 | + 'label' => sprintf( _n( 'Due in %d day', 'Due in %d days', $days, 'subscription' ), $days ), | |
| 128 | + ); | |
| 129 | + } | |
| 130 | + wpsubs_render_adv_select( | |
| 131 | + array( | |
| 132 | + 'name' => 'renewal_due', | |
| 133 | + 'placeholder' => __( 'All Renewals', 'subscription' ), | |
| 134 | + 'value' => $renewal_due ? (string) $renewal_due : '', | |
| 135 | + 'options' => $renewal_options, | |
| 136 | + ) | |
| 137 | + ); | |
| 138 | + ?> | |
| 139 | + | |
| 140 | + <div class="wpsubs-search"> | |
| 141 | + <div class="wpsubs-input-wrap wpsubs-input-wrap--icon-l"> | |
| 142 | + <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> | |
| 143 | + <input type="search" name="s" value="<?php echo esc_attr( $search ); ?>" placeholder="<?php esc_attr_e( 'Search subscriptions...', 'subscription' ); ?>" class="wpsubs-input" /> | |
| 144 | + </div> | |
| 145 | + </div> | |
| 146 | + | |
| 147 | + <button type="submit" name="filter_action" value="filter" class="wpsubs-btn wpsubs-btn--outline"> | |
| 148 | + <?php esc_html_e( 'Filter', 'subscription' ); ?> | |
| 149 | + </button> | |
| 150 | + | |
| 151 | + <?php if ( $filters_active ) : ?> | |
| 152 | + <a href="<?php echo esc_url( remove_query_arg( array( 'subscrpt_status', 'date_filter', 's', 'title', 'paged', 'renewal_due' ) ) ); ?>" class="wpsubs-btn wpsubs-btn--outline"> | |
| 153 | + <?php esc_html_e( 'Clear', 'subscription' ); ?> | |
| 154 | + </a> | |
| 155 | + <?php endif; ?> | |
| 156 | + | |
| 157 | + <div class="wpsubs-toolbar__spacer"></div> | |
| 158 | + | |
| 159 | + <?php // Page size and bulk actions wrap as one right-aligned group, never one without the other. ?> | |
| 160 | + <div style="display:flex;flex-wrap:wrap;gap:8px;margin-inline-start:auto;"> | |
| 161 | + <?php | |
| 162 | + $current_per_page = isset( $_GET['per_page'] ) ? intval( wp_unslash( $_GET['per_page'] ) ) : 20; | |
| 163 | + wpsubs_render_per_page_select( | |
| 164 | + array( | |
| 165 | + 'name' => 'per_page', | |
| 166 | + 'value' => (string) $current_per_page, | |
| 167 | + 'align' => 'right', | |
| 168 | + 'id' => 'wpsubs-per-page-select', | |
| 169 | + ) | |
| 170 | + ); | |
| 171 | + ?> | |
| 172 | + | |
| 173 | + <?php | |
| 174 | + // Bulk action advanced-select + hidden submit. | |
| 175 | + $bulk_options = 'trash' === $status | |
| 176 | + ? array( | |
| 177 | + array( | |
| 178 | + 'value' => 'restore', | |
| 179 | + 'label' => __( 'Restore selected', 'subscription' ), | |
| 180 | + ), | |
| 181 | + array( | |
| 182 | + 'value' => 'delete', | |
| 183 | + 'label' => __( 'Delete permanently', 'subscription' ), | |
| 184 | + 'danger' => true, | |
| 185 | + 'confirm' => __( 'Delete selected subscriptions permanently? This cannot be undone.', 'subscription' ), | |
| 186 | + ), | |
| 187 | + ) | |
| 188 | + : array( | |
| 189 | + array( | |
| 190 | + 'value' => 'trash', | |
| 191 | + 'label' => __( 'Move to trash', 'subscription' ), | |
| 192 | + 'danger' => true, | |
| 193 | + 'confirm' => __( 'Move selected subscriptions to trash?', 'subscription' ), | |
| 194 | + ), | |
| 195 | + ); | |
| 196 | + | |
| 197 | + wpsubs_render_adv_select( | |
| 198 | + array( | |
| 199 | + 'name' => 'action', | |
| 200 | + 'placeholder' => __( 'Select Action', 'subscription' ), | |
| 201 | + 'value' => '-1', | |
| 202 | + 'options' => $bulk_options, | |
| 203 | + 'id' => 'wpsubs-bulk-action-select', | |
| 204 | + 'align' => 'right', | |
| 205 | + ) | |
| 206 | + ); | |
| 207 | + ?> | |
| 208 | + <button type="submit" name="bulk_action" value="1" id="wpsubs-bulk-action-submit" style="display:none;" aria-hidden="true"></button> | |
| 209 | + | |
| 210 | + <?php | |
| 211 | + if ( 'trash' === $status && ! empty( $subscriptions ) ) : | |
| 212 | + $empty_trash_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription-list&action=clean_trash&sub_id=all' ), 'wpsubs_action_clean_trash' ); | |
| 213 | + ?> | |
| 214 | + <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' ); ?>')"> | |
| 215 | + <?php esc_html_e( 'Empty Trash', 'subscription' ); ?> | |
| 216 | + </a> | |
| 217 | + <?php endif; ?> | |
| 218 | + </div> | |
| 219 | + | |
| 220 | + </div><!-- /.wpsubs-toolbar --> | |
| 221 | + | |
| 222 | + <!-- Table card --> | |
| 223 | + <h2 class="screen-reader-text"><?php esc_html_e( 'Subscriptions list', 'subscription' ); ?></h2> | |
| 224 | + <div class="wpsubs-table-card"> | |
| 225 | + <table class="wpsubs-table"> | |
| 226 | + <thead> | |
| 227 | + <tr> | |
| 228 | + <th class="wpsubs-col--check"> | |
| 229 | + <input type="checkbox" id="cb-select-all" class="wpsubs-checkbox"> | |
| 230 | + </th> | |
| 231 | + <th style="width:1%;white-space:nowrap;"><?php esc_html_e( 'Status', 'subscription' ); ?></th> | |
| 232 | + <th><?php esc_html_e( 'Subscription', 'subscription' ); ?></th> | |
| 233 | + <th><?php esc_html_e( 'Customer', 'subscription' ); ?></th> | |
| 234 | + <th><?php esc_html_e( 'Amount', 'subscription' ); ?></th> | |
| 235 | + <th><?php esc_html_e( 'Started', 'subscription' ); ?></th> | |
| 236 | + <th><?php esc_html_e( 'Next Renewal', 'subscription' ); ?></th> | |
| 237 | + <th class="wpsubs-col--actions"><?php esc_html_e( 'Actions', 'subscription' ); ?></th> | |
| 238 | + </tr> | |
| 239 | + </thead> | |
| 240 | + <tbody> | |
| 241 | + | |
| 242 | + <?php if ( ! empty( $subscriptions ) ) : ?> | |
| 243 | + <?php | |
| 244 | + foreach ( $subscriptions as $subscription ) : | |
| 245 | + $subscription_id = $subscription->ID; | |
| 246 | + $subscription_data = SpringDevs\Subscription\Illuminate\Helper::get_subscription_data( $subscription_id ); | |
| 247 | + | |
| 248 | + $subscrpt_status = $subscription_data['status'] ?? ''; | |
| 249 | + $subscrpt_status = empty( $subscrpt_status ) ? get_post_status( $subscription_id ) : $subscrpt_status; | |
| 250 | + | |
| 251 | + $order_id = $subscription_data['order']['order_id'] ?? 0; | |
| 252 | + $order_item_id = $subscription_data['order']['order_item_id'] ?? 0; | |
| 253 | + $order = $order_id ? wc_get_order( $order_id ) : null; | |
| 254 | + $order_item = $order ? $order->get_item( $order_item_id ) : null; | |
| 255 | + $product_name = $order_item ? $order_item->get_name() : '-'; | |
| 256 | + | |
| 257 | + $product_id = $subscription_data['product']['variation_id'] ?: ( $subscription_data['product']['product_id'] ?? 0 ); | |
| 258 | + $product_url = $product_id ? get_edit_post_link( $product_id ) : ''; | |
| 259 | + | |
| 260 | + $customer = $order ? $order->get_formatted_billing_full_name() : '-'; | |
| 261 | + $customer_id = $order ? $order->get_customer_id() : 0; | |
| 262 | + $customer_url = $customer_id ? admin_url( 'user-edit.php?user_id=' . $customer_id ) : ''; | |
| 263 | + $customer_email = $order ? $order->get_billing_email() : ''; | |
| 264 | + | |
| 265 | + $start_date = $subscription_data['start_date'] ? strtotime( $subscription_data['start_date'] ) : 0; | |
| 266 | + $renewal_date = $subscription_data['next_date'] && SpringDevs\Subscription\Illuminate\Helper::has_next_payment( $subscrpt_status ) ? strtotime( $subscription_data['next_date'] ) : 0; | |
| 267 | + | |
| 268 | + $is_trash = 'trash' === $subscription->post_status; | |
| 269 | + $is_grace_period = isset( $subscription_data['grace_period'] ); | |
| 270 | + $grace_remaining = $subscription_data['grace_period']['remaining_days'] ?? 0; | |
| 271 | + | |
| 272 | + // Amount + timing. Net of any discount that carries into renewals. | |
| 273 | + $quantity = $order_item ? (int) $order_item->get_quantity() : 1; | |
| 274 | + $amount_totals = SpringDevs\Subscription\Illuminate\Helper::get_subscription_display_totals( $subscription_id, $order_item ); | |
| 275 | + $price = '' !== ( $subscription_data['price'] ?? '' ) ? (float) $amount_totals['total'] : ''; | |
| 276 | + $price_full = (float) $amount_totals['full_excl'] + $amount_totals['tax'] + $amount_totals['discount_tax']; | |
| 277 | + $has_discount = (bool) $amount_totals['has_discount']; | |
| 278 | + $price_per_unit = '' !== $price ? $price / max( 1, $quantity ) : ''; | |
| 279 | + $timing_per = $subscription_data['schedule']['timing_per'] ?? ''; | |
| 280 | + $timing_option = $subscription_data['schedule']['timing_option'] ?? ''; | |
| 281 | + $timing_label = ''; | |
| 282 | + if ( $timing_per && $timing_option ) { | |
| 283 | + $timing_unit = \SpringDevs\Subscription\Illuminate\Helper::get_typos( $timing_per, $timing_option ); | |
| 284 | + $timing_label = ( (int) $timing_per > 1 ) | |
| 285 | + ? $timing_per . ' ' . $timing_unit | |
| 286 | + : $timing_unit; | |
| 287 | + } | |
| 288 | + | |
| 289 | + // Avatar | |
| 290 | + $name_parts = array_values( array_filter( explode( ' ', trim( $customer ) ) ) ); | |
| 291 | + $initials = '?'; | |
| 292 | + if ( $name_parts ) { | |
| 293 | + $initials = ''; | |
| 294 | + foreach ( array_slice( $name_parts, 0, 2 ) as $part ) { | |
| 295 | + $initials .= strtoupper( $part[0] ); | |
| 296 | + } | |
| 297 | + } | |
| 298 | + $color_slot = ord( strtolower( $initials[0] ?? 'a' ) ) % 8; | |
| 299 | + | |
| 300 | + // Action URLs | |
| 301 | + $nonce_action = 'wpsubs_action_' . $subscription->ID; | |
| 302 | + $view_url = admin_url( 'admin.php?page=wp-subscription-details&id=' . $subscription->ID ); | |
| 303 | + $trash_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription-list&action=trash&sub_id=' . $subscription->ID ), $nonce_action ); | |
| 304 | + $delete_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription-list&action=delete&sub_id=' . $subscription->ID ), $nonce_action ); | |
| 305 | + $restore_url = wp_nonce_url( admin_url( 'admin.php?page=wp-subscription-list&action=restore&sub_id=' . $subscription->ID ), $nonce_action ); | |
| 306 | + | |
| 307 | + // Status badge | |
| 308 | + $badge_mod_map = array( | |
| 309 | + 'active' => 'active', | |
| 310 | + 'pending' => 'pending', | |
| 311 | + 'pe_cancelled' => 'pending-cancel', | |
| 312 | + 'cancelled' => 'cancelled', | |
| 313 | + 'expired' => 'expired', | |
| 314 | + 'completed' => 'completed', | |
| 315 | + 'draft' => 'draft', | |
| 316 | + 'trash' => 'trash', | |
| 317 | + ); | |
| 318 | + $badge_mod = $badge_mod_map[ $subscrpt_status ] ?? 'expired'; | |
| 319 | + $verbose_status = SpringDevs\Subscription\Illuminate\Helper::get_verbose_status( $subscrpt_status ); | |
| 320 | + | |
| 321 | + if ( $is_grace_period && $grace_remaining > 0 ) { | |
| 322 | + $badge_mod = 'active'; | |
| 323 | + $verbose_status = __( 'Active', 'subscription' ); | |
| 324 | + } | |
| 325 | + ?> | |
| 326 | + <tr> | |
| 327 | + <!-- Checkbox --> | |
| 328 | + <td class="wpsubs-col--check"> | |
| 329 | + <input type="checkbox" name="subscription_ids[]" value="<?php echo esc_attr( $subscription->ID ); ?>" class="wpsubs-checkbox wpsubs-row-check"> | |
| 330 | + </td> | |
| 331 | + | |
| 332 | + <!-- Status badge --> | |
| 333 | + <td style="white-space:nowrap;"> | |
| 334 | + <span class="wpsubs-badge wpsubs-badge--<?php echo esc_attr( $badge_mod ); ?>"> | |
| 335 | + <?php echo esc_html( $verbose_status ); ?> | |
| 336 | + <?php if ( $is_grace_period && $grace_remaining > 0 ) : ?> | |
| 337 | + <span class="dashicons dashicons-warning" style="font-size:11px;width:11px;height:11px;color:#d97706;" title="<?php echo esc_attr( sprintf( /* translators: %d: number of days left in the grace period. */ __( '%d days remaining in grace period', 'subscription' ), $grace_remaining ) ); ?>"></span> | |
| 338 | + <?php endif; ?> | |
| 339 | + </span> | |
| 340 | + </td> | |
| 341 | + | |
| 342 | + <!-- Subscription: product name + subscription ID --> | |
| 343 | + <td> | |
| 344 | + <a href="<?php echo esc_url( $view_url ); ?>" class="wpsubs-cell-title" style="font-weight:600;"><?php echo esc_html( $product_name ); ?></a> | |
| 345 | + <span class="wpsubs-cell-id">#<?php echo (int) $subscription->ID; ?></span> | |
| 346 | + </td> | |
| 347 | + | |
| 348 | + <!-- Customer avatar + name + email --> | |
| 349 | + <td> | |
| 350 | + <div class="wpsubs-customer"> | |
| 351 | + <div class="wpsubs-avatar" data-color="<?php echo (int) $color_slot; ?>"> | |
| 352 | + <?php echo esc_html( $initials ); ?> | |
| 353 | + </div> | |
| 354 | + <div class="wpsubs-customer__info"> | |
| 355 | + <?php if ( $customer_url ) : ?> | |
| 356 | + <a href="<?php echo esc_url( $customer_url ); ?>" class="wpsubs-customer__name"><?php echo esc_html( $customer ); ?></a> | |
| 357 | + <?php else : ?> | |
| 358 | + <span class="wpsubs-customer__name"><?php echo esc_html( $customer ); ?></span> | |
| 359 | + <?php endif; ?> | |
| 360 | + <?php if ( $customer_email ) : ?> | |
| 361 | + <span class="wpsubs-customer__sub"><?php echo esc_html( $customer_email ); ?></span> | |
| 362 | + <?php endif; ?> | |
| 363 | + </div> | |
| 364 | + </div> | |
| 365 | + </td> | |
| 366 | + | |
| 367 | + <!-- Subscription amount + timing --> | |
| 368 | + <td style="white-space:nowrap;"> | |
| 369 | + <?php if ( '' !== $price ) : ?> | |
| 370 | + <span class="wpsubs-cell-title" style="font-variant-numeric:tabular-nums;"><?php echo wp_kses_post( wc_price( $price ) ); ?></span> | |
| 371 | + <?php if ( $has_discount ) : ?> | |
| 372 | + <span class="wpsubs-cell-id"><del aria-hidden="true"><?php echo wp_kses_post( wc_price( $price_full ) ); ?></del></span> | |
| 373 | + <?php endif; ?> | |
| 374 | + <?php if ( $quantity > 1 ) : ?> | |
| 375 | + <span class="wpsubs-cell-id"><?php echo wp_kses_post( wc_price( (float) $price_per_unit ) ); ?> × <?php echo (int) $quantity; ?></span> | |
| 376 | + <?php endif; ?> | |
| 377 | + <?php if ( $timing_label ) : ?> | |
| 378 | + <span class="wpsubs-cell-id">/ <?php echo esc_html( $timing_label ); ?></span> | |
| 379 | + <?php endif; ?> | |
| 380 | + <?php else : ?> | |
| 381 | + <span class="wpsubs-cell--muted">—</span> | |
| 382 | + <?php endif; ?> | |
| 383 | + </td> | |
| 384 | + | |
| 385 | + <!-- Start date --> | |
| 386 | + <td class="wpsubs-col--nowrap"> | |
| 387 | + <?php if ( $start_date ) : ?> | |
| 388 | + <span class="wpsubs-cell-title"><?php echo esc_html( wp_date( get_option( 'date_format' ), $start_date ) ); ?></span> | |
| 389 | + <span class="wpsubs-cell-id"><?php echo esc_html( wp_date( get_option( 'time_format' ), $start_date ) ); ?></span> | |
| 390 | + <?php else : ?> | |
| 391 | + <span class="wpsubs-cell--muted">—</span> | |
| 392 | + <?php endif; ?> | |
| 393 | + </td> | |
| 394 | + | |
| 395 | + <!-- Renewal date --> | |
| 396 | + <td class="wpsubs-col--nowrap"> | |
| 397 | + <?php if ( $renewal_date ) : ?> | |
| 398 | + <span class="wpsubs-cell-title"><?php echo esc_html( wp_date( get_option( 'date_format' ), $renewal_date ) ); ?></span> | |
| 399 | + <span class="wpsubs-cell-id"><?php echo esc_html( wp_date( get_option( 'time_format' ), $renewal_date ) ); ?></span> | |
| 400 | + <?php else : ?> | |
| 401 | + <span class="wpsubs-cell--muted">—</span> | |
| 402 | + <?php endif; ?> | |
| 403 | + </td> | |
| 404 | + | |
| 405 | + <!-- Actions --> | |
| 406 | + <td class="wpsubs-cell--actions"> | |
| 407 | + <div class="wpsubs-row-actions"> | |
| 408 | + <button type="button" class="wpsubs-row-actions__trigger" aria-label="<?php esc_attr_e( 'Row actions', 'subscription' ); ?>">···</button> | |
| 409 | + <div class="wpsubs-dropdown"> | |
| 410 | + <a href="<?php echo esc_url( $view_url ); ?>" class="wpsubs-dropdown__item"><?php esc_html_e( 'View / Edit', 'subscription' ); ?></a> | |
| 411 | + <div class="wpsubs-dropdown__divider"></div> | |
| 412 | + <?php if ( ! $is_trash ) : ?> | |
| 413 | + <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> | |
| 414 | + <?php else : ?> | |
| 415 | + <a href="<?php echo esc_url( $restore_url ); ?>" class="wpsubs-dropdown__item"><?php esc_html_e( 'Restore', 'subscription' ); ?></a> | |
| 416 | + <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> | |
| 417 | + <?php endif; ?> | |
| 418 | + </div> | |
| 419 | + </div> | |
| 420 | + </td> | |
| 421 | + </tr> | |
| 422 | + <?php endforeach; ?> | |
| 423 | + | |
| 424 | + <?php else : ?> | |
| 425 | + <tr> | |
| 426 | + <td colspan="8"> | |
| 427 | + <div class="wpsubs-empty"> | |
| 428 | + <div class="wpsubs-empty__icon">📋</div> | |
| 429 | + <div class="wpsubs-empty__title"><?php esc_html_e( 'No subscriptions found', 'subscription' ); ?></div> | |
| 430 | + <div class="wpsubs-empty__desc"><?php esc_html_e( 'Subscriptions will appear here once customers complete checkout.', 'subscription' ); ?></div> | |
| 431 | + </div> | |
| 432 | + </td> | |
| 433 | + </tr> | |
| 434 | + <?php endif; ?> | |
| 435 | + | |
| 436 | + </tbody> | |
| 437 | + </table> | |
| 438 | + | |
| 439 | + <!-- Pagination inside table card --> | |
| 440 | + <?php | |
| 441 | + wpsubs_render_pager( | |
| 442 | + array( | |
| 443 | + 'current' => (int) $paged, | |
| 444 | + 'total' => max( 1, (int) $max_num_pages ), | |
| 445 | + 'info' => true, | |
| 446 | + 'per_page' => (int) $per_page, | |
| 447 | + 'item_count' => (int) $total, | |
| 448 | + 'base_url' => remove_query_arg( 'paged' ), | |
| 449 | + 'link_mode' => 'url', | |
| 450 | + // translators: %1$s = first item number, %2$s = last item number, %3$s = total items. | |
| 451 | + 'info_format' => __( 'Showing %1$s–%2$s of %3$s subscriptions', 'subscription' ), | |
| 452 | + 'context' => 'subscription-list', | |
| 453 | + ) | |
| 454 | + ); | |
| 455 | + ?> | |
| 456 | + | |
| 457 | + </div><!-- /.wpsubs-table-card --> | |
| 458 | + | |
| 459 | + </form> | |
| 460 | + | |
| 140 | 461 | </div> |
| 462 | + | |
| 463 | +<script> | |
| 464 | +( function () { | |
| 465 | + var selectAll = document.getElementById( 'cb-select-all' ); | |
| 466 | + var form = document.getElementById( 'subscriptions-form' ); | |
| 467 | + | |
| 468 | + function rowChecks() { | |
| 469 | + return form ? Array.from( form.querySelectorAll( '.wpsubs-row-check' ) ) : []; | |
| 470 | + } | |
| 471 | + | |
| 472 | + function syncSelectAll() { | |
| 473 | + if ( ! selectAll ) return; | |
| 474 | + var checks = rowChecks(); | |
| 475 | + var checked = checks.filter( function ( cb ) { return cb.checked; } ); | |
| 476 | + checks.forEach( function ( cb ) { | |
| 477 | + var row = cb.closest( 'tr' ); | |
| 478 | + if ( row ) row.classList.toggle( 'wpsubs-row--selected', cb.checked ); | |
| 479 | + } ); | |
| 480 | + selectAll.indeterminate = checked.length > 0 && checked.length < checks.length; | |
| 481 | + selectAll.checked = checks.length > 0 && checked.length === checks.length; | |
| 482 | + } | |
| 483 | + | |
| 484 | + if ( selectAll ) { | |
| 485 | + selectAll.addEventListener( 'change', function () { | |
| 486 | + rowChecks().forEach( function ( cb ) { cb.checked = selectAll.checked; } ); | |
| 487 | + syncSelectAll(); | |
| 488 | + } ); | |
| 489 | + } | |
| 490 | + | |
| 491 | + if ( form ) { | |
| 492 | + form.addEventListener( 'change', function ( e ) { | |
| 493 | + if ( e.target.classList.contains( 'wpsubs-row-check' ) ) syncSelectAll(); | |
| 494 | + } ); | |
| 495 | + } | |
| 496 | + | |
| 497 | + // ── Advanced-select event handlers ────────────────────────── | |
| 498 | + var bulkSubmit = document.getElementById( 'wpsubs-bulk-action-submit' ); | |
| 499 | + document.addEventListener( 'wpsubs:select', function ( e ) { | |
| 500 | + var id = e.target && e.target.id; | |
| 501 | + | |
| 502 | + // Bulk action: submit via hidden button | |
| 503 | + if ( id === 'wpsubs-bulk-action-select' && bulkSubmit ) { | |
| 504 | + bulkSubmit.click(); | |
| 505 | + return; | |
| 506 | + } | |
| 507 | + | |
| 508 | + // Per-page: auto-submit as a filter action | |
| 509 | + if ( id === 'wpsubs-per-page-select' && form ) { | |
| 510 | + var fi = document.createElement( 'input' ); | |
| 511 | + fi.type = 'hidden'; | |
| 512 | + fi.name = 'filter_action'; | |
| 513 | + fi.value = 'filter'; | |
| 514 | + form.appendChild( fi ); | |
| 515 | + form.submit(); | |
| 516 | + } | |
| 517 | + } ); | |
| 518 | + | |
| 519 | + // ── Row action dropdowns ───────────────────────────────────── | |
| 520 | + function closeAllRowMenus() { | |
| 521 | + document.querySelectorAll( '.wpsubs-row-actions--open' ).forEach( function ( el ) { | |
| 522 | + el.classList.remove( 'wpsubs-row-actions--open' ); | |
| 523 | + var d = el.querySelector( '.wpsubs-dropdown' ); | |
| 524 | + if ( d ) d.classList.remove( 'wpsubs-dropdown--open' ); | |
| 525 | + } ); | |
| 526 | + } | |
| 527 | + | |
| 528 | + document.addEventListener( 'click', function ( e ) { | |
| 529 | + var trigger = e.target.closest( '.wpsubs-row-actions__trigger' ); | |
| 530 | + | |
| 531 | + document.querySelectorAll( '.wpsubs-row-actions--open' ).forEach( function ( el ) { | |
| 532 | + if ( el !== ( trigger && trigger.closest( '.wpsubs-row-actions' ) ) ) { | |
| 533 | + el.classList.remove( 'wpsubs-row-actions--open' ); | |
| 534 | + var d = el.querySelector( '.wpsubs-dropdown' ); | |
| 535 | + if ( d ) d.classList.remove( 'wpsubs-dropdown--open' ); | |
| 536 | + } | |
| 537 | + } ); | |
| 538 | + | |
| 539 | + if ( ! trigger ) return; | |
| 540 | + | |
| 541 | + e.stopPropagation(); | |
| 542 | + var wrapper = trigger.closest( '.wpsubs-row-actions' ); | |
| 543 | + var dropdown = wrapper && wrapper.querySelector( '.wpsubs-dropdown' ); | |
| 544 | + if ( ! wrapper || ! dropdown ) return; | |
| 545 | + | |
| 546 | + var isOpen = wrapper.classList.contains( 'wpsubs-row-actions--open' ); | |
| 547 | + wrapper.classList.toggle( 'wpsubs-row-actions--open', ! isOpen ); | |
| 548 | + dropdown.classList.toggle( 'wpsubs-dropdown--open', ! isOpen ); | |
| 549 | + } ); | |
| 550 | + | |
| 551 | + document.addEventListener( 'keydown', function ( e ) { | |
| 552 | + if ( e.key === 'Escape' ) closeAllRowMenus(); | |
| 553 | + } ); | |
| 554 | + | |
| 555 | + syncSelectAll(); | |
| 556 | +}() ); | |
| 557 | +</script> | |