PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 2.0.0
Subscriptions for WooCommerce with Stripe Recurring Payments v2.0.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
← All changes | includes/Admin/Product.php +151 -55 1.3.22.0.0 View file →
@@ -1,10 +1,19 @@
1 1 <?php
2 +/**
3 + * Product editor integration (Subscription tab).
4 + *
5 + * @package SpringDevs\Subscription\Admin
6 + */
2 7
3 8 namespace SpringDevs\Subscription\Admin;
4 9
5 10 use SpringDevs\Subscription\Illuminate\Helper;
6 11
12 +// HPOS: This file does not access WooCommerce order data directly.
13 +// All meta access is for product or subscription data only, not WooCommerce order data.
14 +// If you add new order data access, use WooCommerce CRUD for HPOS compatibility.
15 +
7 16 /**
8 17 * Product class
9 18 *
10 19 * @package SpringDevs\Subscription\Admin
@@ -17,9 +26,8 @@
17 26 public function __construct() {
18 27 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
19 28 add_filter( 'woocommerce_product_data_tabs', array( $this, 'register_tab' ) );
20 29 add_action( 'woocommerce_product_data_panels', array( $this, 'subscription_forms' ) );
21 - add_filter( 'product_type_options', array( $this, 'add_product_type_options' ) );
22 30 add_action( 'save_post_product', array( $this, 'save_subscrpt_data' ) );
23 31 add_filter( 'woocommerce_get_price_html', array( $this, 'change_price_html' ), 10, 2 );
24 32 }
25 33
@@ -37,16 +45,17 @@
37 45 }
38 46
39 47 $enabled = $product->get_meta( '_subscrpt_enabled' );
40 48 if ( $enabled ) :
41 - $type = Helper::get_typos( 1, $product->get_meta( '_subscrpt_timing_option' ) );
49 + $type = Helper::get_typos( 1, $product->get_meta( '_subscrpt_timing_option' ), true );
42 50 $meta_trial_time = $product->get_meta( '_subscrpt_trial_timing_per' );
43 51 $trial = null;
44 52 if ( ! empty( $meta_trial_time ) && $meta_trial_time > 0 ) {
45 - $trial = '<br/> + Get ' . $meta_trial_time . ' ' . Helper::get_typos( $meta_trial_time, $product->get_meta( '_subscrpt_trial_timing_option' ) ) . ' free trial!';
53 + $trial_type = Helper::get_typos( $meta_trial_time, $product->get_meta( '_subscrpt_trial_timing_option' ), true );
54 + $trial = '<br/> + Get ' . $meta_trial_time . ' ' . ucfirst( $trial_type ) . ' free trial!';
46 55 }
47 56
48 - $price_html = $price . ' / ' . $type . $trial;
57 + $price_html = $price . ' / ' . ucfirst( $type ) . $trial;
49 58 return $price_html;
50 59 else :
51 60 return $price;
52 61 endif;
@@ -56,8 +65,57 @@
56 65 * Enqueue Assets.
57 66 */
58 67 public function enqueue_assets() {
59 68 wp_enqueue_script( 'sdevs_subscription_admin' );
69 +
70 + // Plan view assets on the product editor. Enqueued whether or not Pro is
71 + // active: with Pro off, free renders its own panel; with Pro on, the plan
72 + // view mounts on Pro's `subscrpt_simple_plan_panel` hook.
73 + $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
74 + if ( ! $screen || 'product' !== $screen->id ) {
75 + return;
76 + }
77 +
78 + wp_enqueue_style( 'subscrpt_admin_components' );
79 + wp_enqueue_script( 'subscrpt_admin_components' );
80 +
81 + // Shared plan-group + selling-plan modal logic. With Pro active the
82 + // Subscription tab exposes "+ New plan group", which drives these modals.
83 + wp_enqueue_script( 'subscrpt_plan_forms_js' );
84 + wp_localize_script( 'subscrpt_plan_forms_js', 'subscrptPlanForms', \SpringDevs\Subscription\Admin\Plans::plan_forms_config() );
85 +
86 + wp_enqueue_script(
87 + 'subscrpt_product_plans_js',
88 + SUBSCRPT_ASSETS . '/js/admin/product-plans.js',
89 + array( 'subscrpt_admin_components', 'subscrpt_plan_forms_js' ),
90 + SUBSCRPT_VERSION,
91 + true
92 + );
93 +
94 + wp_localize_script(
95 + 'subscrpt_product_plans_js',
96 + 'subscrptProductPlans',
97 + array(
98 + 'restUrl' => esc_url_raw( rest_url( 'wpsubscription/v1/plans' ) ),
99 + 'nonce' => wp_create_nonce( 'wp_rest' ),
100 + 'currency' => function_exists( 'get_woocommerce_currency_symbol' )
101 + ? html_entity_decode( get_woocommerce_currency_symbol(), ENT_QUOTES, 'UTF-8' )
102 + : '$',
103 + 'i18n' => array(
104 + 'connectError' => __( 'Could not connect the plan. Please try again.', 'subscription' ),
105 + 'pickPlan' => __( 'Please select a plan.', 'subscription' ),
106 + 'confirmDetach' => __( 'Detach this product from the plan?', 'subscription' ),
107 + 'loading' => __( 'Loading plans…', 'subscription' ),
108 + 'step1' => __( 'Step 1 of 2 · Plan', 'subscription' ),
109 + 'step2' => __( 'Step 2 of 2 · Plan', 'subscription' ),
110 + 'wizardNext' => __( 'Continue', 'subscription' ),
111 + 'wizardBack' => __( 'Back', 'subscription' ),
112 + 'wizardCreate' => __( 'Create', 'subscription' ),
113 + 'addPlan' => __( 'Add plan', 'subscription' ),
114 + 'copyLinkPrompt' => __( 'Copy this direct checkout link:', 'subscription' ),
115 + ),
116 + )
117 + );
60 118 }
61 119
62 120 /**
63 121 * Register "Subscription" option tab.
@@ -67,10 +125,13 @@
67 125 * @return array
68 126 */
69 127 public function register_tab( $tabs ) {
70 128 $tabs['sdevs_subscription'] = array(
71 - 'label' => __( 'Subscription', 'sdevs_subscrpt' ),
72 - 'class' => array( 'show_if_simple', 'show_if_subscription' ),
129 + 'label' => __( 'Subscription', 'subscription' ),
130 + // Shown for simple and variable products. The panel content differs
131 + // by type (see subscription_forms): simple renders here in free;
132 + // variable is filled by Pro via `subscrpt_variable_subscription_panel`.
133 + 'class' => array( 'show_if_simple', 'show_if_variable' ),
73 134 'target' => 'sdevs_subscription_options',
74 135 'priority' => 11,
75 136 );
76 137 return $tabs;
@@ -76,51 +137,65 @@
76 137 return $tabs;
77 138 }
78 139
79 140 /**
80 - * Display Enable Subscription Checkbox on product data tab.
81 - *
82 - * @param array $product_type_options Product Type options on Data tab.
83 - *
84 - * @return array
141 + * Display forms on product create/edit.
85 142 */
86 - public function add_product_type_options( $product_type_options ) {
87 - $screen = get_current_screen();
88 - $value = 'no';
89 - if ( 'edit' === $screen->parent_base ) {
90 - $product = wc_get_product( get_the_ID() );
91 - if ( $product ) {
92 - $value = $product->get_meta( '_subscrpt_enabled' ) ? 'yes' : 'no';
93 - }
94 - }
143 + public function subscription_forms() {
144 + $screen = get_current_screen();
145 + $subscrpt_current = ( $screen && 'edit' === $screen->parent_base ) ? wc_get_product( get_the_ID() ) : null;
95 146
96 - $wrapper_class = apply_filters( 'subscrpt_simple_enable_checkbox_classes', 'show_if_simple' );
97 - $product_type_options['subscrpt_enable'] = array(
98 - 'id' => 'subscrpt_enable',
99 - 'wrapper_class' => $wrapper_class,
100 - 'label' => __( 'Subscription', 'sdevs_subscrpt' ),
101 - 'description' => __( 'Enable Subscriptions', 'sdevs_subscrpt' ),
102 - 'default' => $value,
103 - );
147 + // Variable products: free renders the panel shell; the content is filled
148 + // by Pro (per-variation sections) via the action below. Free itself is
149 + // simple-only, so with Pro inactive it shows an upgrade note.
150 + if ( $subscrpt_current && $subscrpt_current->is_type( 'variable' ) ) {
151 + ?>
152 + <div id="sdevs_subscription_options" class="panel woocommerce_options_panel option_group sdevs-form sdevs_panel show_if_variable" style="padding:10px;">
153 + <?php
154 + /**
155 + * Fires inside the Subscription tab for a variable product. Pro
156 + * hooks this to render its per-variation subscription sections.
157 + *
158 + * @param \WC_Product $subscrpt_current Variable product being edited.
159 + */
160 + do_action( 'subscrpt_variable_subscription_panel', $subscrpt_current );
104 161
105 - return $product_type_options;
106 - }
162 + if ( ! has_action( 'subscrpt_variable_subscription_panel' ) ) {
163 + ?>
164 + <div style="text-align:center;padding:30px 20px;">
165 + <span style="display:inline-flex;align-items:center;justify-content:center;width:48px;height:48px;border-radius:50%;background:var(--wpsubs-brand-light,#fff1eb);color:var(--wpsubs-brand,#ff4d00);">
166 + <span class="dashicons dashicons-lock" style="font-size:24px;width:24px;height:24px;"></span>
167 + </span>
168 + <h3 style="margin:14px 0 6px;font-size:15px;color:var(--wpsubs-text,#1d2327);display:flex;align-items:center;justify-content:center;gap:8px;">
169 + <?php esc_html_e( 'Variable product subscriptions', 'subscription' ); ?>
170 + <span class="wpsubs-badge wpsubs-badge--pro"><?php esc_html_e( 'Pro', 'subscription' ); ?></span>
171 + </h3>
172 + <p style="margin:0 auto;max-width:380px;font-size:13px;line-height:1.6;color:var(--wpsubs-text-muted,#646970);">
173 + <?php esc_html_e( 'Sell each variation as its own subscription, with per-variation billing, trials and sign-up fees.', 'subscription' ); ?>
174 + </p>
175 + <a href="https://wpsubscription.co/?utm_source=plugin&utm_medium=admin&utm_campaign=upgrade_pro" target="_blank" rel="noopener noreferrer" class="wpsubs-btn wpsubs-btn--primary" style="margin-top:16px;">
176 + <?php esc_html_e( 'Upgrade to Pro', 'subscription' ); ?>
177 + </a>
178 + </div>
179 + <?php
180 + }
181 + ?>
182 + </div>
183 + <?php
184 + return;
185 + }
107 186
108 - /**
109 - * Display forms on product create/edit.
110 - */
111 - public function subscription_forms() {
112 187 if ( function_exists( 'subscrpt_pro_activated' ) ) {
113 188 if ( subscrpt_pro_activated() ) {
114 189 do_action( 'subscrpt_simple_pro_fields', get_the_ID() );
115 190 } else {
116 191 $timing_types = array(
117 - 'days' => __( 'Daily', 'sdevs_subscrpt' ),
118 - 'weeks' => __( 'Weekly', 'sdevs_subscrpt' ),
119 - 'months' => __( 'Monthly', 'sdevs_subscrpt' ),
120 - 'years' => __( 'Yearly', 'sdevs_subscrpt' ),
192 + 'days' => __( 'Daily', 'subscription' ),
193 + 'weeks' => __( 'Weekly', 'subscription' ),
194 + 'months' => __( 'Monthly', 'subscription' ),
195 + 'years' => __( 'Yearly', 'subscription' ),
121 196 );
122 - $trial_timing_types = get_timing_types();
197 + $trial_timing_types = wps_subscription_get_timing_types();
123 198 $subscrpt_timing = null;
124 199 $subscrpt_trial_time = null;
125 200 $subscrpt_trial_timing = null;
126 201 $subscrpt_cart_txt = 'subscribe';
@@ -125,22 +200,43 @@
125 200 $subscrpt_trial_timing = null;
126 201 $subscrpt_cart_txt = 'subscribe';
127 202 $subscrpt_user_cancell = 'yes';
128 203 $subscrpt_limit = 'one';
204 + $subscrpt_enabled = false;
129 205
130 - $screen = get_current_screen();
206 + $subscrpt_product = null;
207 + $screen = get_current_screen();
131 208 if ( 'edit' === $screen->parent_base ) {
132 - $product = wc_get_product( get_the_ID() );
133 - if ( $product ) {
134 - $subscrpt_timing = $product->get_meta( '_subscrpt_timing_option' );
135 - $subscrpt_trial_time = $product->get_meta( '_subscrpt_trial_timing_per' );
136 - $subscrpt_trial_timing = $product->get_meta( '_subscrpt_trial_timing_option' );
137 - $subscrpt_cart_txt = $product->get_meta( '_subscrpt_cart_btn_label' );
138 - $subscrpt_user_cancell = $product->get_meta( '_subscrpt_user_cancel' );
139 - $subscrpt_limit = $product->get_meta( '_subscrpt_limit' );
209 + $subscrpt_product = wc_get_product( get_the_ID() );
210 + if ( $subscrpt_product ) {
211 + $subscrpt_enabled = (bool) $subscrpt_product->get_meta( '_subscrpt_enabled' );
212 + $subscrpt_timing = $subscrpt_product->get_meta( '_subscrpt_timing_option' );
213 + $subscrpt_trial_time = $subscrpt_product->get_meta( '_subscrpt_trial_timing_per' );
214 + $subscrpt_trial_timing = $subscrpt_product->get_meta( '_subscrpt_trial_timing_option' );
215 + $subscrpt_cart_txt = $subscrpt_product->get_meta( '_subscrpt_cart_btn_label' );
216 + $subscrpt_user_cancell = $subscrpt_product->get_meta( '_subscrpt_user_cancel' );
217 + $subscrpt_limit = $subscrpt_product->get_meta( '_subscrpt_limit' );
140 218 }
141 219 }
142 - include 'views/product-form.php';
220 +
221 + // Simple products: plan view (default) + hidden classic settings.
222 + // Variable/other products keep the classic-only panel unchanged.
223 + if ( $subscrpt_product && $subscrpt_product->is_type( 'simple' ) && class_exists( '\SpringDevs\Subscription\Admin\Product\Plans' ) ) {
224 + ?>
225 + <div id="sdevs_subscription_options" class="panel woocommerce_options_panel option_group sdevs-form sdevs_panel show_if_simple" style="padding:10px;" data-subscrpt-product-plans data-product-id="<?php echo esc_attr( $subscrpt_product->get_id() ); ?>"<?php echo Product\Plans::should_default_classic( $subscrpt_product ) ? ' data-subscrpt-default-classic="1"' : ''; ?>>
226 + <?php Product\Plans::render_toolbar( $subscrpt_product ); ?>
227 + <div data-subscrpt-plan-view>
228 + <?php Product\Plans::render_plan_view( $subscrpt_product ); ?>
229 + </div>
230 + <div data-subscrpt-classic-view style="display:none;">
231 + <?php require __DIR__ . '/views/product-classic-fields.php'; ?>
232 + </div>
233 + </div>
234 + <?php
235 + Product\Plans::render_checkout_link_modal( $subscrpt_product );
236 + } else {
237 + include 'views/product-form.php';
238 + }
143 239 }
144 240 }
145 241 }
146 242
@@ -157,9 +253,9 @@
157 253 return;
158 254 }
159 255 }
160 256
161 - if ( ! isset( $_POST['_subscript_nonce'], $_POST['subscrpt_timing'], $_POST['subscrpt_cart_txt'], $_POST['subscrpt_user_cancel'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_subscript_nonce'] ) ), '_subscript_edit_product_nonce' ) ) {
257 + if ( ! isset( $_POST['_subscript_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_subscript_nonce'] ) ), '_subscript_edit_product_nonce' ) ) {
162 258 return;
163 259 }
164 260
165 261 remove_action( 'save_post_product', array( $this, 'save_subscrpt_data' ) );
@@ -164,13 +260,13 @@
164 260
165 261 remove_action( 'save_post_product', array( $this, 'save_subscrpt_data' ) );
166 262
167 263 $subscrpt_enable = isset( $_POST['subscrpt_enable'] );
168 - $subscrpt_timing = sanitize_text_field( wp_unslash( $_POST['subscrpt_timing'] ) );
169 - $subscrpt_trial_time = sanitize_text_field( wp_unslash( $_POST['subscrpt_trial_time'] ) );
170 - $subscrpt_trial_timing = sanitize_text_field( wp_unslash( $_POST['subscrpt_trial_timing'] ) );
171 - $subscrpt_cart_txt = sanitize_text_field( wp_unslash( $_POST['subscrpt_cart_txt'] ) );
172 - $subscrpt_user_cancel = sanitize_text_field( wp_unslash( $_POST['subscrpt_user_cancel'] ) );
264 + $subscrpt_timing = isset( $_POST['subscrpt_timing'] ) ? sanitize_text_field( wp_unslash( $_POST['subscrpt_timing'] ) ) : '';
265 + $subscrpt_trial_time = isset( $_POST['subscrpt_trial_time'] ) ? sanitize_text_field( wp_unslash( $_POST['subscrpt_trial_time'] ) ) : '';
266 + $subscrpt_trial_timing = isset( $_POST['subscrpt_trial_timing'] ) ? sanitize_text_field( wp_unslash( $_POST['subscrpt_trial_timing'] ) ) : '';
267 + $subscrpt_cart_txt = isset( $_POST['subscrpt_cart_txt'] ) ? sanitize_text_field( wp_unslash( $_POST['subscrpt_cart_txt'] ) ) : '';
268 + $subscrpt_user_cancel = isset( $_POST['subscrpt_user_cancel'] ) ? sanitize_text_field( wp_unslash( $_POST['subscrpt_user_cancel'] ) ) : '';
173 269 $subscrpt_limit = isset( $_POST['subscrpt_limit'] ) ? sanitize_text_field( wp_unslash( $_POST['subscrpt_limit'] ) ) : null;
174 270
175 271 $product = wc_get_product( $product_id );
176 272 $product->update_meta_data( '_subscrpt_enabled', $subscrpt_enable );