PluginProbe
Tiered Pricing Table for WooCommerce / 6.1.0
Tiered Pricing Table for WooCommerce v6.1.0
8.0.2 7.1.7 7.1.5 7.1.4 7.1.1 6.5.0 6.4.0 6.1.0 trunk 1.0 1.1 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2 2.2.3 2.3.0 2.3.1 2.3.2 2.3.3 All 91 releases
tier-pricing-table / src / Addons / GlobalTieredPricing / GlobalPricingRule.php

GlobalPricingRule.php in Tiered Pricing Table for WooCommerce 6.1.0, at src/Addons/GlobalTieredPricing/GlobalPricingRule.php

602 lines 20.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace TierPricingTable\Addons\GlobalTieredPricing;
4
5 use TierPricingTable\Addons\GlobalTieredPricing\PricingRule\RuleSettings;
6 use TierPricingTable\Forms\Form;
7 use Exception;
8 use WC_Product;
9 use WP_User;
10 class GlobalPricingRule {
11 /**
12 * Rule ID
13 *
14 * @var int
15 */
16 public $id;
17
18 /**
19 * Is suspended
20 *
21 * @var bool
22 */
23 public $isSuspended = false;
24
25 /**
26 * Regular pricing type
27 *
28 * @var string
29 */
30 public $pricingType;
31
32 /**
33 * Regular price
34 *
35 * @var ?float
36 */
37 public $regularPrice;
38
39 /**
40 * Sale price
41 *
42 * @var ?float
43 */
44 public $salePrice;
45
46 /**
47 * Percentage Discount
48 *
49 * @var ?float
50 */
51 public $discount;
52
53 /**
54 * Percentage Discount
55 *
56 * @var string
57 */
58 public $discountType = 'sale_price';
59
60 /**
61 * Applying type
62 *
63 * @var string
64 */
65 public $applyingType;
66
67 /**
68 * Tiered Pricing type
69 *
70 * @var string
71 */
72 public $tieredPricingType;
73
74 /**
75 * Percentage Tiered Pricing Rules
76 *
77 * @var array
78 */
79 public $percentageTieredPricingRules = array();
80
81 /**
82 * Fixed Tiered Pricing Rules
83 *
84 * @var array
85 */
86 public $fixedTieredPricingRules = array();
87
88 /**
89 * Included categories
90 *
91 * @var array
92 */
93 public $includedProductCategories = array();
94
95 /**
96 * Excluded categories
97 *
98 * @var array
99 */
100 public $excludedProductCategories = array();
101
102 /**
103 * Included products
104 *
105 * @var array
106 */
107 public $includedProducts = array();
108
109 /**
110 * Excluded products
111 *
112 * @var array
113 */
114 public $excludedProducts = array();
115
116 /**
117 * Included product roles
118 *
119 * @var array
120 */
121 public $includedUsersRole = array();
122
123 /**
124 * Excluded product roles
125 *
126 * @var array
127 */
128 public $excludedUsersRole = array();
129
130 /**
131 * Included users
132 *
133 * @var array
134 */
135 public $includedUsers = array();
136
137 /**
138 * Excluded users
139 *
140 * @var array
141 */
142 public $excludedUsers = array();
143
144 /**
145 * Product minimum purchase quantity
146 *
147 * @var int
148 */
149 public $minimum;
150
151 public $priorityOptions;
152
153 /**
154 * Array with custom data from 3rd-party addons
155 *
156 * @var array
157 */
158 public $data = array();
159
160 public function getId() : int {
161 return $this->id;
162 }
163
164 public function setId( int $id ) {
165 $this->id = $id;
166 }
167
168 public function getDiscount() : ?float {
169 return $this->discount;
170 }
171
172 public function setDiscount( ?float $discount ) {
173 $this->discount = $discount;
174 }
175
176 public function getDiscountType() : string {
177 return $this->discountType;
178 }
179
180 public function setDiscountType( string $discountType ) {
181 $this->discountType = ( in_array( $discountType, array('sale_price', 'regular_price') ) ? $discountType : 'sale_price' );
182 }
183
184 public function getTieredPricingType() : string {
185 return 'fixed';
186 }
187
188 public function setTieredPricingType( string $tieredPricingType ) {
189 $this->tieredPricingType = ( in_array( $tieredPricingType, array('percentage', 'fixed') ) ? $tieredPricingType : 'fixed' );
190 }
191
192 public function getPercentageTieredPricingRules() : array {
193 return $this->percentageTieredPricingRules;
194 }
195
196 public function setPercentageTieredPricingRules( array $percentageTieredPricingRules ) {
197 $this->percentageTieredPricingRules = $percentageTieredPricingRules;
198 }
199
200 public function getFixedTieredPricingRules() : array {
201 return $this->fixedTieredPricingRules;
202 }
203
204 public function setFixedTieredPricingRules( array $fixedTieredPricingRules ) {
205 $this->fixedTieredPricingRules = $fixedTieredPricingRules;
206 }
207
208 public function getApplyingType() : string {
209 return $this->applyingType;
210 }
211
212 public function setApplyingType( string $applyingType ) {
213 $this->applyingType = ( in_array( $applyingType, array('individual', 'cross') ) ? $applyingType : 'cross' );
214 }
215
216 public function getPricingType() : string {
217 return $this->pricingType;
218 }
219
220 public function setPricingType( string $priceType ) {
221 $this->pricingType = ( in_array( $priceType, array('percentage', 'flat') ) ? $priceType : 'flat' );
222 }
223
224 public function getTieredPricingRules() : array {
225 return $this->getFixedTieredPricingRules();
226 }
227
228 public function getRegularPrice() : ?float {
229 return $this->regularPrice;
230 }
231
232 public function setRegularPrice( ?float $regularPrice ) {
233 $this->regularPrice = ( !Form::isEmpty( $regularPrice ) ? floatval( $regularPrice ) : null );
234 }
235
236 public function getSalePrice() : ?float {
237 return $this->salePrice;
238 }
239
240 public function setSalePrice( ?float $salePrice ) {
241 $this->salePrice = $salePrice;
242 }
243
244 /**
245 * Create instance from array
246 *
247 * @param array $data
248 *
249 * @return self
250 */
251 public static function fromArray( array $data ) : self {
252 $applyingType = $data['applying_type'] ?? 'individual';
253 $applyingType = ( in_array( $applyingType, array('individual', 'cross') ) ? $applyingType : 'cross' );
254 $tieredPricingType = $data['tiered_pricing_type'] ?? 'fixed';
255 $tieredPricingType = ( in_array( $tieredPricingType, array('flat', 'percentage') ) ? $tieredPricingType : 'fixed' );
256 $percentageRules = ( isset( $data['percentage_rules'] ) ? (array) $data['percentage_rules'] : array() );
257 $fixedRules = ( isset( $data['fixed_rules'] ) ? (array) $data['fixed_rules'] : array() );
258 $pricingType = $data['pricing_type'] ?? 'flat';
259 $pricingType = ( in_array( $pricingType, array('flat', 'percentage') ) ? $pricingType : 'flat' );
260 $regularPrice = $data['regular_price'] ?? null;
261 $salePrice = $data['sale_price'] ?? null;
262 $discount = $data['discount'] ?? null;
263 $discountType = $data['discount_type'] ?? 'sale_price';
264 $minimum = $data['minimum'] ?? null;
265 $self = new self();
266 $self->setPricingType( (string) $pricingType );
267 $self->setRegularPrice( ( Form::isEmpty( $regularPrice ) ? null : (float) $regularPrice ) );
268 $self->setSalePrice( ( Form::isEmpty( $salePrice ) ? null : (float) $salePrice ) );
269 $self->setDiscount( ( Form::isEmpty( $discount ) ? null : (float) $discount ) );
270 $self->setDiscountType( (string) $discountType );
271 $self->setApplyingType( $applyingType );
272 $self->setTieredPricingType( $tieredPricingType );
273 $self->setPercentageTieredPricingRules( $percentageRules );
274 $self->setFixedTieredPricingRules( $fixedRules );
275 $self->setMinimum( ( Form::isEmpty( $minimum ) ? null : (int) $minimum ) );
276 return $self;
277 }
278
279 /**
280 * Validate
281 *
282 * @throws Exception
283 */
284 public function validatePricing() {
285 $valid = !Form::isEmpty( $this->getRegularPrice() );
286 $valid = $valid || !Form::isEmpty( $this->getSalePrice() );
287 $valid = $valid || !empty( $this->getTieredPricingRules() );
288 $valid = $valid || !Form::isEmpty( $this->getMinimum() );
289 $valid = $valid || !Form::isEmpty( $this->getDiscount() );
290 $valid = $valid || $this->getSettings()->getPriorityType() === 'flexible';
291 $valid = apply_filters( 'tiered_pricing_table/global_pricing/validation', $valid, $this );
292 if ( !$valid ) {
293 throw new Exception(esc_html__( 'The pricing rule does not affect either prices or product quantity. The rule will be skipped.', 'tier-pricing-table' ));
294 }
295 }
296
297 public function isValidPricing() : bool {
298 try {
299 $this->validatePricing();
300 } catch ( Exception $e ) {
301 return false;
302 }
303 return true;
304 }
305
306 public function getMinimum() : ?int {
307 return $this->minimum;
308 }
309
310 public function setMinimum( ?int $minimum ) {
311 $this->minimum = ( intval( $minimum ) > 1 ? $minimum : null );
312 }
313
314 public function getIncludedProductCategories() : array {
315 return $this->includedProductCategories;
316 }
317
318 public function getExcludedProductCategories() : array {
319 return $this->excludedProductCategories;
320 }
321
322 public function setIncludedProductCategories( array $includedProductCategories ) {
323 $this->includedProductCategories = $includedProductCategories;
324 }
325
326 public function setExcludedProductCategories( array $excludedProductCategories ) {
327 $this->excludedProductCategories = $excludedProductCategories;
328 }
329
330 public function getIncludedProducts() : array {
331 return $this->includedProducts;
332 }
333
334 public function getExcludedProducts() : array {
335 return $this->excludedProducts;
336 }
337
338 public function setIncludedProducts( array $includedProducts ) {
339 $this->includedProducts = $includedProducts;
340 }
341
342 public function setExcludedProducts( array $excludedProducts ) {
343 $this->excludedProducts = $excludedProducts;
344 }
345
346 public function getIncludedUserRoles() : array {
347 return $this->includedUsersRole;
348 }
349
350 public function getExcludedUserRoles() : array {
351 return $this->excludedUsersRole;
352 }
353
354 public function setIncludedUsersRole( array $includedUsersRole ) {
355 $this->includedUsersRole = $includedUsersRole;
356 }
357
358 public function setExcludedUsersRole( array $excludedUsersRole ) {
359 $this->excludedUsersRole = $excludedUsersRole;
360 }
361
362 public function getIncludedUsers() : array {
363 return $this->includedUsers;
364 }
365
366 public function getExcludedUsers() : array {
367 return $this->excludedUsers;
368 }
369
370 public function setIncludedUsers( array $includedUsers ) {
371 $this->includedUsers = $includedUsers;
372 }
373
374 public function setExcludedUsers( array $excludedUsers ) {
375 $this->excludedUsers = $excludedUsers;
376 }
377
378 public function getSettings() : RuleSettings {
379 if ( !$this->priorityOptions ) {
380 $this->priorityOptions = new RuleSettings($this);
381 }
382 return $this->priorityOptions;
383 }
384
385 public function asArray() : array {
386 return array(
387 'pricing_type' => $this->getPricingType(),
388 'regular_price' => $this->getRegularPrice(),
389 'sale_price' => $this->getSalePrice(),
390 'discount' => $this->getDiscount(),
391 'discount_type' => $this->getDiscountType(),
392 'applying_type' => $this->getApplyingType(),
393 'tiered_pricing_type' => $this->getTieredPricingType(),
394 'percentage_rules' => $this->getPercentageTieredPricingRules(),
395 'fixed_rules' => $this->getFixedTieredPricingRules(),
396 'minimum' => $this->getMinimum(),
397 'included_categories' => $this->getIncludedProductCategories(),
398 'included_products' => $this->getIncludedProducts(),
399 'included_users' => $this->getIncludedUsers(),
400 'included_users_role' => $this->getIncludedUserRoles(),
401 'excluded_categories' => $this->getExcludedProductCategories(),
402 'excluded_products' => $this->getExcludedProducts(),
403 'excluded_users' => $this->getExcludedUsers(),
404 'excluded_users_role' => $this->getExcludedUserRoles(),
405 'rule_id' => $this->getId(),
406 'is_suspended' => $this->isSuspended(),
407 );
408 }
409
410 public function save() {
411 $dataToUpdate = array(
412 '_tpt_pricing_type' => $this->getPricingType(),
413 '_tpt_regular_price' => $this->getRegularPrice(),
414 '_tpt_sale_price' => $this->getSalePrice(),
415 '_tpt_discount' => $this->getDiscount(),
416 '_tpt_discount_type' => $this->getDiscountType(),
417 '_tpt_applying_type' => $this->getApplyingType(),
418 '_tpt_tiered_pricing_type' => $this->getTieredPricingType(),
419 '_tpt_percentage_rules' => $this->getPercentageTieredPricingRules(),
420 '_tpt_fixed_rules' => $this->getFixedTieredPricingRules(),
421 '_tpt_minimum' => $this->getMinimum(),
422 '_tpt_included_categories' => $this->getIncludedProductCategories(),
423 '_tpt_included_products' => $this->getIncludedProducts(),
424 '_tpt_included_users' => $this->getIncludedUsers(),
425 '_tpt_included_user_roles' => $this->getIncludedUserRoles(),
426 '_tpt_excluded_categories' => $this->getExcludedProductCategories(),
427 '_tpt_excluded_products' => $this->getExcludedProducts(),
428 '_tpt_excluded_users' => $this->getExcludedUsers(),
429 '_tpt_excluded_user_roles' => $this->getExcludedUserRoles(),
430 '_tpt_is_suspended' => wc_bool_to_string( $this->isSuspended() ),
431 );
432 foreach ( $dataToUpdate as $key => $value ) {
433 update_post_meta( $this->getId(), $key, $value );
434 }
435 }
436
437 public static function build( $ruleId ) : self {
438 // Simple data to read
439 $dataToRead = array(
440 '_tpt_pricing_type' => 'pricing_type',
441 '_tpt_sale_price' => 'sale_price',
442 '_tpt_regular_price' => 'regular_price',
443 '_tpt_discount' => 'discount',
444 '_tpt_discount_type' => 'discount_type',
445 '_tpt_applying_type' => 'applying_type',
446 '_tpt_tiered_pricing_type' => 'tiered_pricing_type',
447 '_tpt_minimum' => 'minimum',
448 '_tpt_is_suspended' => 'is_suspended',
449 );
450 $data = array();
451 foreach ( $dataToRead as $key => $name ) {
452 $data[$name] = get_post_meta( $ruleId, $key, true );
453 }
454 $priceRule = self::fromArray( $data );
455 $existingRoles = wp_roles()->roles;
456 $includedCategoriesIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_included_categories', true ) ) );
457 $includedProductsIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_included_products', true ) ) );
458 $includedUsersRole = array_filter( (array) get_post_meta( $ruleId, '_tpt_included_user_roles', true ), function ( $role ) use($existingRoles) {
459 return array_key_exists( $role, $existingRoles );
460 } );
461 $includedUsers = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_included_users', true ) ) );
462 $excludedCategoriesIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_excluded_categories', true ) ) );
463 $excludedProductsIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_excluded_products', true ) ) );
464 $excludedUsersRole = array_filter( (array) get_post_meta( $ruleId, '_tpt_excluded_user_roles', true ), function ( $role ) use($existingRoles) {
465 return array_key_exists( $role, $existingRoles );
466 } );
467 $excludedUsers = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_excluded_users', true ) ) );
468 $isSuspended = get_post_meta( $ruleId, '_tpt_is_suspended', true ) === 'yes';
469 $priceRule->setPercentageTieredPricingRules( self::readPricingRules( 'percentage', $ruleId ) );
470 $priceRule->setFixedTieredPricingRules( self::readPricingRules( 'fixed', $ruleId ) );
471 $priceRule->setIncludedProductCategories( $includedCategoriesIds );
472 $priceRule->setIncludedUsers( $includedUsers );
473 $priceRule->setIncludedUsersRole( $includedUsersRole );
474 $priceRule->setIncludedProducts( $includedProductsIds );
475 $priceRule->setExcludedProductCategories( $excludedCategoriesIds );
476 $priceRule->setExcludedUsers( $excludedUsers );
477 $priceRule->setExcludedUsersRole( $excludedUsersRole );
478 $priceRule->setExcludedProducts( $excludedProductsIds );
479 $priceRule->setIsSuspended( $isSuspended );
480 $priceRule->setId( $ruleId );
481 return apply_filters( 'tiered_pricing_table/global_pricing/after_built_rule', $priceRule );
482 }
483
484 protected static function readPricingRules( $type, $id ) : array {
485 $type = ( in_array( $type, array('percentage', 'fixed') ) ? $type : 'fixed' );
486 $rules = get_post_meta( $id, "_tpt_{$type}_rules", true );
487 $rules = ( !empty( $rules ) ? $rules : array() );
488 $rules = ( is_array( $rules ) ? array_filter( $rules ) : array() );
489 ksort( $rules );
490 return $rules;
491 }
492
493 public function setIsSuspended( bool $isSuspended ) {
494 $this->isSuspended = $isSuspended;
495 }
496
497 public function suspend() {
498 $this->setIsSuspended( true );
499 }
500
501 public function reactivate() {
502 $this->setIsSuspended( false );
503 }
504
505 public function isSuspended() : bool {
506 return $this->isSuspended;
507 }
508
509 /**
510 * Wrapper for the main "match" function to provide the hook for 3rd party devs
511 *
512 * @param WP_User $user
513 * @param WC_Product $product
514 *
515 * @return bool
516 */
517 public function matchRequirements( WP_User $user, WC_Product $product ) : bool {
518 $matched = $this->_matchRequirements( $user, $product );
519 return apply_filters(
520 'tiered_pricing_table/global_pricing/match_requirements',
521 $matched,
522 $this,
523 $user,
524 $product
525 );
526 }
527
528 protected function _matchRequirements( WP_User $user, WC_Product $product ) : bool {
529 $parentProduct = ( $product->is_type( array('variation', 'subscription-variation') ) ? wc_get_product( $product->get_parent_id() ) : $product );
530 /**
531 * 1. Check for product exclusion
532 *
533 * If product in exclusion - pricing rule does not match immediately
534 */
535 if ( !empty( $this->getExcludedProducts() ) ) {
536 if ( in_array( $product->get_id(), $this->getExcludedProducts() ) || in_array( $parentProduct->get_id(), $this->getExcludedProducts() ) ) {
537 return false;
538 }
539 }
540 if ( !empty( $this->getExcludedProductCategories() ) ) {
541 if ( !empty( array_intersect( $parentProduct->get_category_ids(), $this->getExcludedProductCategories() ) ) ) {
542 return false;
543 }
544 }
545 /**
546 * 2. Check for user exclusion
547 *
548 * If users in exclusion - pricing rule does not match immediately
549 */
550 if ( in_array( $user->ID, $this->getExcludedUsers() ) ) {
551 return false;
552 }
553 foreach ( $this->getExcludedUserRoles() as $role ) {
554 if ( in_array( $role, $user->roles ) ) {
555 return false;
556 }
557 }
558 /**
559 * 3. Check for rule limitation for specific products
560 *
561 * If yes - match rule only for selected product/product categories
562 */
563 $productMatched = false;
564 $productLimitations = false;
565 if ( !empty( $this->getIncludedProducts() ) ) {
566 $productLimitations = true;
567 if ( in_array( $product->get_id(), $this->getIncludedProducts() ) || in_array( $parentProduct->get_id(), $this->getIncludedProducts() ) ) {
568 $productMatched = true;
569 }
570 }
571 if ( !empty( $this->getIncludedProductCategories() ) ) {
572 $productLimitations = true;
573 if ( !empty( array_intersect( $parentProduct->get_category_ids(), $this->getIncludedProductCategories() ) ) ) {
574 $productMatched = true;
575 }
576 }
577 // There is product limitation and the product/category does not match the rule
578 if ( $productLimitations && !$productMatched ) {
579 return false;
580 }
581 /**
582 * 4. If there are no user limits - match the rule immediately
583 */
584 if ( empty( $this->getIncludedUserRoles() ) && empty( $this->getIncludedUsers() ) ) {
585 return true;
586 }
587 /**
588 * 5. If there are user limits - check for user ID and user role.
589 */
590 if ( in_array( $user->ID, $this->getIncludedUsers() ) ) {
591 return true;
592 }
593 foreach ( $this->getIncludedUserRoles() as $role ) {
594 if ( in_array( $role, $user->roles ) ) {
595 return true;
596 }
597 }
598 return false;
599 }
600
601 }
602