PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 1.10.2
Subscriptions for WooCommerce with Stripe Recurring Payments v1.10.2
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 1.4.1 1.4.2 1.5.0 1.5.1 All 61 releases
subscription / includes / Admin / Menu.php

Menu.php in Subscriptions for WooCommerce with Stripe Recurring Payments 1.10.2, at includes/Admin/Menu.php

718 lines 24.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace SpringDevs\Subscription\Admin;
4
5 /**
6 * Menu class
7 *
8 * @package SpringDevs\Subscription\Admin
9 */
10 class Menu {
11
12 /**
13 * Initialize the class
14 */
15 public function __construct() {
16 add_action( 'admin_menu', array( $this, 'create_admin_menu' ) );
17 add_action( 'admin_menu', array( $this, 'reorder_submenu' ), 999 );
18 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
19 add_action( 'wp_ajax_subscrpt_bulk_action', array( $this, 'handle_bulk_action_ajax' ) );
20 }
21
22 /**
23 * Enqueue admin assets
24 */
25 public function enqueue_admin_assets() {
26 wp_enqueue_style(
27 'wp-subscription-admin',
28 SUBSCRPT_ASSETS . '/css/admin.css',
29 array(),
30 SUBSCRPT_VERSION
31 );
32
33 // Enqueue admin JavaScript for subscription list functionality
34 wp_enqueue_script(
35 'sdevs_subscription_admin',
36 SUBSCRPT_ASSETS . '/js/admin.js',
37 array( 'jquery' ),
38 SUBSCRPT_VERSION,
39 true
40 );
41
42 // Enqueue onboarding wizard JS (loaded on wizard page)
43 wp_enqueue_script(
44 'subscrpt-onboarding-wizard',
45 SUBSCRPT_ASSETS . '/js/admin/onboarding-wizard.js',
46 array( 'jquery' ),
47 SUBSCRPT_VERSION,
48 true
49 );
50 wp_localize_script(
51 'subscrpt-onboarding-wizard',
52 'subscrpt_wizard',
53 array(
54 'ajax_url' => admin_url( 'admin-ajax.php' ),
55 'subscriptions_url' => admin_url( 'admin.php?page=wp-subscription' ),
56 'currency_symbol' => get_woocommerce_currency_symbol(),
57 )
58 );
59
60 // Localize script for AJAX
61 wp_localize_script(
62 'sdevs_subscription_admin',
63 'wp_subscription_ajax',
64 array(
65 'nonce' => wp_create_nonce( 'subscrpt_bulk_action_nonce' ),
66 'ajaxurl' => admin_url( 'admin-ajax.php' ),
67 )
68 );
69 }
70
71 /**
72 * Create Subscriptions Menu.
73 */
74 public function create_admin_menu() {
75 $parent_slug = 'wp-subscription';
76 // Determine if the menu is active
77 $is_active = isset( $_GET['page'] ) && strpos( sanitize_text_field( wp_unslash( $_GET['page'] ) ), 'wp-subscription' ) === 0;
78 $icon_url = $is_active
79 ? SUBSCRPT_ASSETS . '/images/icons/subscription-20.png'
80 : SUBSCRPT_ASSETS . '/images/icons/subscription-20-gray.png';
81
82 $pro_text = __( 'WPSubscription Pro required', 'subscription' );
83 $pro_badge = subscrpt_pro_activated() ? '' : ' <span title="' . $pro_text . '"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" style="fill:var(--wpsubs-brand);vertical-align:middle;margin-bottom:2.2px;flex-shrink:0;" aria-hidden="true"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M19 19h-14c-.5 0 -.9 -.3 -1 -.8l-2 -10c0 -.4 .1 -.8 .5 -1.1c.4 -.2 .8 -.2 1.1 0l4.1 3.3l3.4 -5.1c.4 -.6 1.3 -.6 1.7 0l3.4 5.1l4.1 -3.3c.3 -.3 .8 -.3 1.1 0c.4 .2 .5 .6 .5 1.1l-2 10c0 .5 -.5 .8 -1 .8z"/></svg></span>';
84
85 // Main menu
86 add_menu_page(
87 __( 'WPSubscription', 'subscription' ),
88 __( 'WPSubscription', 'subscription' ),
89 'manage_options',
90 $parent_slug,
91 array( $this, 'render_subscriptions_page' ),
92 $icon_url,
93 40
94 );
95
96 // Onboarding Wizard (hidden from menu with CSS. can be accessed via direct URL: admin.php?page=wp-subscription-onboarding)
97 add_submenu_page(
98 $parent_slug,
99 __( 'Setup Wizard', 'subscription' ),
100 __( 'Setup Wizard', 'subscription' ),
101 'manage_options',
102 'wp-subscription-onboarding',
103 array( $this, 'render_onboarding_wizard' )
104 );
105
106 // Subscriptions List
107 add_submenu_page(
108 $parent_slug,
109 __( 'Subscriptions', 'subscription' ),
110 __( 'Subscriptions', 'subscription' ),
111 'manage_options',
112 $parent_slug,
113 array( $this, 'render_subscriptions_page' )
114 );
115
116 // Stats Overview
117 add_submenu_page(
118 $parent_slug,
119 __( 'Reports', 'subscription' ),
120 __( 'Reports', 'subscription' ) . $pro_badge,
121 'manage_options',
122 'wp-subscription-stats',
123 array( $this, 'render_stats_page' )
124 );
125
126 // Subscription Health
127 add_submenu_page(
128 $parent_slug,
129 __( 'Health', 'subscription' ),
130 __( 'Health', 'subscription' ) . $pro_badge,
131 'manage_options',
132 'wp-subscription-health',
133 array( $this, 'render_health_page' )
134 );
135
136 // Delivery Schedules
137 add_submenu_page(
138 $parent_slug,
139 __( 'Delivery', 'subscription' ),
140 __( 'Delivery', 'subscription' ) . $pro_badge,
141 'manage_options',
142 'wp-subscription-delivery',
143 array( $this, 'render_delivery_page' )
144 );
145
146 // Help & Resources
147 add_submenu_page(
148 $parent_slug,
149 __( 'Help', 'subscription' ),
150 __( 'Help', 'subscription' ),
151 'manage_options',
152 'wp-subscription-support',
153 array( $this, 'render_support_page' )
154 );
155
156 // Add WPSubscription link under WooCommerce menu
157 add_submenu_page(
158 'woocommerce',
159 __( 'WPSubscription', 'subscription' ),
160 __( 'WPSubscription', 'subscription' ),
161 'manage_options',
162 'wp-subscription',
163 array( $this, 'render_subscriptions_page' )
164 );
165 }
166
167 /**
168 * Reorder the WPSubscription submenu after all items are registered.
169 *
170 * Runs at admin_menu priority 999 so every plugin has already inserted
171 * its items. A filter lets the Pro plugin (or any extension) adjust the
172 * slug order before sorting is applied.
173 *
174 * @do_action subscrpt_submenu_order {string[]} $order Ordered list of submenu page slugs.
175 */
176 public function reorder_submenu() {
177 $parent = 'wp-subscription';
178
179 global $submenu;
180
181 if ( empty( $submenu[ $parent ] ) ) {
182 return;
183 }
184
185 // slug => position. Use gaps of 10 so extensions can insert between items.
186 $default_order = [
187 'wp-subscription' => 10, // Subscriptions
188 'wp-subscription-stats' => 20, // Reports
189 'wp-subscription-delivery' => 30, // Delivery (pro)
190 'wp-subscription-health' => 50, // Health
191 'wp-subscription-integrations' => 60, // Integrations
192 'wp-subscription-support' => 70, // Help & Resources
193 'wp-subscription-settings' => 998, // Settings
194 'wp-subscription-license' => 999, // License (pro)
195 ];
196
197 // Place pro pages at the bottom if pro is not active.
198 if ( ! subscrpt_pro_activated() ) {
199 $default_order['wp-subscription-stats'] = 200;
200 $default_order['wp-subscription-delivery'] = 210;
201 $default_order['wp-subscription-health'] = 220;
202 }
203
204 /**
205 * Filter the WPSubscription submenu slug order.
206 *
207 * Each entry is a slug => integer position pair. Lower positions appear
208 * first. Use gaps of 10 between built-in positions so extensions can
209 * insert their own slugs between existing items without renumbering.
210 *
211 * Example (pro plugin adding Delivery at position 35):
212 * add_filter( 'subscrpt_submenu_order', function( $order ) {
213 * $order['wp-subscription-delivery'] = 35;
214 * return $order;
215 * } );
216 *
217 * @param array<string,int> $order Map of slug => position.
218 */
219 $order = apply_filters( 'subscrpt_submenu_order', $default_order );
220
221 // Sort by position value, preserving slug keys.
222 asort( $order );
223
224 // Index current items by slug for fast lookup.
225 $indexed = [];
226 foreach ( $submenu[ $parent ] as $item ) {
227 $indexed[ $item[2] ] = $item;
228 }
229
230 // Build sorted list from the ordered slugs.
231 $sorted = [];
232 foreach ( $order as $slug => $position ) {
233 if ( isset( $indexed[ $slug ] ) ) {
234 $sorted[] = $indexed[ $slug ];
235 unset( $indexed[ $slug ] );
236 }
237 }
238
239 // Append any remaining items not covered by the order list.
240 foreach ( $indexed as $item ) {
241 $sorted[] = $item;
242 }
243
244 $submenu[ $parent ] = $sorted; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Intentional reorder of submenu items.
245 }
246
247 /**
248 * Render the admin header
249 */
250 public function render_admin_footer() {
251 ?>
252 <div style="text-align:center;margin:38px 0 0 0;font-size:14px;color:#888;">
253 Made with <span style="color:#e25555;font-size:1.1em;">♥</span> by the WPSubscription Team
254 <div style="margin-top:6px;">
255 <a href="https://wpsubscription.co/contact" target="_blank" style="color:#2563eb;text-decoration:none;">Support</a>
256 &nbsp;/&nbsp;
257 <a href="https://docs.converslabs.com/en" target="_blank" style="color:#2563eb;text-decoration:none;">Docs</a>
258 </div>
259 </div>
260 <?php
261 }
262 /**
263 * Render the admin header.
264 *
265 * @param string $title Page title shown on the left.
266 * @param string $subtitle Optional subtitle shown below the title.
267 */
268 public function render_admin_header( string $title = '', string $subtitle = '' ) {
269 $current = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : 'wp-subscription';
270
271 // Kept for backward compatibility — extensions may hook here for side-effects.
272 $menu_items = apply_filters( 'subscrpt_admin_header_menu_items', [], $current );
273 unset( $menu_items ); // Nav no longer rendered in header; navigation is in WP sidebar.
274 ?>
275 <div class="wp-subscription-admin-header">
276 <div class="wp-subscription-admin-header-inner">
277 <div class="wp-subscription-admin-header-left">
278 <nav class="wp-subscription-breadcrumb" aria-label="<?php esc_attr_e( 'Breadcrumb', 'subscription' ); ?>">
279 <a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-subscription' ) ); ?>" class="wp-subscription-breadcrumb-home" aria-label="<?php esc_attr_e( 'WPSubscription Home', 'subscription' ); ?>">
280 <span class="dashicons dashicons-admin-home"></span>
281 </a>
282 <?php if ( $title ) : ?>
283 <span class="wp-subscription-breadcrumb-sep" aria-hidden="true">/</span>
284 <span class="wp-subscription-breadcrumb-current"><?php echo esc_html( $title ); ?></span>
285 <?php endif; ?>
286 </nav>
287 </div>
288 <div class="wp-subscription-admin-header-right">
289 <?php if ( ! class_exists( 'Sdevs_Wc_Subscription_Pro' ) ) : ?>
290 <a target="_blank" href="https://wpsubscription.co/?utm_source=plugin&utm_medium=admin&utm_campaign=upgrade_pro" class="wpsubs-btn wpsubs-btn--primary wpsubs-btn--sm" rel="noreferrer noopener">
291 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" style="flex-shrink:0;"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M19 19h-14c-.5 0 -.9 -.3 -1 -.8l-2 -10c0 -.4 .1 -.8 .5 -1.1c.4 -.2 .8 -.2 1.1 0l4.1 3.3l3.4 -5.1c.4 -.6 1.3 -.6 1.7 0l3.4 5.1l4.1 -3.3c.3 -.3 .8 -.3 1.1 0c.4 .2 .5 .6 .5 1.1l-2 10c0 .5 -.5 .8 -1 .8z"/></svg>
292 <?php esc_html_e( 'Upgrade to Pro', 'subscription' ); ?>
293 </a>
294 <?php endif; ?>
295 <img src="<?php echo esc_url( SUBSCRPT_ASSETS . '/images/logo-title.svg' ); ?>" alt="WPSubscription" class="wp-subscription-logo">
296 </div>
297 </div>
298 </div>
299 <?php
300 }
301
302 /**
303 * Render Subscriptions page
304 */
305 public function render_subscriptions_page() {
306 $this->render_admin_header( __( 'Subscriptions', 'subscription' ), __( 'Manage your subscriptions', 'subscription' ) );
307
308 // Handle filters
309 $status = isset( $_GET['subscrpt_status'] ) ? sanitize_text_field( wp_unslash( $_GET['subscrpt_status'] ) ) : '';
310 $search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
311 $date_filter = isset( $_GET['date_filter'] ) ? sanitize_text_field( wp_unslash( $_GET['date_filter'] ) ) : '';
312 $per_page = isset( $_GET['per_page'] ) ? max( 1, intval( $_GET['per_page'] ) ) : 20;
313 $paged = isset( $_GET['paged'] ) ? max( 1, intval( $_GET['paged'] ) ) : 1;
314
315 // Handle form submissions (both filters and bulk actions)
316 $request_method = isset( $_SERVER['REQUEST_METHOD'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '';
317 if ( 'POST' === $request_method ) {
318 // Verify nonce before processing any POST data.
319 $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';
320 if ( ! wp_verify_nonce( $nonce, 'subscrpt_list_action' ) ) {
321 wp_die( esc_html__( 'Security check failed.', 'subscription' ) );
322 }
323 // Handle bulk actions
324 if ( isset( $_POST['bulk_action'] ) || isset( $_POST['bulk_action2'] ) ) {
325 $bulk_action = isset( $_POST['bulk_action'] ) ? sanitize_text_field( wp_unslash( $_POST['bulk_action'] ) ) : sanitize_text_field( wp_unslash( $_POST['bulk_action2'] ?? '' ) );
326 $action = isset( $_POST['action'] ) ? sanitize_text_field( wp_unslash( $_POST['action'] ) ) : sanitize_text_field( wp_unslash( $_POST['action2'] ?? '' ) );
327
328 if ( $bulk_action && $action && $action !== '-1' && isset( $_POST['subscription_ids'] ) && is_array( $_POST['subscription_ids'] ) ) {
329 $subscription_ids = array_map( 'intval', $_POST['subscription_ids'] );
330
331 if ( $action === 'trash' ) {
332 foreach ( $subscription_ids as $sub_id ) {
333 wp_trash_post( $sub_id );
334 }
335 } elseif ( $action === 'restore' ) {
336 foreach ( $subscription_ids as $sub_id ) {
337 wp_untrash_post( $sub_id );
338 }
339 } elseif ( $action === 'delete' ) {
340 foreach ( $subscription_ids as $sub_id ) {
341 wp_delete_post( $sub_id, true );
342 }
343 }
344
345 wp_safe_redirect( admin_url( 'admin.php?page=wp-subscription' ) );
346 exit;
347 }
348 }
349
350 // Handle filter form submission
351 if ( isset( $_POST['filter_action'] ) ) {
352 $filter_params = array();
353
354 if ( ! empty( $_POST['subscrpt_status'] ) ) {
355 $filter_params['subscrpt_status'] = sanitize_text_field( wp_unslash( $_POST['subscrpt_status'] ) );
356 }
357 if ( ! empty( $_POST['date_filter'] ) ) {
358 $filter_params['date_filter'] = sanitize_text_field( wp_unslash( $_POST['date_filter'] ) );
359 }
360 if ( ! empty( $_POST['s'] ) ) {
361 $filter_params['s'] = sanitize_text_field( wp_unslash( $_POST['s'] ) );
362 }
363 if ( ! empty( $_POST['per_page'] ) ) {
364 $filter_params['per_page'] = intval( $_POST['per_page'] );
365 }
366
367 $redirect_url = add_query_arg( $filter_params, admin_url( 'admin.php?page=wp-subscription' ) );
368 wp_safe_redirect( $redirect_url );
369 exit;
370 }
371 }
372
373 // Handle individual actions
374 if ( isset( $_GET['action'] ) && ! empty( $_GET['sub_id'] ) ) {
375 $sub_id = intval( $_GET['sub_id'] );
376 $action = sanitize_text_field( wp_unslash( $_GET['action'] ) );
377 $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
378
379 // Clean trash action.
380 if ( $action === 'clean_trash' ) {
381 // Verify nonce for security.
382 $nonce_action = 'wpsubs_action_clean_trash';
383 if ( ! wp_verify_nonce( $nonce, $nonce_action ) ) {
384 echo '<div class="notice notice-error"><p>' . esc_html__( 'Security check failed. Please try again.', 'subscription' ) . '</p></div>';
385 wp_die();
386 }
387
388 // Clean all trash items.
389 $trash_posts = get_posts(
390 [
391 'post_type' => 'subscrpt_order',
392 'post_status' => 'trash',
393 'numberposts' => -1,
394 'fields' => 'ids',
395 ]
396 );
397
398 foreach ( $trash_posts as $trash_id ) {
399 wp_delete_post( $trash_id, true );
400 }
401
402 wp_safe_redirect( admin_url( 'admin.php?page=wp-subscription&subscrpt_status=trash' ) );
403 exit;
404 } else {
405 // For other actions, verify nonce with subscription ID.
406 $nonce_action = 'wpsubs_action_' . $sub_id;
407 if ( ! wp_verify_nonce( $nonce, $nonce_action ) ) {
408 echo '<div class="notice notice-error"><p>' . esc_html__( 'Security check failed. Please try again.', 'subscription' ) . '</p></div>';
409 wp_die();
410 }
411
412 $redirect_url = admin_url( 'admin.php?page=wp-subscription' );
413
414 switch ( $action ) {
415 case 'duplicate':
416 $post = get_post( $sub_id );
417 if ( $post && $post->post_type === 'subscrpt_order' ) {
418 $new_post = [
419 'post_title' => $post->post_title . ' (Copy)',
420 'post_content' => $post->post_content,
421 'post_status' => 'draft',
422 'post_type' => 'subscrpt_order',
423 ];
424 $new_id = wp_insert_post( $new_post );
425 if ( $new_id ) {
426 $meta = get_post_meta( $sub_id );
427 foreach ( $meta as $key => $values ) {
428 foreach ( $values as $value ) {
429 add_post_meta( $new_id, $key, maybe_unserialize( $value ) );
430 }
431 }
432 }
433 }
434 break;
435 case 'trash':
436 wp_trash_post( $sub_id );
437 break;
438 case 'restore':
439 wp_untrash_post( $sub_id );
440 break;
441 case 'delete':
442 wp_delete_post( $sub_id, true );
443 $redirect_url = admin_url( 'admin.php?page=wp-subscription&subscrpt_status=trash' );
444 break;
445 }
446
447 wp_safe_redirect( $redirect_url );
448 exit;
449 }
450 }
451
452 $args = [
453 'post_type' => 'subscrpt_order',
454 'post_status' => 'any',
455 'posts_per_page' => $per_page,
456 'paged' => $paged,
457 'orderby' => 'date',
458 'order' => 'DESC',
459 ];
460
461 if ( $status ) {
462 $args['post_status'] = $status;
463 }
464 // Search only by subscription ID
465 if ( $search !== '' ) {
466 if ( is_numeric( $search ) ) {
467 $args['p'] = intval( $search );
468 } else {
469 // If not numeric, return no results
470 $args['post__in'] = array( 0 );
471 }
472 }
473 // Dynamic date filter (YYYY-MM)
474 if ( $date_filter && preg_match( '/^\d{4}-\d{2}$/', $date_filter ) ) {
475 $year = substr( $date_filter, 0, 4 );
476 $month = substr( $date_filter, 5, 2 );
477 $args['date_query'][] = [
478 'year' => intval( $year ),
479 'month' => intval( $month ),
480 ];
481 }
482
483 $query = new \WP_Query( $args );
484 $subscriptions = $query->posts;
485 $total = $query->found_posts;
486 $max_num_pages = $query->max_num_pages;
487
488 // Get all possible statuses for filter dropdown
489 $all_statuses = get_post_stati( [ 'show_in_admin_all_list' => true ], 'objects' );
490
491 include __DIR__ . '/views/subscription-list.php';
492 ?>
493 <div style="text-align:center;margin:38px 0 0 0;font-size:14px;color:#888;">
494 Made with <span style="color:#e25555;font-size:1.1em;">♥</span> by the WPSubscription Team
495 <div style="margin-top:6px;">
496 <a href="https://wpsubscription.co/contact" target="_blank" style="color:#2563eb;text-decoration:none;">Support</a>
497 &nbsp;/&nbsp;
498 <a href="https://docs.converslabs.com/en" target="_blank" style="color:#2563eb;text-decoration:none;">Docs</a>
499 </div>
500 </div>
501 <?php
502 }
503
504 /**
505 * Render Stats page
506 */
507 public function render_stats_page() {
508 $this->render_admin_header( __( 'Reports', 'subscription' ), __( 'View your subscription analytics', 'subscription' ) );
509
510 if ( ! subscrpt_pro_activated() ) {
511 include 'views/reports-preview.php';
512 } else {
513 // Allow pro plugin to override the entire stats page content.
514 do_action( 'subscrpt_render_stats_page' );
515 }
516
517 $this->render_admin_footer();
518 }
519
520 /**
521 * Render Health page
522 */
523 public function render_health_page() {
524 $this->render_admin_header( __( 'Health', 'subscription' ), __( 'Monitor your subscription health', 'subscription' ) );
525
526 if ( ! subscrpt_pro_activated() ) {
527 include 'views/health-preview.php';
528 } else {
529 // Allow pro plugin to render the full health page content.
530 do_action( 'subscrpt_render_health_page' );
531 }
532
533 $this->render_admin_footer();
534 }
535
536 /**
537 * Render Delivery Schedules page.
538 * When pro is active, fires subscrpt_render_delivery_page for pro to handle.
539 */
540 public function render_delivery_page() {
541 $this->render_admin_header( __( 'Delivery Schedules', 'subscription' ), __( 'Track and manage subscription delivery schedules.', 'subscription' ) );
542
543 if ( ! subscrpt_pro_activated() ) {
544 include 'views/delivery-preview.php';
545 } else {
546 // Allow pro plugin to render the full delivery page content.
547 do_action( 'subscrpt_render_delivery_page' );
548 }
549
550 $this->render_admin_footer();
551 }
552
553 /**
554 * Render Support page
555 */
556 public function render_support_page() {
557 $this->render_admin_header( __( 'Help & Resources', 'subscription' ), __( 'Documentation, community links, and ways to get help with WPSubscription.', 'subscription' ) );
558 include 'views/support.php';
559 $this->render_admin_footer();
560 }
561
562 /**
563 * Render Onboarding Wizard page
564 * SPA-style: all sections rendered at once, JS controls visibility
565 * Initial load always shows page 1 (JS handles transitions from there)
566 */
567 public function render_onboarding_wizard() {
568 // Start session if not already started
569 if ( ! session_id() && ! headers_sent() ) {
570 session_start();
571 }
572
573 // Always start at page 1 on direct load (SPA behavior — JS drives page transitions)
574 $GLOBALS['wizard_page'] = 1;
575
576 $this->render_admin_header( __( 'Setup Wizard', 'subscription' ), __( 'Create your first subscription product', 'subscription' ) );
577 include __DIR__ . '/views/onboarding-wizard.php';
578 $this->render_admin_footer();
579 }
580
581 /**
582 * Render the legacy subscriptions list page (WP_List_Table based)
583 */
584 public function render_legacy_subscriptions_page() {
585 // No longer needed, as the menu now links directly to the post type list.
586 }
587
588 /**
589 * Handle bulk action AJAX
590 */
591 public function handle_bulk_action_ajax() {
592 // Verify nonce
593 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
594 if ( ! wp_verify_nonce( $nonce, 'subscrpt_bulk_action_nonce' ) ) {
595 wp_send_json_error( array( 'message' => __( 'Security check failed.', 'subscription' ) ) );
596 }
597
598 // Check permissions
599 if ( ! current_user_can( 'manage_woocommerce' ) ) {
600 wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'subscription' ) ) );
601 }
602
603 // Get action and subscription IDs
604 $bulk_action = isset( $_POST['bulk_action'] ) ? sanitize_text_field( wp_unslash( $_POST['bulk_action'] ) ) : '';
605 $subscription_ids = isset( $_POST['subscription_ids'] ) ? array_map( 'intval', $_POST['subscription_ids'] ) : array();
606
607 if ( empty( $subscription_ids ) ) {
608 wp_send_json_error( array( 'message' => __( 'No subscriptions selected.', 'subscription' ) ) );
609 }
610
611 $processed_count = 0;
612 $errors = array();
613
614 foreach ( $subscription_ids as $subscription_id ) {
615 $post = get_post( $subscription_id );
616
617 if ( ! $post || $post->post_type !== 'subscrpt_order' ) {
618 // translators: Subscription ID.
619 $errors[] = sprintf( __( 'Subscription #%d not found.', 'subscription' ), $subscription_id );
620 continue;
621 }
622
623 try {
624 switch ( $bulk_action ) {
625 case 'trash':
626 if ( wp_trash_post( $subscription_id ) ) {
627 ++$processed_count;
628 } else {
629 $errors[] = sprintf(
630 // translators: Subscription ID.
631 __( 'Failed to move subscription #%d to trash.', 'subscription' ),
632 $subscription_id
633 );
634 }
635 break;
636
637 case 'restore':
638 if ( wp_untrash_post( $subscription_id ) ) {
639 ++$processed_count;
640 } else {
641 $errors[] = sprintf(
642 // translators: Subscription ID.
643 __( 'Failed to restore subscription #%d.', 'subscription' ),
644 $subscription_id
645 );
646 }
647 break;
648
649 case 'delete':
650 if ( wp_delete_post( $subscription_id, true ) ) {
651 ++$processed_count;
652 } else {
653 $errors[] = sprintf(
654 // translators: Subscription ID.
655 __( 'Failed to delete subscription #%d.', 'subscription' ),
656 $subscription_id
657 );
658 }
659 break;
660
661 default:
662 $errors[] = sprintf(
663 // translators: Bulk action.
664 __( 'Unknown action: %s', 'subscription' ),
665 $bulk_action
666 );
667 break;
668 }
669 } catch ( Exception $e ) {
670 $errors[] = sprintf(
671 // translators: Subscription ID, Error message.
672 __( 'Error processing subscription #%1$d: %2$s', 'subscription' ),
673 $subscription_id,
674 $e->getMessage()
675 );
676 }
677 }
678
679 // Prepare response message
680 $message = '';
681 if ( $processed_count > 0 ) {
682 switch ( $bulk_action ) {
683 case 'trash':
684 $message = sprintf(
685 // translators: Number of subscriptions.
686 _n( '%d subscription moved to trash.', '%d subscriptions moved to trash.', $processed_count, 'subscription' ),
687 $processed_count
688 );
689 break;
690 case 'restore':
691 $message = sprintf(
692 // translators: Number of subscriptions.
693 _n( '%d subscription restored.', '%d subscriptions restored.', $processed_count, 'subscription' ),
694 $processed_count
695 );
696 break;
697 case 'delete':
698 $message = sprintf(
699 // translators: Number of subscriptions.
700 _n( '%d subscription permanently deleted.', '%d subscriptions permanently deleted.', $processed_count, 'subscription' ),
701 $processed_count
702 );
703 break;
704 }
705 }
706
707 if ( ! empty( $errors ) ) {
708 $message .= ' ' . __( 'Some errors occurred:', 'subscription' ) . ' ' . implode( ', ', $errors );
709 }
710
711 if ( $processed_count > 0 ) {
712 wp_send_json_success( array( 'message' => $message ) );
713 } else {
714 wp_send_json_error( array( 'message' => $message ? $message : __( 'No subscriptions were processed.', 'subscription' ) ) );
715 }
716 }
717 }
718