PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 1.10.0
Subscriptions for WooCommerce with Stripe Recurring Payments v1.10.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 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.0, at includes/Admin/Menu.php

696 lines 27.6 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_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
18 add_action( 'wp_ajax_subscrpt_bulk_action', array( $this, 'handle_bulk_action_ajax' ) );
19 }
20
21 /**
22 * Enqueue admin assets
23 */
24 public function enqueue_admin_assets() {
25 wp_enqueue_style(
26 'wp-subscription-admin',
27 SUBSCRPT_ASSETS . '/css/admin.css',
28 array(),
29 SUBSCRPT_VERSION
30 );
31
32 // Enqueue admin JavaScript for subscription list functionality
33 wp_enqueue_script(
34 'sdevs_subscription_admin',
35 SUBSCRPT_ASSETS . '/js/admin.js',
36 array( 'jquery' ),
37 SUBSCRPT_VERSION,
38 true
39 );
40
41 // Localize script for AJAX
42 wp_localize_script(
43 'sdevs_subscription_admin',
44 'wp_subscription_ajax',
45 array(
46 'nonce' => wp_create_nonce( 'subscrpt_bulk_action_nonce' ),
47 'ajaxurl' => admin_url( 'admin-ajax.php' ),
48 )
49 );
50 }
51
52 /**
53 * Create Subscriptions Menu.
54 */
55 public function create_admin_menu() {
56 $parent_slug = 'wp-subscription';
57 // Determine if the menu is active
58 $is_active = isset( $_GET['page'] ) && strpos( sanitize_text_field( wp_unslash( $_GET['page'] ) ), 'wp-subscription' ) === 0;
59 $icon_url = $is_active
60 ? SUBSCRPT_ASSETS . '/images/icons/subscription-20.png'
61 : SUBSCRPT_ASSETS . '/images/icons/subscription-20-gray.png';
62 // Main menu
63 add_menu_page(
64 __( 'WPSubscription', 'subscription' ),
65 __( 'WPSubscription', 'subscription' ),
66 'manage_woocommerce',
67 $parent_slug,
68 array( $this, 'render_subscriptions_page' ),
69 $icon_url,
70 40
71 );
72
73 // Subscriptions List
74 add_submenu_page(
75 $parent_slug,
76 __( 'Subscriptions', 'subscription' ),
77 __( 'Subscriptions', 'subscription' ),
78 'manage_woocommerce',
79 $parent_slug,
80 array( $this, 'render_subscriptions_page' )
81 );
82
83 // Stats Overview
84 add_submenu_page(
85 $parent_slug,
86 __( 'Reports', 'subscription' ),
87 __( 'Reports', 'subscription' ),
88 'manage_woocommerce',
89 'wp-subscription-stats',
90 array( $this, 'render_stats_page' )
91 );
92
93 // Subscription Health
94 add_submenu_page(
95 $parent_slug,
96 __( 'Health', 'subscription' ),
97 __( 'Health', 'subscription' ),
98 'manage_woocommerce',
99 'wp-subscription-health',
100 array( $this, 'render_health_page' )
101 );
102
103 /*
104 ! For god's sake, check existing code before implementing!
105 // Settings
106 add_submenu_page(
107 $parent_slug,
108 __( 'Settings', 'subscription' ),
109 __( 'Settings', 'subscription' ),
110 'manage_woocommerce',
111 'wp-subscription-settings',
112 array( $this, 'render_settings_page' )
113 );
114 */
115
116 // Support
117 add_submenu_page(
118 $parent_slug,
119 __( 'Support', 'subscription' ),
120 __( 'Support', 'subscription' ),
121 'manage_woocommerce',
122 'wp-subscription-support',
123 array( $this, 'render_support_page' )
124 );
125
126 // Add WPSubscription link under WooCommerce menu
127 add_submenu_page(
128 'woocommerce',
129 __( 'WPSubscription', 'subscription' ),
130 __( 'WPSubscription', 'subscription' ),
131 'manage_woocommerce',
132 'wp-subscription',
133 array( $this, 'render_subscriptions_page' )
134 );
135 }
136 /**
137 * Render the admin header
138 */
139 public function render_admin_footer() {
140 ?>
141 <div style="text-align:center;margin:38px 0 0 0;font-size:14px;color:#888;">
142 Made with <span style="color:#e25555;font-size:1.1em;"></span> by the WPSubscription Team
143 <div style="margin-top:6px;">
144 <a href="https://wpsubscription.co/contact" target="_blank" style="color:#2563eb;text-decoration:none;">Support</a>
145 &nbsp;/&nbsp;
146 <a href="https://docs.converslabs.com/en" target="_blank" style="color:#2563eb;text-decoration:none;">Docs</a>
147 </div>
148 </div>
149 <?php
150 }
151 /**
152 * Render the admin header.
153 *
154 * @param string $title Page title shown on the left.
155 * @param string $subtitle Optional subtitle shown below the title.
156 */
157 public function render_admin_header( string $title = '', string $subtitle = '' ) {
158 $current = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : 'wp-subscription';
159
160 // Kept for backward compatibility — extensions may hook here for side-effects.
161 $menu_items = apply_filters( 'subscrpt_admin_header_menu_items', [], $current );
162 unset( $menu_items ); // Nav no longer rendered in header; navigation is in WP sidebar.
163 ?>
164 <div class="wp-subscription-admin-header">
165 <div class="wp-subscription-admin-header-inner">
166 <div class="wp-subscription-admin-header-left">
167 <nav class="wp-subscription-breadcrumb" aria-label="<?php esc_attr_e( 'Breadcrumb', 'subscription' ); ?>">
168 <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' ); ?>">
169 <span class="dashicons dashicons-admin-home"></span>
170 </a>
171 <?php if ( $title ) : ?>
172 <span class="wp-subscription-breadcrumb-sep" aria-hidden="true">/</span>
173 <span class="wp-subscription-breadcrumb-current"><?php echo esc_html( $title ); ?></span>
174 <?php endif; ?>
175 </nav>
176 </div>
177 <div class="wp-subscription-admin-header-right">
178 <?php if ( ! class_exists( 'Sdevs_Wc_Subscription_Pro' ) ) : ?>
179 <a target="_blank" href="https://wpsubscription.co/?utm_source=plugin&utm_medium=admin&utm_campaign=upgrade_pro" class="wp-subscription-upgrade-btn"><?php esc_html_e( 'Upgrade to Pro', 'subscription' ); ?></a>
180 <?php endif; ?>
181 <img src="<?php echo esc_url( SUBSCRPT_ASSETS . '/images/logo-title.svg' ); ?>" alt="WPSubscription" class="wp-subscription-logo">
182 </div>
183 </div>
184 </div>
185 <?php
186 }
187
188 /**
189 * Render Subscriptions page
190 */
191 public function render_subscriptions_page() {
192 $this->render_admin_header( __( 'Subscriptions', 'subscription' ), __( 'Manage your subscriptions', 'subscription' ) );
193
194 // Handle filters
195 $status = isset( $_GET['subscrpt_status'] ) ? sanitize_text_field( wp_unslash( $_GET['subscrpt_status'] ) ) : '';
196 $search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
197 $date_filter = isset( $_GET['date_filter'] ) ? sanitize_text_field( wp_unslash( $_GET['date_filter'] ) ) : '';
198 $per_page = isset( $_GET['per_page'] ) ? max( 1, intval( $_GET['per_page'] ) ) : 20;
199 $paged = isset( $_GET['paged'] ) ? max( 1, intval( $_GET['paged'] ) ) : 1;
200
201 // Handle form submissions (both filters and bulk actions)
202 $request_method = isset( $_SERVER['REQUEST_METHOD'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '';
203 if ( 'POST' === $request_method ) {
204 // Verify nonce before processing any POST data.
205 $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';
206 if ( ! wp_verify_nonce( $nonce, 'subscrpt_list_action' ) ) {
207 wp_die( esc_html__( 'Security check failed.', 'subscription' ) );
208 }
209 // Handle bulk actions
210 if ( isset( $_POST['bulk_action'] ) || isset( $_POST['bulk_action2'] ) ) {
211 $bulk_action = isset( $_POST['bulk_action'] ) ? sanitize_text_field( wp_unslash( $_POST['bulk_action'] ) ) : sanitize_text_field( wp_unslash( $_POST['bulk_action2'] ?? '' ) );
212 $action = isset( $_POST['action'] ) ? sanitize_text_field( wp_unslash( $_POST['action'] ) ) : sanitize_text_field( wp_unslash( $_POST['action2'] ?? '' ) );
213
214 if ( $bulk_action && $action && $action !== '-1' && isset( $_POST['subscription_ids'] ) && is_array( $_POST['subscription_ids'] ) ) {
215 $subscription_ids = array_map( 'intval', $_POST['subscription_ids'] );
216
217 if ( $action === 'trash' ) {
218 foreach ( $subscription_ids as $sub_id ) {
219 wp_trash_post( $sub_id );
220 }
221 } elseif ( $action === 'restore' ) {
222 foreach ( $subscription_ids as $sub_id ) {
223 wp_untrash_post( $sub_id );
224 }
225 } elseif ( $action === 'delete' ) {
226 foreach ( $subscription_ids as $sub_id ) {
227 wp_delete_post( $sub_id, true );
228 }
229 }
230
231 wp_safe_redirect( admin_url( 'admin.php?page=wp-subscription' ) );
232 exit;
233 }
234 }
235
236 // Handle filter form submission
237 if ( isset( $_POST['filter_action'] ) ) {
238 $filter_params = array();
239
240 if ( ! empty( $_POST['subscrpt_status'] ) ) {
241 $filter_params['subscrpt_status'] = sanitize_text_field( wp_unslash( $_POST['subscrpt_status'] ) );
242 }
243 if ( ! empty( $_POST['date_filter'] ) ) {
244 $filter_params['date_filter'] = sanitize_text_field( wp_unslash( $_POST['date_filter'] ) );
245 }
246 if ( ! empty( $_POST['s'] ) ) {
247 $filter_params['s'] = sanitize_text_field( wp_unslash( $_POST['s'] ) );
248 }
249 if ( ! empty( $_POST['per_page'] ) ) {
250 $filter_params['per_page'] = intval( $_POST['per_page'] );
251 }
252
253 $redirect_url = add_query_arg( $filter_params, admin_url( 'admin.php?page=wp-subscription' ) );
254 wp_safe_redirect( $redirect_url );
255 exit;
256 }
257 }
258
259 // Handle individual actions
260 if ( isset( $_GET['action'] ) && ! empty( $_GET['sub_id'] ) ) {
261 $sub_id = intval( $_GET['sub_id'] );
262 $action = sanitize_text_field( wp_unslash( $_GET['action'] ) );
263 $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
264
265 // Clean trash action.
266 if ( $action === 'clean_trash' ) {
267 // Verify nonce for security.
268 $nonce_action = 'wpsubs_action_clean_trash';
269 if ( ! wp_verify_nonce( $nonce, $nonce_action ) ) {
270 echo '<div class="notice notice-error"><p>' . esc_html__( 'Security check failed. Please try again.', 'subscription' ) . '</p></div>';
271 wp_die();
272 }
273
274 // Clean all trash items.
275 $trash_posts = get_posts(
276 [
277 'post_type' => 'subscrpt_order',
278 'post_status' => 'trash',
279 'numberposts' => -1,
280 'fields' => 'ids',
281 ]
282 );
283
284 foreach ( $trash_posts as $trash_id ) {
285 wp_delete_post( $trash_id, true );
286 }
287
288 wp_safe_redirect( admin_url( 'admin.php?page=wp-subscription&subscrpt_status=trash' ) );
289 exit;
290 } else {
291 // For other actions, verify nonce with subscription ID.
292 $nonce_action = 'wpsubs_action_' . $sub_id;
293 if ( ! wp_verify_nonce( $nonce, $nonce_action ) ) {
294 echo '<div class="notice notice-error"><p>' . esc_html__( 'Security check failed. Please try again.', 'subscription' ) . '</p></div>';
295 wp_die();
296 }
297
298 $redirect_url = admin_url( 'admin.php?page=wp-subscription' );
299
300 switch ( $action ) {
301 case 'duplicate':
302 $post = get_post( $sub_id );
303 if ( $post && $post->post_type === 'subscrpt_order' ) {
304 $new_post = [
305 'post_title' => $post->post_title . ' (Copy)',
306 'post_content' => $post->post_content,
307 'post_status' => 'draft',
308 'post_type' => 'subscrpt_order',
309 ];
310 $new_id = wp_insert_post( $new_post );
311 if ( $new_id ) {
312 $meta = get_post_meta( $sub_id );
313 foreach ( $meta as $key => $values ) {
314 foreach ( $values as $value ) {
315 add_post_meta( $new_id, $key, maybe_unserialize( $value ) );
316 }
317 }
318 }
319 }
320 break;
321 case 'trash':
322 wp_trash_post( $sub_id );
323 break;
324 case 'restore':
325 wp_untrash_post( $sub_id );
326 break;
327 case 'delete':
328 wp_delete_post( $sub_id, true );
329 $redirect_url = admin_url( 'admin.php?page=wp-subscription&subscrpt_status=trash' );
330 break;
331 }
332
333 wp_safe_redirect( $redirect_url );
334 exit;
335 }
336 }
337
338 $args = [
339 'post_type' => 'subscrpt_order',
340 'post_status' => 'any',
341 'posts_per_page' => $per_page,
342 'paged' => $paged,
343 'orderby' => 'date',
344 'order' => 'DESC',
345 ];
346
347 if ( $status ) {
348 $args['post_status'] = $status;
349 }
350 // Search only by subscription ID
351 if ( $search !== '' ) {
352 if ( is_numeric( $search ) ) {
353 $args['p'] = intval( $search );
354 } else {
355 // If not numeric, return no results
356 $args['post__in'] = array( 0 );
357 }
358 }
359 // Dynamic date filter (YYYY-MM)
360 if ( $date_filter && preg_match( '/^\d{4}-\d{2}$/', $date_filter ) ) {
361 $year = substr( $date_filter, 0, 4 );
362 $month = substr( $date_filter, 5, 2 );
363 $args['date_query'][] = [
364 'year' => intval( $year ),
365 'month' => intval( $month ),
366 ];
367 }
368
369 $query = new \WP_Query( $args );
370 $subscriptions = $query->posts;
371 $total = $query->found_posts;
372 $max_num_pages = $query->max_num_pages;
373
374 // Get all possible statuses for filter dropdown
375 $all_statuses = get_post_stati( [ 'show_in_admin_all_list' => true ], 'objects' );
376
377 include __DIR__ . '/views/subscription-list.php';
378 ?>
379 <div style="text-align:center;margin:38px 0 0 0;font-size:14px;color:#888;">
380 Made with <span style="color:#e25555;font-size:1.1em;"></span> by the WPSubscription Team
381 <div style="margin-top:6px;">
382 <a href="https://wpsubscription.co/contact" target="_blank" style="color:#2563eb;text-decoration:none;">Support</a>
383 &nbsp;/&nbsp;
384 <a href="https://docs.converslabs.com/en" target="_blank" style="color:#2563eb;text-decoration:none;">Docs</a>
385 </div>
386 </div>
387 <?php
388 }
389
390 /**
391 * Render Stats page
392 */
393 public function render_stats_page() {
394 $this->render_admin_header( __( 'Reports', 'subscription' ), __( 'View your subscription analytics', 'subscription' ) );
395
396 if ( ! subscrpt_pro_activated() ) {
397 $args = [
398 'preview_image_url' => SUBSCRPT_ASSETS . '/images/previews/reports-page-preview.png',
399 'cta_title' => __( 'Unlock Subscription Reports', 'subscription' ),
400 'cta_description' => __( 'Subscription Reports require WPSubscription Pro. Unlock advanced features, priority support, and more with WPSubscription Pro.', 'subscription' ),
401 ];
402 $preview_content = subscrpt_render_page_preview( $args );
403 echo $preview_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Content is already escaped. Re-escaping will break the HTML structure.
404 } else {
405 // Allow pro plugin to override the entire stats page content.
406 do_action( 'subscrpt_render_stats_page' );
407 }
408
409 $this->render_admin_footer();
410 }
411
412 /**
413 * Render Health page
414 */
415 public function render_health_page() {
416 $this->render_admin_header( __( 'Health', 'subscription' ), __( 'Monitor your subscription health', 'subscription' ) );
417
418 if ( ! subscrpt_pro_activated() ) {
419 $args = [
420 'preview_image_url' => SUBSCRPT_ASSETS . '/images/previews/subscrpt-health-preview.png',
421 'cta_title' => __( 'Unlock Subscription Health', 'subscription' ),
422 'cta_description' => __( 'Subscription Health requires WPSubscription Pro. Unlock advanced features, priority support, and more with WPSubscription Pro.', 'subscription' ),
423 ];
424 $preview_content = subscrpt_render_page_preview( $args );
425 echo $preview_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Content is already escaped. Re-escaping will break the HTML structure.
426 } else {
427 // Allow pro plugin to render the full health page content.
428 do_action( 'subscrpt_render_health_page' );
429 }
430
431 $this->render_admin_footer();
432 }
433
434 /**
435 * Render Support page
436 */
437 public function render_support_page() {
438 $this->render_admin_header( __( 'Support', 'subscription' ), __( 'Get help and resources', 'subscription' ) );
439 ?>
440 <div class="wp-subscription-admin-content" style="max-width:1240px;margin:32px auto 0 auto">
441 <?php if ( ! class_exists( 'Sdevs_Wc_Subscription_Pro' ) ) : ?>
442 <!-- HERO VARIANT 1: Emoji -->
443 <div class="wp-subscription-hero-upgrade" style="margin-bottom:18px;">
444 <div class="wp-subscription-hero-content">
445 <span class="wp-subscription-hero-icon"></span>
446 <span class="wp-subscription-hero-title">
447 Unlock advanced features, priority support,<br>
448 and more subscription control and reporting.
449 </span>
450 </div>
451 <a href="https://wpsubscription.co/?utm_source=plugin&utm_medium=admin&utm_campaign=upgrade_pro"
452 target="_blank"
453 class="wp-subscription-hero-btn">
454 UPGRADE TO PRO
455 </a>
456 </div>
457 <?php endif; ?>
458
459 <!-- Product Overview & Video -->
460 <div class="wp-subscription-admin-box" style="margin-bottom:24px;display:flex;gap:32px;align-items:flex-start;flex-wrap:wrap;">
461 <div style="flex:1;">
462 <h3>Product Overview</h3>
463 <p style="font-size:14px;line-height:1.7;margin:0 0 10px 0;">
464 WPSubscription helps you to sell products and services on a recurring basis using your existing WooCommerce store. Whether you're offering digital licenses, physical product boxes, or ongoing service plans, this plugin provides the tools to build and manage subscription models.
465 </p>
466 <ul style="font-size:14px;line-height:1.6;margin:0 0 0 18px;padding:0;list-style:disc;">
467 <li>Create simple or variable subscription products</li>
468 <li>Set billing intervals (daily, weekly, monthly, yearly)</li>
469 <li>Offer free trials and sign-up fees</li>
470 <li>Allow customers to cancel or renew subscriptions manually</li>
471 <li>View and manage subscriptions from the admin dashboard</li>
472 <li>Customize subscription behavior and role assignment</li>
473 <li>Integrate with payment gateways that support recurring billing (Stripe, PayPal)</li>
474 </ul>
475 </div>
476 <div style="flex:1;">
477 <div style="background:#f4f7fa;border-radius:8px;padding:10px 10px 6px 10px;box-shadow:0 2px 8px #e0e7ef;">
478 <div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:6px;text-align: center;">
479 <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/o8usgcZp1nY?si=iPb5z7bvcy0rIyOQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
480 ß </div>
481 <!-- <div style="text-align:center;font-size:13px;color:#888;margin-top:4px;">Watch: Quick Product Tour</div> -->
482 </div>
483 </div>
484 </div>
485
486 <!-- PRO Features List -->
487 <div class="wp-subscription-admin-box wp-subscription-pro-features" style="margin-bottom:32px;">
488 <div style="font-size:1.3em;font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:10px;">
489 <svg width="28" height="28" fill="none" viewBox="0 0 28 28"><circle cx="14" cy="14" r="14" fill="#2196f3"/><path d="M9 14.5l3 3 7-7" stroke="#fff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
490 <span>WPSubscription PRO Features</span>
491 </div>
492 <ul class="wp-subscription-pro-feature-list" style="list-style:none;padding:0;margin:0;display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:18px;">
493 <li style="background:#f4f7fa;border-radius:8px;padding:18px 20px;display:flex;align-items:center;gap:14px;box-shadow:0 2px 8px #e0e7ef;">
494 <span style="font-size:1.5em;color:#2196f3;">🔀</span>
495 <span><b>Variable Product</b><br><span style="color:#555;font-size:0.98em;">Offer flexible subscription options for variable products.</span></span>
496 </li>
497 <li style="background:#f4f7fa;border-radius:8px;padding:18px 20px;display:flex;align-items:center;gap:14px;box-shadow:0 2px 8px #e0e7ef;">
498 <span style="font-size:1.5em;color:#2196f3;">🚚</span>
499 <span><b>Delivery Schedule</b><br><span style="color:#555;font-size:0.98em;">Set custom delivery intervals for each subscription.</span></span>
500 </li>
501 <li style="background:#f4f7fa;border-radius:8px;padding:18px 20px;display:flex;align-items:center;gap:14px;box-shadow:0 2px 8px #e0e7ef;">
502 <span style="font-size:1.5em;color:#2196f3;">📜</span>
503 <span><b>Subscription History</b><br><span style="color:#555;font-size:0.98em;">Track all changes and events for every subscription.</span></span>
504 </li>
505 <li style="background:#f4f7fa;border-radius:8px;padding:18px 20px;display:flex;align-items:center;gap:14px;box-shadow:0 2px 8px #e0e7ef;">
506 <span style="font-size:1.5em;color:#2196f3;">⏳</span>
507 <span><b>More Subscription Durations</b><br><span style="color:#555;font-size:0.98em;">Offer more flexible and custom subscription periods.</span></span>
508 </li>
509 <li style="background:#f4f7fa;border-radius:8px;padding:18px 20px;display:flex;align-items:center;gap:14px;box-shadow:0 2px 8px #e0e7ef;">
510 <span style="font-size:1.5em;color:#2196f3;">💶</span>
511 <span><b>Sign Up Fee</b><br><span style="color:#555;font-size:0.98em;">Charge a one-time sign up fee for new subscribers.</span></span>
512 </li>
513 <li style="background:#f4f7fa;border-radius:8px;padding:18px 20px;display:flex;align-items:center;gap:14px;box-shadow:0 2px 8px #e0e7ef;">
514 <span style="font-size:1.5em;color:#2196f3;">⏩</span>
515 <span><b>Early Renewal</b><br><span style="color:#555;font-size:0.98em;">Allow customers to renew their subscription before expiry.</span></span>
516 </li>
517 <li style="background:#f4f7fa;border-radius:8px;padding:18px 20px;display:flex;align-items:center;gap:14px;box-shadow:0 2px 8px #e0e7ef;">
518 <span style="font-size:1.5em;color:#2196f3;">💳</span>
519 <span><b>Renewal Price</b><br><span style="color:#555;font-size:0.98em;">Set a different price for subscription renewals.</span></span>
520 </li>
521 </ul>
522 </div>
523
524 <!-- Support Resources: 2 rows, 2 columns, each in its own box -->
525 <div class="wp-subscription-support-resources" style="display:grid;grid-template-columns:repeat(2,1fr);gap:24px;margin-bottom:24px;">
526 <div class="wp-subscription-admin-box">
527 <h3>Documentation</h3>
528 <p style="font-size:14px;margin:0 0 8px 0;">Read our <a href="https://docs.converslabs.com/en\" target=\"_blank\" style=\"color:#2271b1;\">comprehensive docs</a> for setup, migration, and advanced usage.</p>
529 <a href="https://docs.converslabs.com/en" target="_blank" class="button button-small" style="font-size:13px;padding:5px 14px;">View Docs</a>
530 </div>
531 <div class="wp-subscription-admin-box">
532 <h3>Facing An Issue?</h3>
533 <p style="font-size:14px;margin:0 0 8px 0;">If you have a problem, <a href="https://wpsubscription.co/contact\" target=\"_blank\" style=\"color:#d93025;\">open a support ticket</a> or check our FAQ.</p>
534 <a href="https://wpsubscription.co/contact" target="_blank" class="button button-small" style="font-size:13px;padding:5px 14px;">Get Support</a>
535 </div>
536 <div class="wp-subscription-admin-box">
537 <h3>Request a Feature</h3>
538 <p style="font-size:14px;margin:0 0 8px 0;">Have an idea? <a href="https://wpsubscription.co/contact\" target=\"_blank\" style=\"color:#2271b1;\">Request a feature</a> or vote on others.</p>
539 <a href="https://wpsubscription.co/contact" target="_blank" class="button button-small" style="font-size:13px;padding:5px 14px;">Request Feature</a>
540 </div>
541 <div class="wp-subscription-admin-box">
542 <h3>Show Your Love</h3>
543 <p style="font-size:14px;margin:0 0 8px 0;">Enjoying WPSubscription? <a href="https://wordpress.org/support/plugin/subscription/reviews/\" target=\"_blank\" style=\"color:#f59e42;\">Leave us a review</a> or share your experience!</p>
544 <a href="https://wordpress.org/support/plugin/subscription/reviews/" target="_blank" class="button button-small" style="font-size:13px;padding:5px 14px;">Leave a Review</a>
545 </div>
546 </div>
547 </div>
548 <div style="text-align:center;margin:38px 0 0 0;font-size:14px;color:#888;">
549 Made with <span style="color:#e25555;font-size:1.1em;">♥</span> by the WPSubscription Team
550 <div style="margin-top:6px;">
551 <a href="https://wpsubscription.co/contact" target="_blank" style="color:#2563eb;text-decoration:none;">Support</a>
552 &nbsp;/&nbsp;
553 <a href="https://docs.converslabs.com/en" target="_blank" style="color:#2563eb;text-decoration:none;">Docs</a>
554 </div>
555 </div>
556 <?php
557 }
558
559 /**
560 * Render the legacy subscriptions list page (WP_List_Table based)
561 */
562 public function render_legacy_subscriptions_page() {
563 // No longer needed, as the menu now links directly to the post type list.
564 }
565
566 /**
567 * Handle bulk action AJAX
568 */
569 public function handle_bulk_action_ajax() {
570 // Verify nonce
571 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
572 if ( ! wp_verify_nonce( $nonce, 'subscrpt_bulk_action_nonce' ) ) {
573 wp_send_json_error( array( 'message' => __( 'Security check failed.', 'subscription' ) ) );
574 }
575
576 // Check permissions
577 if ( ! current_user_can( 'manage_woocommerce' ) ) {
578 wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'subscription' ) ) );
579 }
580
581 // Get action and subscription IDs
582 $bulk_action = isset( $_POST['bulk_action'] ) ? sanitize_text_field( wp_unslash( $_POST['bulk_action'] ) ) : '';
583 $subscription_ids = isset( $_POST['subscription_ids'] ) ? array_map( 'intval', $_POST['subscription_ids'] ) : array();
584
585 if ( empty( $subscription_ids ) ) {
586 wp_send_json_error( array( 'message' => __( 'No subscriptions selected.', 'subscription' ) ) );
587 }
588
589 $processed_count = 0;
590 $errors = array();
591
592 foreach ( $subscription_ids as $subscription_id ) {
593 $post = get_post( $subscription_id );
594
595 if ( ! $post || $post->post_type !== 'subscrpt_order' ) {
596 // translators: Subscription ID.
597 $errors[] = sprintf( __( 'Subscription #%d not found.', 'subscription' ), $subscription_id );
598 continue;
599 }
600
601 try {
602 switch ( $bulk_action ) {
603 case 'trash':
604 if ( wp_trash_post( $subscription_id ) ) {
605 ++$processed_count;
606 } else {
607 $errors[] = sprintf(
608 // translators: Subscription ID.
609 __( 'Failed to move subscription #%d to trash.', 'subscription' ),
610 $subscription_id
611 );
612 }
613 break;
614
615 case 'restore':
616 if ( wp_untrash_post( $subscription_id ) ) {
617 ++$processed_count;
618 } else {
619 $errors[] = sprintf(
620 // translators: Subscription ID.
621 __( 'Failed to restore subscription #%d.', 'subscription' ),
622 $subscription_id
623 );
624 }
625 break;
626
627 case 'delete':
628 if ( wp_delete_post( $subscription_id, true ) ) {
629 ++$processed_count;
630 } else {
631 $errors[] = sprintf(
632 // translators: Subscription ID.
633 __( 'Failed to delete subscription #%d.', 'subscription' ),
634 $subscription_id
635 );
636 }
637 break;
638
639 default:
640 $errors[] = sprintf(
641 // translators: Bulk action.
642 __( 'Unknown action: %s', 'subscription' ),
643 $bulk_action
644 );
645 break;
646 }
647 } catch ( Exception $e ) {
648 $errors[] = sprintf(
649 // translators: Subscription ID, Error message.
650 __( 'Error processing subscription #%1$d: %2$s', 'subscription' ),
651 $subscription_id,
652 $e->getMessage()
653 );
654 }
655 }
656
657 // Prepare response message
658 $message = '';
659 if ( $processed_count > 0 ) {
660 switch ( $bulk_action ) {
661 case 'trash':
662 $message = sprintf(
663 // translators: Number of subscriptions.
664 _n( '%d subscription moved to trash.', '%d subscriptions moved to trash.', $processed_count, 'subscription' ),
665 $processed_count
666 );
667 break;
668 case 'restore':
669 $message = sprintf(
670 // translators: Number of subscriptions.
671 _n( '%d subscription restored.', '%d subscriptions restored.', $processed_count, 'subscription' ),
672 $processed_count
673 );
674 break;
675 case 'delete':
676 $message = sprintf(
677 // translators: Number of subscriptions.
678 _n( '%d subscription permanently deleted.', '%d subscriptions permanently deleted.', $processed_count, 'subscription' ),
679 $processed_count
680 );
681 break;
682 }
683 }
684
685 if ( ! empty( $errors ) ) {
686 $message .= ' ' . __( 'Some errors occurred:', 'subscription' ) . ' ' . implode( ', ', $errors );
687 }
688
689 if ( $processed_count > 0 ) {
690 wp_send_json_success( array( 'message' => $message ) );
691 } else {
692 wp_send_json_error( array( 'message' => $message ? $message : __( 'No subscriptions were processed.', 'subscription' ) ) );
693 }
694 }
695 }
696