PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 2.4.0
Subscriptions for WooCommerce with Stripe Recurring Payments v2.4.0
2.4.0 2.3.0 2.2.0 2.1.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 All 65 releases
← All changes | includes/Frontend/Product.php +163 -40 1.3.1 → 2.4.0 View file →
@@ -1,10 +1,21 @@
1 1 <?php
2 +/**
3 + * Storefront product handling for subscriptions.
4 + *
5 + * @package SpringDevs\Subscription
6 + */
2 7
3 8 namespace SpringDevs\Subscription\Frontend;
4 9
5 10 use SpringDevs\Subscription\Illuminate\Helper;
11 +use SpringDevs\Subscription\Illuminate\Subscription\Subscription;
6 12
13 +// HPOS: This file is compatible with WooCommerce High-Performance Order Storage (HPOS).
14 +// All WooCommerce order data is accessed via WooCommerce CRUD methods (wc_get_order, wc_get_order_item_meta, etc.).
15 +// All direct post meta access is for subscription data only, not WooCommerce order data.
16 +// If you add new order data access, use WooCommerce CRUD for HPOS compatibility.
17 +
7 18 /**
8 19 * Product class
9 20 * control single product page
10 21 */
@@ -13,16 +24,54 @@
13 24 /**
14 25 * Initialize the class
15 26 */
16 27 public function __construct() {
17 - add_filter( 'woocommerce_product_single_add_to_cart_text', array( $this, 'change_single_add_to_cart_text' ) );
18 - add_filter( 'woocommerce_product_add_to_cart_text', array( $this, 'change_single_add_to_cart_text' ) );
28 + add_filter(
29 + 'woocommerce_product_single_add_to_cart_text',
30 + array(
31 + $this,
32 + 'change_single_add_to_cart_text',
33 + ),
34 + 10,
35 + 2
36 + );
37 + add_filter( 'woocommerce_product_add_to_cart_text', array( $this, 'change_loop_add_to_cart_text' ), 10, 2 );
38 + // Plan-tied simple products behave like a variable product on the shop /
39 + // Product Collection loops: a "Select options" link to the product page (so
40 + // the customer picks a plan), never a direct/ajax add-to-cart. These native
41 + // product-method filters drive both the classic loop and the block button.
42 + add_filter( 'woocommerce_product_add_to_cart_url', array( $this, 'plan_loop_add_to_cart_url' ), 10, 2 );
43 + add_filter( 'woocommerce_product_supports', array( $this, 'plan_loop_disable_ajax' ), 10, 3 );
19 44 add_filter( 'woocommerce_get_price_html', array( $this, 'change_price_html' ), 10, 2 );
20 45 add_filter( 'woocommerce_quantity_input_args', array( $this, 'update_input_args' ), 10, 2 );
21 46 add_filter( 'woocommerce_add_to_cart', array( $this, 'update_cart_quantity' ), 10, 3 );
22 - add_filter( 'woocommerce_store_api_product_quantity_minimum', array( $this, 'update_quantity_min_max' ), 10, 2 );
23 - add_filter( 'woocommerce_store_api_product_quantity_maximum', array( $this, 'update_quantity_min_max' ), 10, 2 );
24 - add_action( 'woocommerce_after_cart_item_quantity_update', array( $this, 'validate_quantity_on_manual_renewal' ), 10, 2 );
47 + add_filter(
48 + 'woocommerce_store_api_product_quantity_minimum',
49 + array(
50 + $this,
51 + 'update_quantity_min_max',
52 + ),
53 + 10,
54 + 2
55 + );
56 + add_filter(
57 + 'woocommerce_store_api_product_quantity_maximum',
58 + array(
59 + $this,
60 + 'update_quantity_min_max',
61 + ),
62 + 10,
63 + 2
64 + );
65 + add_action(
66 + 'woocommerce_after_cart_item_quantity_update',
67 + array(
68 + $this,
69 + 'validate_quantity_on_manual_renewal',
70 + ),
71 + 10,
72 + 2
73 + );
25 74 add_filter( 'woocommerce_is_purchasable', array( $this, 'check_if_purchasable' ), 20, 2 );
26 75 add_action( 'woocommerce_single_product_summary', array( $this, 'text_if_active' ) );
27 76 add_filter( 'woocommerce_loop_add_to_cart_link', array( $this, 'remove_button_active_products' ), 10, 2 );
28 77 }
@@ -35,14 +84,15 @@
35 84 *
36 85 * @return mixed
37 86 */
38 87 public function remove_button_active_products( $button, $product ) {
88 + $product = Subscription::get_subs_product( $product );
39 89 if ( ! $product->is_type( 'simple' ) ) {
40 90 return $button;
41 91 }
42 - $enabled = $product->get_meta( '_subscrpt_enabled' );
43 - if ( $enabled ) {
44 - $limit = $product->get_meta( '_subscrpt_limit' );
92 +
93 + if ( $product->is_enabled() ) {
94 + $limit = $product->get_limit();
45 95 if ( 'one' === $limit ) {
46 96 $unexpired = Helper::subscription_exists( $product->get_id(), array( 'active', 'pending' ) );
47 97 if ( $unexpired ) {
48 98 return;
@@ -59,28 +109,29 @@
59 109 * Display notice if already purchased.
60 110 */
61 111 public function text_if_active() {
62 112 global $product;
63 - if ( ! $product->is_type( 'simple' ) ) {
113 + $sdevs_product = Subscription::get_subs_product( $product );
114 + if ( ! $sdevs_product->is_type( 'simple' ) ) {
64 115 return;
65 116 }
66 - $enabled = $product->get_meta( '_subscrpt_enabled' );
67 - if ( $enabled ) {
68 - $limit = $product->get_meta( '_subscrpt_limit' );
117 +
118 + if ( $sdevs_product->is_enabled() ) {
119 + $limit = $sdevs_product->get_limit();
69 120 if ( 'unlimited' === $limit ) {
70 121 return;
71 122 }
72 123 if ( 'one' === $limit ) {
73 - $unexpired = Helper::subscription_exists( $product->get_id(), array( 'active', 'pending' ) );
124 + $unexpired = Helper::subscription_exists( $sdevs_product->get_id(), array( 'active', 'pending' ) );
74 125 if ( ! $unexpired ) {
75 126 return false;
76 127 } else {
77 - echo '<strong>' . esc_html_e( 'You Already Subscribed These Product!', 'sdevs_subscrpt' ) . '</strong>';
128 + echo '<strong>' . esc_html_e( 'You Already Subscribed These Product!', 'subscription' ) . '</strong>';
78 129 }
79 130 }
80 131 if ( 'only_one' === $limit ) {
81 - if ( ! Helper::check_trial( $product->get_id() ) ) {
82 - echo '<strong>' . esc_html_e( 'You Already Subscribed These Product!', 'sdevs_subscrpt' ) . '</strong>';
132 + if ( ! Helper::check_trial( $sdevs_product->get_id() ) ) {
133 + echo '<strong>' . esc_html_e( 'You Already Subscribed These Product!', 'subscription' ) . '</strong>';
83 134 }
84 135 }
85 136 }
86 137 }
@@ -85,19 +136,19 @@
85 136 }
86 137 }
87 138
88 139 /**
89 - * Check if product pruchasable.
140 + * Check if product purchasable.
90 141 *
91 142 * @param boolean $is_purchasable True\False.
92 - * @param \WC_Product $product Product.
143 + * @param \WC_Product $product Product.
93 144 *
94 145 * @return boolean
95 146 */
96 147 public function check_if_purchasable( $is_purchasable, $product ) {
97 - $enabled = $product->get_meta( '_subscrpt_enabled' );
98 - if ( $enabled ) {
99 - $limit = $product->get_meta( '_subscrpt_limit' );
148 + $product = Subscription::get_subs_product( $product );
149 + if ( $product->is_enabled() ) {
150 + $limit = $product->get_limit();
100 151 if ( 'unlimited' === $limit ) {
101 152 return true;
102 153 } elseif ( 'only_one' === $limit ) {
103 154 return Helper::check_trial( $product->get_id() );
@@ -104,8 +155,9 @@
104 155 } elseif ( 'one' === $limit ) {
105 156 return ! Helper::subscription_exists( $product->get_id(), array( 'active', 'pending' ) );
106 157 }
107 158 }
159 +
108 160 return $is_purchasable;
109 161 }
110 162
111 163 /**
@@ -123,9 +175,10 @@
123 175 $order_item_id = get_post_meta( $expired, '_subscrpt_order_item_id', true );
124 176 $item_quantity = (int) wc_get_order_item_meta( $order_item_id, '_qty', true );
125 177 if ( $item_quantity !== $quantity ) {
126 178 WC()->cart->set_quantity( $cart_item_key, $item_quantity );
127 - wc_add_notice( 'You can only add ' . $item_quantity . ' ' . ( $item_quantity > 1 ? 'items' : 'item' ) . ' on cart!', 'error' );
179 + /* translators: %d: the number of items allowed in the cart. */
180 + wc_add_notice( sprintf( _n( 'You can only add %d item to the cart.', 'You can only add %d items to the cart.', $item_quantity, 'subscription' ), $item_quantity ), 'error' );
128 181 }
129 182 }
130 183 }
131 184
@@ -144,8 +197,9 @@
144 197 $item_quantity = (int) wc_get_order_item_meta( $order_item_id, '_qty', true );
145 198
146 199 return $item_quantity;
147 200 }
201 +
148 202 return $value;
149 203 }
150 204
151 205 /**
@@ -163,9 +217,10 @@
163 217 $order_item_id = get_post_meta( $expired, '_subscrpt_order_item_id', true );
164 218 $item_quantity = (int) wc_get_order_item_meta( $order_item_id, '_qty', true );
165 219 if ( $item_quantity !== $quantity ) {
166 220 WC()->cart->set_quantity( $cart_item_key, $item_quantity );
167 - wc_add_notice( 'You can only add ' . $item_quantity . ' ' . ( $item_quantity > 1 ? 'items' : 'item' ) . ' on cart!', 'error' );
221 + /* translators: %d: the number of items allowed in the cart. */
222 + wc_add_notice( sprintf( _n( 'You can only add %d item to the cart.', 'You can only add %d items to the cart.', $item_quantity, 'subscription' ), $item_quantity ), 'error' );
168 223 }
169 224 }
170 225 }
171 226
@@ -193,20 +248,21 @@
193 248
194 249 /**
195 250 * Change single product add-to-cart button text.
196 251 *
197 - * @param String $text Add-to-cart button Text.
252 + * @param string $text Add-to-cart button Text.
253 + * @param \WC_Product $product Product Object.
198 254 */
199 - public function change_single_add_to_cart_text( $text ) {
200 - global $product;
201 - if ( ! $product || $product->is_type( 'variable' ) || '' === $product->get_price() ) {
255 + public function change_single_add_to_cart_text( $text, $product ) {
256 + $product = Subscription::get_subs_product( $product );
257 + if ( $product->is_type( 'variable' ) || '' === $product->get_price() ) {
202 258 return $text;
203 259 }
204 - $cart_btn_label = $product->get_meta( '_subscrpt_cart_btn_label' );
260 + $cart_btn_label = $product->get_button_label();
205 261 $expired = Helper::subscription_exists( $product->get_id(), 'expired' );
206 262 if ( $expired ) {
207 - $text = __( 'Renew', 'sdevs_subscrpt' );
208 - } elseif ( $product->get_meta( '_subscrpt_enabled' ) && $cart_btn_label ) {
263 + $text = __( 'Renew', 'subscription' );
264 + } elseif ( $product->is_enabled() && $cart_btn_label ) {
209 265 $text = $cart_btn_label;
210 266 }
211 267
212 268 return $text;
@@ -212,8 +268,71 @@
212 268 return $text;
213 269 }
214 270
215 271 /**
272 + * Whether this is a plan-tied simple product that should behave like a
273 + * variable product on the shop / Product Collection loops.
274 + *
275 + * @param mixed $product Product object.
276 + *
277 + * @return bool
278 + */
279 + private function is_plan_loop_product( $product ) {
280 + return $product instanceof \WC_Product
281 + && $product->is_type( 'simple' )
282 + && subscrpt_plan_offered( $product->get_id() );
283 + }
284 +
285 + /**
286 + * Loop / block add-to-cart button text: "Select options" for plan products,
287 + * otherwise the classic label logic.
288 + *
289 + * @param string $text Add-to-cart button text.
290 + * @param \WC_Product $product Product object.
291 + *
292 + * @return string
293 + */
294 + public function change_loop_add_to_cart_text( $text, $product ) {
295 + if ( $this->is_plan_loop_product( $product ) ) {
296 + return __( 'Select options', 'subscription' );
297 + }
298 +
299 + return $this->change_single_add_to_cart_text( $text, $product );
300 + }
301 +
302 + /**
303 + * Loop / block add-to-cart URL: the product page for plan products, so the
304 + * customer lands on the plan selector instead of a bare add-to-cart.
305 + *
306 + * @param string $url Add-to-cart URL.
307 + * @param \WC_Product $product Product object.
308 + *
309 + * @return string
310 + */
311 + public function plan_loop_add_to_cart_url( $url, $product ) {
312 + return $this->is_plan_loop_product( $product ) ? $product->get_permalink() : $url;
313 + }
314 +
315 + /**
316 + * Disable ajax add-to-cart for plan products so their loop / block button
317 + * renders as a "Select options" link (navigates to the product page) rather
318 + * than an ajax add.
319 + *
320 + * @param bool $supports Whether the feature is supported.
321 + * @param string $feature Feature name.
322 + * @param \WC_Product $product Product object.
323 + *
324 + * @return bool
325 + */
326 + public function plan_loop_disable_ajax( $supports, $feature, $product ) {
327 + if ( 'ajax_add_to_cart' === $feature && $this->is_plan_loop_product( $product ) ) {
328 + return false;
329 + }
330 +
331 + return $supports;
332 + }
333 +
334 + /**
216 335 * Add trial, signup fee etc. with product price.
217 336 *
218 337 * @param mixed $price Price.
219 338 * @param \WC_Product $product Product.
@@ -220,24 +339,28 @@
220 339 *
221 340 * @return mixed
222 341 */
223 342 public function change_price_html( $price, $product ) {
343 + $product = Subscription::get_subs_product( $product );
224 344 if ( ! $product->is_type( 'simple' ) || '' === $price ) {
225 345 return $price;
226 346 }
227 347
228 - $enabled = $product->get_meta( '_subscrpt_enabled' );
229 - if ( $enabled ) :
230 - $timing_option = $product->get_meta( '_subscrpt_timing_option' );
231 - $type = Helper::get_typos( 1, $timing_option );
232 - $meta_trial_time = $product->get_meta( '_subscrpt_trial_timing_per' );
233 - $has_trial = Helper::check_trial( $product->get_id() );
234 - $trial = null;
235 - if ( ! empty( $meta_trial_time ) && $meta_trial_time > 0 && $has_trial ) {
236 - $trial = '<br/><small> + Get ' . $meta_trial_time . ' ' . Helper::get_typos( $meta_trial_time, $product->get_meta( '_subscrpt_trial_timing_option' ) ) . ' free trial!</small>';
348 + if ( $product->is_enabled() ) :
349 + $timing_option = $product->get_timing_option();
350 + $type = ucfirst( Helper::get_typos( 1, $timing_option, true ) );
351 +
352 + $trial = null;
353 + if ( $product->has_trial() ) {
354 + $meta_trial_time = $product->get_trial_timing_per();
355 + $trial_type = Helper::get_typos( $meta_trial_time, $product->get_trial_timing_option(), true );
356 + $trial = '<br/><small> + Get ' . $meta_trial_time . ' ' . ucfirst( $trial_type ) . ' free trial!</small>';
237 357 }
238 358
239 - return apply_filters( 'subscrpt_simple_price_html', ( $price . ' / ' . $type . $trial ), $product, $price, $timing_option, $trial );
359 + $timing_html = "<span class='wpsubs-subscription-timing'>&nbsp;/&nbsp;{$type}</span>";
360 + $price_html = $price . $timing_html . $trial;
361 +
362 + return apply_filters( 'subscrpt_simple_price_html', $price_html, $product, $price, $trial );
240 363 else :
241 364 return $price;
242 365 endif;
243 366 }