PluginProbe
Tiered Pricing Table for WooCommerce / 8.0.2
Tiered Pricing Table for WooCommerce v8.0.2
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 8.0.2, at src/Addons/GlobalTieredPricing/GlobalPricingRule.php

817 lines 28.7 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 * Tax status
69 *
70 * @var string
71 */
72 public $taxStatus = '';
73
74 /**
75 * Tax class
76 *
77 * @var string
78 */
79 public $taxClass = '';
80
81 /**
82 * Tiered Pricing type
83 *
84 * @var string
85 */
86 public $tieredPricingType;
87
88 /**
89 * Percentage Tiered Pricing Rules
90 *
91 * @var array
92 */
93 public $percentageTieredPricingRules = array();
94
95 /**
96 * Fixed Tiered Pricing Rules
97 *
98 * @var array
99 */
100 public $fixedTieredPricingRules = array();
101
102 /**
103 * Included categories
104 *
105 * @var array
106 */
107 public $includedProductCategories = array();
108
109 /**
110 * Excluded categories
111 *
112 * @var array
113 */
114 public $excludedProductCategories = array();
115
116 /**
117 * Included tags
118 *
119 * @var array
120 */
121 public $includedProductTags = array();
122
123 /**
124 * Excluded tags
125 *
126 * @var array
127 */
128 public $excludedProductTags = array();
129
130 /**
131 * Included brands
132 *
133 * @var array
134 */
135 public $includedProductBrands = array();
136
137 /**
138 * Excluded brands
139 *
140 * @var array
141 */
142 public $excludedProductBrands = array();
143
144 /**
145 * Included products
146 *
147 * @var array
148 */
149 public $includedProducts = array();
150
151 /**
152 * Excluded products
153 *
154 * @var array
155 */
156 public $excludedProducts = array();
157
158 /**
159 * Included product roles
160 *
161 * @var array
162 */
163 public $includedUsersRole = array();
164
165 /**
166 * Excluded product roles
167 *
168 * @var array
169 */
170 public $excludedUsersRole = array();
171
172 /**
173 * Included users
174 *
175 * @var array
176 */
177 public $includedUsers = array();
178
179 /**
180 * Excluded users
181 *
182 * @var array
183 */
184 public $excludedUsers = array();
185
186 /**
187 * Product minimum purchase quantity
188 *
189 * @var int|null
190 */
191 public $minimum;
192
193 /**
194 * Mix and match minimum quantity
195 *
196 * @var bool|null
197 */
198 public $mixAndMatchMinQuantity = null;
199
200 public $priorityOptions;
201
202 /**
203 * Array with custom data from 3rd-party addons
204 *
205 * @var array
206 */
207 public $data = array();
208
209 public function getId() : int {
210 return $this->id;
211 }
212
213 public function setId( int $id ) {
214 $this->id = $id;
215 }
216
217 public function getDiscount() : ?float {
218 return $this->discount;
219 }
220
221 public function setDiscount( ?float $discount ) {
222 $this->discount = $discount;
223 }
224
225 public function getDiscountType() : string {
226 return $this->discountType;
227 }
228
229 public function setDiscountType( string $discountType ) {
230 $this->discountType = ( in_array( $discountType, array('sale_price', 'regular_price') ) ? $discountType : 'sale_price' );
231 }
232
233 public function getTieredPricingType() : string {
234 return 'fixed';
235 }
236
237 public function setTieredPricingType( string $tieredPricingType ) {
238 $this->tieredPricingType = ( in_array( $tieredPricingType, array('percentage', 'fixed') ) ? $tieredPricingType : 'fixed' );
239 }
240
241 public function getPercentageTieredPricingRules() : array {
242 return $this->percentageTieredPricingRules;
243 }
244
245 public function setPercentageTieredPricingRules( array $percentageTieredPricingRules ) {
246 $this->percentageTieredPricingRules = $percentageTieredPricingRules;
247 }
248
249 public function getFixedTieredPricingRules() : array {
250 return $this->fixedTieredPricingRules;
251 }
252
253 public function setFixedTieredPricingRules( array $fixedTieredPricingRules ) {
254 $this->fixedTieredPricingRules = $fixedTieredPricingRules;
255 }
256
257 public function getApplyingType() : string {
258 return $this->applyingType;
259 }
260
261 public function setApplyingType( string $applyingType ) {
262 $this->applyingType = ( in_array( $applyingType, array('individual', 'cross') ) ? $applyingType : 'cross' );
263 }
264
265 public function getPricingType() : string {
266 return $this->pricingType;
267 }
268
269 public function setPricingType( string $priceType ) {
270 $this->pricingType = ( in_array( $priceType, array('percentage', 'flat') ) ? $priceType : 'flat' );
271 }
272
273 public function getTieredPricingRules() : array {
274 return $this->getFixedTieredPricingRules();
275 }
276
277 public function getTaxStatus() : string {
278 return $this->taxStatus;
279 }
280
281 public function setTaxStatus( string $taxStatus ) {
282 $this->taxStatus = $taxStatus;
283 }
284
285 public function getTaxClass() : string {
286 return $this->taxClass;
287 }
288
289 public function setTaxClass( string $taxClass ) {
290 $this->taxClass = $taxClass;
291 }
292
293 public function getRegularPrice() : ?float {
294 return $this->regularPrice;
295 }
296
297 public function setRegularPrice( ?float $regularPrice ) {
298 $this->regularPrice = ( !Form::isEmpty( $regularPrice ) ? floatval( $regularPrice ) : null );
299 }
300
301 public function getSalePrice() : ?float {
302 return $this->salePrice;
303 }
304
305 public function setSalePrice( ?float $salePrice ) {
306 $this->salePrice = $salePrice;
307 }
308
309 /**
310 * Create instance from array
311 *
312 * @param array $data
313 *
314 * @return self
315 */
316 public static function fromArray( array $data ) : self {
317 $applyingType = $data['applying_type'] ?? 'individual';
318 $applyingType = ( in_array( $applyingType, array('individual', 'cross') ) ? $applyingType : 'cross' );
319 $tieredPricingType = $data['tiered_pricing_type'] ?? 'fixed';
320 $tieredPricingType = ( in_array( $tieredPricingType, array('flat', 'percentage') ) ? $tieredPricingType : 'fixed' );
321 $percentageRules = ( isset( $data['percentage_rules'] ) ? (array) $data['percentage_rules'] : array() );
322 $fixedRules = ( isset( $data['fixed_rules'] ) ? (array) $data['fixed_rules'] : array() );
323 $pricingType = $data['pricing_type'] ?? 'flat';
324 $pricingType = ( in_array( $pricingType, array('flat', 'percentage') ) ? $pricingType : 'flat' );
325 $regularPrice = $data['regular_price'] ?? null;
326 $salePrice = $data['sale_price'] ?? null;
327 $discount = $data['discount'] ?? null;
328 $discountType = $data['discount_type'] ?? 'sale_price';
329 $minimum = $data['minimum'] ?? null;
330 $mixAndMatchValue = $data['mix_and_match_minimum'] ?? '';
331 $mixAndMatch = ( $mixAndMatchValue === '' ? null : $mixAndMatchValue === 'yes' );
332 $taxStatus = $data['tax_status'] ?? '';
333 $taxClass = $data['tax_class'] ?? '';
334 $self = new self();
335 $self->setTaxStatus( $taxStatus );
336 $self->setTaxClass( $taxClass );
337 $self->setPricingType( (string) $pricingType );
338 $self->setRegularPrice( ( Form::isEmpty( $regularPrice ) ? null : (float) $regularPrice ) );
339 $self->setSalePrice( ( Form::isEmpty( $salePrice ) ? null : (float) $salePrice ) );
340 $self->setDiscount( ( Form::isEmpty( $discount ) ? null : (float) $discount ) );
341 $self->setDiscountType( (string) $discountType );
342 $self->setApplyingType( $applyingType );
343 $self->setTieredPricingType( $tieredPricingType );
344 $self->setPercentageTieredPricingRules( $percentageRules );
345 $self->setFixedTieredPricingRules( $fixedRules );
346 $self->setMinimum( ( Form::isEmpty( $minimum ) ? null : (int) $minimum ) );
347 $self->setMixAndMatchMinQuantity( $mixAndMatch );
348 return $self;
349 }
350
351 /**
352 * Validate
353 *
354 * @throws Exception
355 */
356 public function validatePricing() {
357 $valid = !Form::isEmpty( $this->getRegularPrice() );
358 $valid = $valid || !Form::isEmpty( $this->getSalePrice() );
359 $valid = $valid || !empty( $this->getTieredPricingRules() );
360 $valid = $valid || !Form::isEmpty( $this->getMinimum() );
361 $valid = $valid || !Form::isEmpty( $this->getDiscount() );
362 $valid = $valid || $this->getSettings()->getPriorityType() === 'flexible';
363 $valid = apply_filters( 'tiered_pricing_table/global_pricing/validation', $valid, $this );
364 if ( !$valid ) {
365 throw new Exception(esc_html__( 'The pricing rule does not affect either prices or product quantity. The rule will be skipped.', 'tier-pricing-table' ));
366 }
367 }
368
369 public function isValidPricing() : bool {
370 try {
371 $this->validatePricing();
372 } catch ( Exception $e ) {
373 return false;
374 }
375 return true;
376 }
377
378 public function getMinimum() : ?int {
379 return $this->minimum;
380 }
381
382 public function setMinimum( ?int $minimum ) {
383 $this->minimum = ( intval( $minimum ) > 1 ? $minimum : null );
384 }
385
386 public function getMixAndMatchMinQuantity() : ?bool {
387 return $this->mixAndMatchMinQuantity;
388 }
389
390 public function setMixAndMatchMinQuantity( ?bool $mixAndMatch ) {
391 $this->mixAndMatchMinQuantity = $mixAndMatch;
392 }
393
394 public function getIncludedProductCategories() : array {
395 return $this->includedProductCategories;
396 }
397
398 public function getExcludedProductCategories() : array {
399 return $this->excludedProductCategories;
400 }
401
402 public function setIncludedProductCategories( array $includedProductCategories ) {
403 $this->includedProductCategories = $includedProductCategories;
404 }
405
406 public function setExcludedProductCategories( array $excludedProductCategories ) {
407 $this->excludedProductCategories = $excludedProductCategories;
408 }
409
410 public function getIncludedProductTags() : array {
411 return $this->includedProductTags;
412 }
413
414 public function getExcludedProductTags() : array {
415 return $this->excludedProductTags;
416 }
417
418 public function setIncludedProductTags( array $includedProductTags ) {
419 $this->includedProductTags = $includedProductTags;
420 }
421
422 public function setExcludedProductTags( array $excludedProductTags ) {
423 $this->excludedProductTags = $excludedProductTags;
424 }
425
426 public function getIncludedProductBrands() : array {
427 return $this->includedProductBrands;
428 }
429
430 public function getExcludedProductBrands() : array {
431 return $this->excludedProductBrands;
432 }
433
434 public function setIncludedProductBrands( array $includedProductBrands ) {
435 $this->includedProductBrands = $includedProductBrands;
436 }
437
438 public function setExcludedProductBrands( array $excludedProductBrands ) {
439 $this->excludedProductBrands = $excludedProductBrands;
440 }
441
442 public function getIncludedProducts() : array {
443 return $this->includedProducts;
444 }
445
446 public function getExcludedProducts() : array {
447 return $this->excludedProducts;
448 }
449
450 public function setIncludedProducts( array $includedProducts ) {
451 $this->includedProducts = $includedProducts;
452 }
453
454 public function setExcludedProducts( array $excludedProducts ) {
455 $this->excludedProducts = $excludedProducts;
456 }
457
458 public function getIncludedUserRoles() : array {
459 return $this->includedUsersRole;
460 }
461
462 public function getExcludedUserRoles() : array {
463 return $this->excludedUsersRole;
464 }
465
466 public function setIncludedUsersRole( array $includedUsersRole ) {
467 $this->includedUsersRole = $includedUsersRole;
468 }
469
470 public function setExcludedUsersRole( array $excludedUsersRole ) {
471 $this->excludedUsersRole = $excludedUsersRole;
472 }
473
474 public function getIncludedUsers() : array {
475 return $this->includedUsers;
476 }
477
478 public function getExcludedUsers() : array {
479 return $this->excludedUsers;
480 }
481
482 public function setIncludedUsers( array $includedUsers ) {
483 $this->includedUsers = $includedUsers;
484 }
485
486 public function setExcludedUsers( array $excludedUsers ) {
487 $this->excludedUsers = $excludedUsers;
488 }
489
490 public function getSettings() : RuleSettings {
491 if ( !$this->priorityOptions ) {
492 $this->priorityOptions = new RuleSettings($this);
493 }
494 return $this->priorityOptions;
495 }
496
497 public function asArray() : array {
498 return array(
499 'pricing_type' => $this->getPricingType(),
500 'regular_price' => $this->getRegularPrice(),
501 'sale_price' => $this->getSalePrice(),
502 'discount' => $this->getDiscount(),
503 'discount_type' => $this->getDiscountType(),
504 'applying_type' => $this->getApplyingType(),
505 'tiered_pricing_type' => $this->getTieredPricingType(),
506 'percentage_rules' => $this->getPercentageTieredPricingRules(),
507 'fixed_rules' => $this->getFixedTieredPricingRules(),
508 'minimum' => $this->getMinimum(),
509 'mix_and_match_minimum' => $this->getMixAndMatchMinQuantity(),
510 'tax_status' => $this->getTaxStatus(),
511 'tax_class' => $this->getTaxClass(),
512 'included_categories' => $this->getIncludedProductCategories(),
513 'included_tags' => $this->getIncludedProductTags(),
514 'included_brands' => $this->getIncludedProductBrands(),
515 'included_products' => $this->getIncludedProducts(),
516 'included_users' => $this->getIncludedUsers(),
517 'included_users_role' => $this->getIncludedUserRoles(),
518 'excluded_categories' => $this->getExcludedProductCategories(),
519 'excluded_tags' => $this->getExcludedProductTags(),
520 'excluded_brands' => $this->getExcludedProductBrands(),
521 'excluded_products' => $this->getExcludedProducts(),
522 'excluded_users' => $this->getExcludedUsers(),
523 'excluded_users_role' => $this->getExcludedUserRoles(),
524 'rule_id' => $this->getId(),
525 'is_suspended' => $this->isSuspended(),
526 );
527 }
528
529 public function save() {
530 $dataToUpdate = array(
531 '_tpt_pricing_type' => $this->getPricingType(),
532 '_tpt_regular_price' => $this->getRegularPrice(),
533 '_tpt_sale_price' => $this->getSalePrice(),
534 '_tpt_discount' => $this->getDiscount(),
535 '_tpt_discount_type' => $this->getDiscountType(),
536 '_tpt_applying_type' => $this->getApplyingType(),
537 '_tpt_tiered_pricing_type' => $this->getTieredPricingType(),
538 '_tpt_percentage_rules' => $this->getPercentageTieredPricingRules(),
539 '_tpt_fixed_rules' => $this->getFixedTieredPricingRules(),
540 '_tpt_minimum' => $this->getMinimum(),
541 '_tpt_mix_and_match_minimum' => ( is_null( $this->getMixAndMatchMinQuantity() ) ? '' : wc_bool_to_string( $this->getMixAndMatchMinQuantity() ) ),
542 '_tpt_tax_status' => $this->getTaxStatus(),
543 '_tpt_tax_class' => $this->getTaxClass(),
544 '_tpt_included_categories' => $this->getIncludedProductCategories(),
545 '_tpt_included_tags' => $this->getIncludedProductTags(),
546 '_tpt_included_brands' => $this->getIncludedProductBrands(),
547 '_tpt_included_products' => $this->getIncludedProducts(),
548 '_tpt_included_users' => $this->getIncludedUsers(),
549 '_tpt_included_user_roles' => $this->getIncludedUserRoles(),
550 '_tpt_excluded_categories' => $this->getExcludedProductCategories(),
551 '_tpt_excluded_tags' => $this->getExcludedProductTags(),
552 '_tpt_excluded_brands' => $this->getExcludedProductBrands(),
553 '_tpt_excluded_products' => $this->getExcludedProducts(),
554 '_tpt_excluded_users' => $this->getExcludedUsers(),
555 '_tpt_excluded_user_roles' => $this->getExcludedUserRoles(),
556 '_tpt_is_suspended' => wc_bool_to_string( $this->isSuspended() ),
557 );
558 foreach ( $dataToUpdate as $key => $value ) {
559 update_post_meta( $this->getId(), $key, $value );
560 }
561 }
562
563 public static function build( $ruleId ) : self {
564 // Simple data to read
565 $dataToRead = array(
566 '_tpt_pricing_type' => 'pricing_type',
567 '_tpt_sale_price' => 'sale_price',
568 '_tpt_regular_price' => 'regular_price',
569 '_tpt_discount' => 'discount',
570 '_tpt_discount_type' => 'discount_type',
571 '_tpt_applying_type' => 'applying_type',
572 '_tpt_tiered_pricing_type' => 'tiered_pricing_type',
573 '_tpt_minimum' => 'minimum',
574 '_tpt_mix_and_match_minimum' => 'mix_and_match_minimum',
575 '_tpt_tax_status' => 'tax_status',
576 '_tpt_tax_class' => 'tax_class',
577 '_tpt_is_suspended' => 'is_suspended',
578 );
579 $data = array();
580 foreach ( $dataToRead as $key => $name ) {
581 $data[$name] = get_post_meta( $ruleId, $key, true );
582 }
583 $priceRule = self::fromArray( $data );
584 $existingRoles = wp_roles()->roles;
585 $includedCategoriesIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_included_categories', true ) ) );
586 $includedTagsIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_included_tags', true ) ) );
587 $includedBrandsIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_included_brands', true ) ) );
588 $includedProductsIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_included_products', true ) ) );
589 $includedUsersRole = array_filter( (array) get_post_meta( $ruleId, '_tpt_included_user_roles', true ), function ( $role ) use($existingRoles) {
590 return array_key_exists( $role, $existingRoles );
591 } );
592 $includedUsers = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_included_users', true ) ) );
593 $excludedCategoriesIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_excluded_categories', true ) ) );
594 $excludedTagsIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_excluded_tags', true ) ) );
595 $excludedBrandsIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_excluded_brands', true ) ) );
596 $excludedProductsIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_excluded_products', true ) ) );
597 $excludedUsersRole = array_filter( (array) get_post_meta( $ruleId, '_tpt_excluded_user_roles', true ), function ( $role ) use($existingRoles) {
598 return array_key_exists( $role, $existingRoles );
599 } );
600 $excludedUsers = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_excluded_users', true ) ) );
601 $isSuspended = get_post_meta( $ruleId, '_tpt_is_suspended', true ) === 'yes';
602 $priceRule->setPercentageTieredPricingRules( self::readPricingRules( 'percentage', $ruleId ) );
603 $priceRule->setFixedTieredPricingRules( self::readPricingRules( 'fixed', $ruleId ) );
604 $priceRule->setIncludedProductCategories( $includedCategoriesIds );
605 $priceRule->setIncludedProductTags( $includedTagsIds );
606 $priceRule->setIncludedProductBrands( $includedBrandsIds );
607 $priceRule->setIncludedUsers( $includedUsers );
608 $priceRule->setIncludedUsersRole( $includedUsersRole );
609 $priceRule->setIncludedProducts( $includedProductsIds );
610 $priceRule->setExcludedProductCategories( $excludedCategoriesIds );
611 $priceRule->setExcludedProductTags( $excludedTagsIds );
612 $priceRule->setExcludedProductBrands( $excludedBrandsIds );
613 $priceRule->setExcludedUsers( $excludedUsers );
614 $priceRule->setExcludedUsersRole( $excludedUsersRole );
615 $priceRule->setExcludedProducts( $excludedProductsIds );
616 $priceRule->setIsSuspended( $isSuspended );
617 $priceRule->setId( $ruleId );
618 return apply_filters( 'tiered_pricing_table/global_pricing/after_built_rule', $priceRule );
619 }
620
621 protected static function readPricingRules( $type, $id ) : array {
622 $type = ( in_array( $type, array('percentage', 'fixed') ) ? $type : 'fixed' );
623 $rules = get_post_meta( $id, "_tpt_{$type}_rules", true );
624 $rules = ( !empty( $rules ) ? $rules : array() );
625 $rules = ( is_array( $rules ) ? array_filter( $rules ) : array() );
626 ksort( $rules );
627 return $rules;
628 }
629
630 public function setIsSuspended( bool $isSuspended ) {
631 $this->isSuspended = $isSuspended;
632 }
633
634 public function suspend() {
635 $this->setIsSuspended( true );
636 }
637
638 public function reactivate() {
639 $this->setIsSuspended( false );
640 }
641
642 public function isSuspended() : bool {
643 return $this->isSuspended;
644 }
645
646 /**
647 * Wrapper for the main "match" function to provide the hook for 3rd party devs
648 *
649 * @param WP_User $user
650 * @param WC_Product $product
651 *
652 * @return bool
653 */
654 public function matchRequirements( WP_User $user, WC_Product $product ) : bool {
655 $matched = $this->_matchRequirements( $user, $product );
656 return apply_filters(
657 'tiered_pricing_table/global_pricing/match_requirements',
658 $matched,
659 $this,
660 $user,
661 $product
662 );
663 }
664
665 protected function _matchRequirements( WP_User $user, WC_Product $product ) : bool {
666 $parentProduct = ( $product->is_type( array('variation', 'subscription-variation') ) ? wc_get_product( $product->get_parent_id() ) : $product );
667 /**
668 * 1. Check for product exclusion
669 *
670 * If the product in exclusion - pricing rule does not match immediately
671 */
672 $excludedProducts = $this->translateIds( $this->getExcludedProducts(), 'product' );
673 if ( !empty( $excludedProducts ) ) {
674 if ( in_array( $product->get_id(), $excludedProducts ) || in_array( $parentProduct->get_id(), $excludedProducts ) ) {
675 return false;
676 }
677 }
678 $excludedProductCategories = $this->translateIds( $this->getExcludedProductCategories(), 'product_cat' );
679 if ( !empty( $excludedProductCategories ) ) {
680 if ( !empty( array_intersect( $parentProduct->get_category_ids(), $excludedProductCategories ) ) ) {
681 return false;
682 }
683 }
684 $excludedProductTags = $this->translateIds( $this->getExcludedProductTags(), 'product_tag' );
685 if ( !empty( $excludedProductTags ) ) {
686 if ( !empty( array_intersect( $parentProduct->get_tag_ids(), $excludedProductTags ) ) ) {
687 return false;
688 }
689 }
690 $excludedProductBrands = $this->translateIds( $this->getExcludedProductBrands(), 'product_brand' );
691 if ( !empty( $excludedProductBrands ) ) {
692 $productBrands = wp_get_post_terms( $parentProduct->get_id(), 'product_brand', array(
693 'fields' => 'ids',
694 ) );
695 if ( is_wp_error( $productBrands ) ) {
696 $productBrands = [];
697 }
698 if ( !empty( array_intersect( $productBrands, $excludedProductBrands ) ) ) {
699 return false;
700 }
701 }
702 /**
703 * 2. Check for user exclusion
704 *
705 * If users in exclusion - pricing rule does not match immediately
706 */
707 if ( in_array( $user->ID, $this->getExcludedUsers() ) ) {
708 return false;
709 }
710 foreach ( $this->getExcludedUserRoles() as $role ) {
711 if ( in_array( $role, $user->roles ) ) {
712 return false;
713 }
714 }
715 /**
716 * 3. Check for rule limitation for specific products
717 *
718 * If yes - match rule only for selected product/product categories
719 */
720 $productMatched = false;
721 $productLimitations = false;
722 $includedProducts = $this->translateIds( $this->getIncludedProducts(), 'product' );
723 if ( !empty( $includedProducts ) ) {
724 $productLimitations = true;
725 if ( in_array( $product->get_id(), $includedProducts ) || in_array( $parentProduct->get_id(), $includedProducts ) ) {
726 $productMatched = true;
727 }
728 }
729 $includedProductCategories = $this->translateIds( $this->getIncludedProductCategories(), 'product_cat' );
730 if ( !empty( $includedProductCategories ) ) {
731 $productLimitations = true;
732 if ( !empty( array_intersect( $parentProduct->get_category_ids(), $includedProductCategories ) ) ) {
733 $productMatched = true;
734 }
735 }
736 $includedProductTags = $this->translateIds( $this->getIncludedProductTags(), 'product_tag' );
737 if ( !empty( $includedProductTags ) ) {
738 $productLimitations = true;
739 if ( !empty( array_intersect( $parentProduct->get_tag_ids(), $includedProductTags ) ) ) {
740 $productMatched = true;
741 }
742 }
743 $includedProductBrands = $this->translateIds( $this->getIncludedProductBrands(), 'product_brand' );
744 if ( !empty( $includedProductBrands ) ) {
745 $productLimitations = true;
746 $productBrands = wp_get_post_terms( $parentProduct->get_id(), 'product_brand', array(
747 'fields' => 'ids',
748 ) );
749 if ( is_wp_error( $productBrands ) ) {
750 $productBrands = [];
751 }
752 if ( !empty( array_intersect( $productBrands, $includedProductBrands ) ) ) {
753 $productMatched = true;
754 }
755 }
756 // There is product limitation and the product/category does not match the rule
757 if ( $productLimitations && !$productMatched ) {
758 return false;
759 }
760 /**
761 * 4. If there are no user limits - match the rule immediately
762 */
763 if ( empty( $this->getIncludedUserRoles() ) && empty( $this->getIncludedUsers() ) ) {
764 return true;
765 }
766 /**
767 * 5. If there are user limits - check for user ID and user role.
768 */
769 if ( in_array( $user->ID, $this->getIncludedUsers() ) ) {
770 return true;
771 }
772 foreach ( $this->getIncludedUserRoles() as $role ) {
773 if ( in_array( $role, $user->roles ) ) {
774 return true;
775 }
776 }
777 return false;
778 }
779
780 /**
781 * Map a rule's stored object IDs to the language currently being viewed.
782 *
783 * A rule is created in a single language and stores the product/term IDs of that language. The
784 * product being priced (and the term IDs returned by WooCommerce for it) are in the current
785 * language, so under WPML/Polylang a raw ID comparison would never match on a translation. This
786 * translates the stored IDs into the current language via the shared `wpml_object_id` filter
787 * (implemented by both WPML and Polylang) so the comparison works across every translation.
788 *
789 * When no multilingual plugin is active the filter has no listeners, so the IDs are returned
790 * unchanged with no overhead.
791 *
792 * @param int[] $ids Object IDs as stored on the rule.
793 * @param string $type Element type: a post type ('product') or taxonomy ('product_cat',
794 * 'product_tag', 'product_brand').
795 *
796 * @return int[]
797 */
798 protected function translateIds( array $ids, string $type ) : array {
799 if ( empty( $ids ) || !has_filter( 'wpml_object_id' ) ) {
800 return $ids;
801 }
802 $translated = array();
803 foreach ( $ids as $id ) {
804 // The `true` argument keeps the original ID when the object has no translation in the
805 // current language, preserving the previous behaviour for untranslated objects.
806 $translated[] = (int) apply_filters(
807 'wpml_object_id',
808 $id,
809 $type,
810 true
811 );
812 }
813 return $translated;
814 }
815
816 }
817