← All changes
|
src/Addons/GlobalTieredPricing/GlobalPricingRule.php
+288
-13
7.1.4
→
8.1.0
View file →
| @@ -3,8 +3,10 @@ | ||
| 3 | 3 | namespace TierPricingTable\Addons\GlobalTieredPricing; |
| 4 | 4 | |
| 5 | 5 | use TierPricingTable\Addons\GlobalTieredPricing\PricingRule\RuleSettings; |
| 6 | 6 | use TierPricingTable\Forms\Form; |
| 7 | +use DateTime; | |
| 8 | +use DateTimeZone; | |
| 7 | 9 | use Exception; |
| 8 | 10 | use WC_Product; |
| 9 | 11 | use WP_User; |
| 10 | 12 | class GlobalPricingRule { |
| @@ -113,8 +115,15 @@ | ||
| 113 | 115 | */ |
| 114 | 116 | public $excludedProductCategories = array(); |
| 115 | 117 | |
| 116 | 118 | /** |
| 119 | + * Whether the included and excluded categories cover their subcategories too. | |
| 120 | + * | |
| 121 | + * @var bool | |
| 122 | + */ | |
| 123 | + public $includeSubcategories = false; | |
| 124 | + | |
| 125 | + /** | |
| 117 | 126 | * Included tags |
| 118 | 127 | * |
| 119 | 128 | * @var array |
| 120 | 129 | */ |
| @@ -198,9 +207,59 @@ | ||
| 198 | 207 | public $mixAndMatchMinQuantity = null; |
| 199 | 208 | |
| 200 | 209 | public $priorityOptions; |
| 201 | 210 | |
| 211 | + const DEFAULT_PRIORITY = 10; | |
| 212 | + | |
| 202 | 213 | /** |
| 214 | + * The pseudo role of a visitor who is not logged in, selectable in the role lists. The same key the | |
| 215 | + * wholesale registration add-on uses (NonLoggedInUsers\Roles::GUEST). | |
| 216 | + */ | |
| 217 | + const GUEST_ROLE = 'guest'; | |
| 218 | + | |
| 219 | + /** | |
| 220 | + * The roles the matcher reads for a user: a visitor who is not logged in has the guest pseudo role. | |
| 221 | + */ | |
| 222 | + public static function rolesOf( WP_User $user ) : array { | |
| 223 | + return ( $user->ID ? (array) $user->roles : array(self::GUEST_ROLE) ); | |
| 224 | + } | |
| 225 | + | |
| 226 | + /** | |
| 227 | + * Whether a role key may be stored in the rule's role lists: an existing WordPress role or the guest pseudo role. | |
| 228 | + */ | |
| 229 | + public static function isSelectableRole( $role ) : bool { | |
| 230 | + return self::GUEST_ROLE === $role || function_exists( 'wp_roles' ) && array_key_exists( $role, wp_roles()->roles ); | |
| 231 | + } | |
| 232 | + | |
| 233 | + /** | |
| 234 | + * Order among global rules: the lowest number is applied first; equal numbers, the newest rule first. | |
| 235 | + * | |
| 236 | + * @var int | |
| 237 | + */ | |
| 238 | + public $priority = self::DEFAULT_PRIORITY; | |
| 239 | + | |
| 240 | + /** | |
| 241 | + * First day the rule applies (Y-m-d in the site's timezone, inclusive), null = no start. | |
| 242 | + * | |
| 243 | + * @var ?string | |
| 244 | + */ | |
| 245 | + public $startDate = null; | |
| 246 | + | |
| 247 | + /** | |
| 248 | + * Last day the rule applies (Y-m-d in the site's timezone, inclusive), null = no end. | |
| 249 | + * | |
| 250 | + * @var ?string | |
| 251 | + */ | |
| 252 | + public $endDate = null; | |
| 253 | + | |
| 254 | + /** | |
| 255 | + * post_date_gmt of the rule: orders rules with the same priority (newest first). | |
| 256 | + * | |
| 257 | + * @var string | |
| 258 | + */ | |
| 259 | + public $createdAt = ''; | |
| 260 | + | |
| 261 | + /** | |
| 203 | 262 | * Array with custom data from 3rd-party addons |
| 204 | 263 | * |
| 205 | 264 | * @var array |
| 206 | 265 | */ |
| @@ -344,8 +403,11 @@ | ||
| 344 | 403 | $self->setPercentageTieredPricingRules( $percentageRules ); |
| 345 | 404 | $self->setFixedTieredPricingRules( $fixedRules ); |
| 346 | 405 | $self->setMinimum( ( Form::isEmpty( $minimum ) ? null : (int) $minimum ) ); |
| 347 | 406 | $self->setMixAndMatchMinQuantity( $mixAndMatch ); |
| 407 | + $self->setPriority( $data['priority'] ?? self::DEFAULT_PRIORITY ); | |
| 408 | + $self->setStartDate( $data['start_date'] ?? null ); | |
| 409 | + $self->setEndDate( $data['end_date'] ?? null ); | |
| 348 | 410 | return $self; |
| 349 | 411 | } |
| 350 | 412 | |
| 351 | 413 | /** |
| @@ -353,13 +415,19 @@ | ||
| 353 | 415 | * |
| 354 | 416 | * @throws Exception |
| 355 | 417 | */ |
| 356 | 418 | public function validatePricing() { |
| 357 | - $valid = !Form::isEmpty( $this->getRegularPrice() ); | |
| 358 | - $valid = $valid || !Form::isEmpty( $this->getSalePrice() ); | |
| 419 | + // only the fields of the chosen pricing type reach the storefront: a discount left over from | |
| 420 | + // "percentage" changes nothing in "flat" mode, and the other way round | |
| 421 | + if ( 'percentage' === $this->getPricingType() ) { | |
| 422 | + $valid = (float) $this->getDiscount() > 0; | |
| 423 | + } else { | |
| 424 | + $valid = !Form::isEmpty( $this->getRegularPrice() ) || !Form::isEmpty( $this->getSalePrice() ); | |
| 425 | + } | |
| 359 | 426 | $valid = $valid || !empty( $this->getTieredPricingRules() ); |
| 360 | 427 | $valid = $valid || !Form::isEmpty( $this->getMinimum() ); |
| 361 | - $valid = $valid || !Form::isEmpty( $this->getDiscount() ); | |
| 428 | + // a mix & match choice alone changes how product-level minimums count across variations | |
| 429 | + $valid = $valid || !is_null( $this->getMixAndMatchMinQuantity() ); | |
| 362 | 430 | $valid = $valid || $this->getSettings()->getPriorityType() === 'flexible'; |
| 363 | 431 | $valid = apply_filters( 'tiered_pricing_table/global_pricing/validation', $valid, $this ); |
| 364 | 432 | if ( !$valid ) { |
| 365 | 433 | throw new Exception(esc_html__( 'The pricing rule does not affect either prices or product quantity. The rule will be skipped.', 'tier-pricing-table' )); |
| @@ -406,8 +474,45 @@ | ||
| 406 | 474 | public function setExcludedProductCategories( array $excludedProductCategories ) { |
| 407 | 475 | $this->excludedProductCategories = $excludedProductCategories; |
| 408 | 476 | } |
| 409 | 477 | |
| 478 | + public function isIncludeSubcategories() : bool { | |
| 479 | + return $this->includeSubcategories; | |
| 480 | + } | |
| 481 | + | |
| 482 | + public function setIncludeSubcategories( bool $includeSubcategories ) { | |
| 483 | + $this->includeSubcategories = $includeSubcategories; | |
| 484 | + } | |
| 485 | + | |
| 486 | + /** | |
| 487 | + * The included categories as the matcher reads them: with every subcategory when the switch is on. | |
| 488 | + */ | |
| 489 | + public function getEffectiveIncludedCategories() : array { | |
| 490 | + return ( $this->includeSubcategories ? self::withSubcategories( $this->includedProductCategories ) : $this->includedProductCategories ); | |
| 491 | + } | |
| 492 | + | |
| 493 | + /** | |
| 494 | + * The excluded categories as the matcher reads them: with every subcategory when the switch is on. | |
| 495 | + */ | |
| 496 | + public function getEffectiveExcludedCategories() : array { | |
| 497 | + return ( $this->includeSubcategories ? self::withSubcategories( $this->excludedProductCategories ) : $this->excludedProductCategories ); | |
| 498 | + } | |
| 499 | + | |
| 500 | + /** | |
| 501 | + * The ids plus all their descendant product categories. | |
| 502 | + */ | |
| 503 | + public static function withSubcategories( array $ids ) : array { | |
| 504 | + $all = array(); | |
| 505 | + foreach ( array_map( 'intval', $ids ) as $id ) { | |
| 506 | + $all[] = $id; | |
| 507 | + $children = get_term_children( $id, 'product_cat' ); | |
| 508 | + if ( is_array( $children ) ) { | |
| 509 | + $all = array_merge( $all, array_map( 'intval', $children ) ); | |
| 510 | + } | |
| 511 | + } | |
| 512 | + return array_values( array_unique( $all ) ); | |
| 513 | + } | |
| 514 | + | |
| 410 | 515 | public function getIncludedProductTags() : array { |
| 411 | 516 | return $this->includedProductTags; |
| 412 | 517 | } |
| 413 | 518 | |
| @@ -486,8 +591,168 @@ | ||
| 486 | 591 | public function setExcludedUsers( array $excludedUsers ) { |
| 487 | 592 | $this->excludedUsers = $excludedUsers; |
| 488 | 593 | } |
| 489 | 594 | |
| 595 | + public function getPriority() : int { | |
| 596 | + return $this->priority; | |
| 597 | + } | |
| 598 | + | |
| 599 | + /** | |
| 600 | + * @param mixed $priority Anything non-numeric (an empty meta row, for instance) falls back to the default. | |
| 601 | + */ | |
| 602 | + public function setPriority( $priority ) { | |
| 603 | + $this->priority = ( is_numeric( $priority ) ? max( 0, (int) $priority ) : self::DEFAULT_PRIORITY ); | |
| 604 | + } | |
| 605 | + | |
| 606 | + public function getStartDate() : ?string { | |
| 607 | + return $this->startDate; | |
| 608 | + } | |
| 609 | + | |
| 610 | + public function setStartDate( $date ) { | |
| 611 | + $this->startDate = self::normalizeDate( $date ); | |
| 612 | + } | |
| 613 | + | |
| 614 | + public function getEndDate() : ?string { | |
| 615 | + return $this->endDate; | |
| 616 | + } | |
| 617 | + | |
| 618 | + public function setEndDate( $date ) { | |
| 619 | + $this->endDate = self::normalizeDate( $date ); | |
| 620 | + } | |
| 621 | + | |
| 622 | + public function hasSchedule() : bool { | |
| 623 | + return null !== $this->startDate || null !== $this->endDate; | |
| 624 | + } | |
| 625 | + | |
| 626 | + public function getCreatedAt() : string { | |
| 627 | + return $this->createdAt; | |
| 628 | + } | |
| 629 | + | |
| 630 | + public function setCreatedAt( string $createdAt ) { | |
| 631 | + $this->createdAt = $createdAt; | |
| 632 | + } | |
| 633 | + | |
| 634 | + /** | |
| 635 | + * A "Y-m-d" string or null; anything else (a partial date, "0000-00-00", a timestamp) is dropped. | |
| 636 | + */ | |
| 637 | + protected static function normalizeDate( $date ) : ?string { | |
| 638 | + $date = ( is_string( $date ) ? trim( $date ) : '' ); | |
| 639 | + if ( '' === $date ) { | |
| 640 | + return null; | |
| 641 | + } | |
| 642 | + $parsed = DateTime::createFromFormat( '!Y-m-d', $date ); | |
| 643 | + return ( $parsed && $parsed->format( 'Y-m-d' ) === $date ? $date : null ); | |
| 644 | + } | |
| 645 | + | |
| 646 | + /** | |
| 647 | + * 00:00:00 of the start date in the site's timezone, as a timestamp; null without a start date. | |
| 648 | + */ | |
| 649 | + public function getStartTimestamp() : ?int { | |
| 650 | + return ( null === $this->startDate ? null : self::dayBoundary( $this->startDate, false ) ); | |
| 651 | + } | |
| 652 | + | |
| 653 | + /** | |
| 654 | + * 23:59:59 of the end date in the site's timezone, as a timestamp; null without an end date. | |
| 655 | + */ | |
| 656 | + public function getEndTimestamp() : ?int { | |
| 657 | + return ( null === $this->endDate ? null : self::dayBoundary( $this->endDate, true ) ); | |
| 658 | + } | |
| 659 | + | |
| 660 | + protected static function dayBoundary( string $date, bool $endOfDay ) : int { | |
| 661 | + $timezone = ( function_exists( 'wp_timezone' ) ? wp_timezone() : new DateTimeZone('UTC') ); | |
| 662 | + $moment = new DateTime($date . (( $endOfDay ? ' 23:59:59' : ' 00:00:00' )), $timezone); | |
| 663 | + return $moment->getTimestamp(); | |
| 664 | + } | |
| 665 | + | |
| 666 | + /** | |
| 667 | + * "active", "scheduled" (starts later) or "expired" (ended) at the given moment (default: now). | |
| 668 | + */ | |
| 669 | + public function getScheduleStatus( ?int $now = null ) : string { | |
| 670 | + $now = ( null === $now ? time() : $now ); | |
| 671 | + $start = $this->getStartTimestamp(); | |
| 672 | + $end = $this->getEndTimestamp(); | |
| 673 | + if ( null !== $start && $now < $start ) { | |
| 674 | + return 'scheduled'; | |
| 675 | + } | |
| 676 | + if ( null !== $end && $now > $end ) { | |
| 677 | + return 'expired'; | |
| 678 | + } | |
| 679 | + return 'active'; | |
| 680 | + } | |
| 681 | + | |
| 682 | + public function isWithinSchedule( ?int $now = null ) : bool { | |
| 683 | + return 'active' === $this->getScheduleStatus( $now ); | |
| 684 | + } | |
| 685 | + | |
| 686 | + const EFFECTIVE_STATUSES = array( | |
| 687 | + 'active', | |
| 688 | + 'scheduled', | |
| 689 | + 'expired', | |
| 690 | + 'suspended', | |
| 691 | + 'skipped' | |
| 692 | + ); | |
| 693 | + | |
| 694 | + /** | |
| 695 | + * What the storefront makes of a published rule, in order of precedence: "suspended", "expired", | |
| 696 | + * "skipped" (changes neither prices nor quantity limits), "scheduled" (starts later) or "active". | |
| 697 | + */ | |
| 698 | + public function getEffectiveStatus( ?int $now = null ) : string { | |
| 699 | + if ( $this->isSuspended() ) { | |
| 700 | + return 'suspended'; | |
| 701 | + } | |
| 702 | + $schedule = $this->getScheduleStatus( $now ); | |
| 703 | + if ( 'expired' === $schedule ) { | |
| 704 | + return 'expired'; | |
| 705 | + } | |
| 706 | + // shown before "scheduled" so an empty campaign rule gets fixed before it starts | |
| 707 | + if ( !$this->isValidPricing() ) { | |
| 708 | + return 'skipped'; | |
| 709 | + } | |
| 710 | + return $schedule; | |
| 711 | + } | |
| 712 | + | |
| 713 | + /** | |
| 714 | + * Whether a higher-priority rule can take products and customers away from this one: only a rule | |
| 715 | + * that is (or will be) in force. A suspended, expired or skipped rule is not applied anyway. | |
| 716 | + */ | |
| 717 | + public function canBeOverridden( ?int $now = null ) : bool { | |
| 718 | + return in_array( $this->getEffectiveStatus( $now ), array('active', 'scheduled'), true ); | |
| 719 | + } | |
| 720 | + | |
| 721 | + /** | |
| 722 | + * usort() callback: the order the rules are matched in. Lowest priority number first; among equal | |
| 723 | + * numbers the newest rule first (today's behaviour for rules that never had a priority), then by ID. | |
| 724 | + */ | |
| 725 | + public static function compareOrder( GlobalPricingRule $a, GlobalPricingRule $b ) : int { | |
| 726 | + if ( $a->getPriority() !== $b->getPriority() ) { | |
| 727 | + return $a->getPriority() <=> $b->getPriority(); | |
| 728 | + } | |
| 729 | + if ( $a->getCreatedAt() !== $b->getCreatedAt() ) { | |
| 730 | + return strcmp( $b->getCreatedAt(), $a->getCreatedAt() ); | |
| 731 | + } | |
| 732 | + return (int) $b->id <=> (int) $a->id; | |
| 733 | + } | |
| 734 | + | |
| 735 | + /** | |
| 736 | + * Whether this rule has a higher priority than another one (and so is applied where both match). | |
| 737 | + */ | |
| 738 | + public function outranks( GlobalPricingRule $other ) : bool { | |
| 739 | + return self::compareOrder( $this, $other ) < 0; | |
| 740 | + } | |
| 741 | + | |
| 742 | + /** | |
| 743 | + * The rule's title, or "Rule #ID" for a rule saved without one. | |
| 744 | + */ | |
| 745 | + public static function titleOf( int $id ) : string { | |
| 746 | + $title = get_the_title( $id ); | |
| 747 | + /* translators: %d: rule ID */ | |
| 748 | + return ( '' !== trim( (string) $title ) ? $title : sprintf( __( 'Rule #%d', 'tier-pricing-table' ), $id ) ); | |
| 749 | + } | |
| 750 | + | |
| 751 | + public function getTitle() : string { | |
| 752 | + return self::titleOf( (int) $this->id ); | |
| 753 | + } | |
| 754 | + | |
| 490 | 755 | public function getSettings() : RuleSettings { |
| 491 | 756 | if ( !$this->priorityOptions ) { |
| 492 | 757 | $this->priorityOptions = new RuleSettings($this); |
| 493 | 758 | } |
| @@ -508,9 +773,13 @@ | ||
| 508 | 773 | 'minimum' => $this->getMinimum(), |
| 509 | 774 | 'mix_and_match_minimum' => $this->getMixAndMatchMinQuantity(), |
| 510 | 775 | 'tax_status' => $this->getTaxStatus(), |
| 511 | 776 | 'tax_class' => $this->getTaxClass(), |
| 777 | + 'priority' => $this->getPriority(), | |
| 778 | + 'start_date' => $this->getStartDate(), | |
| 779 | + 'end_date' => $this->getEndDate(), | |
| 512 | 780 | 'included_categories' => $this->getIncludedProductCategories(), |
| 781 | + 'include_subcategories' => $this->isIncludeSubcategories(), | |
| 513 | 782 | 'included_tags' => $this->getIncludedProductTags(), |
| 514 | 783 | 'included_brands' => $this->getIncludedProductBrands(), |
| 515 | 784 | 'included_products' => $this->getIncludedProducts(), |
| 516 | 785 | 'included_users' => $this->getIncludedUsers(), |
| @@ -540,9 +809,13 @@ | ||
| 540 | 809 | '_tpt_minimum' => $this->getMinimum(), |
| 541 | 810 | '_tpt_mix_and_match_minimum' => ( is_null( $this->getMixAndMatchMinQuantity() ) ? '' : wc_bool_to_string( $this->getMixAndMatchMinQuantity() ) ), |
| 542 | 811 | '_tpt_tax_status' => $this->getTaxStatus(), |
| 543 | 812 | '_tpt_tax_class' => $this->getTaxClass(), |
| 813 | + '_tpt_priority' => $this->getPriority(), | |
| 814 | + '_tpt_start_date' => (string) $this->getStartDate(), | |
| 815 | + '_tpt_end_date' => (string) $this->getEndDate(), | |
| 544 | 816 | '_tpt_included_categories' => $this->getIncludedProductCategories(), |
| 817 | + '_tpt_include_subcategories' => wc_bool_to_string( $this->isIncludeSubcategories() ), | |
| 545 | 818 | '_tpt_included_tags' => $this->getIncludedProductTags(), |
| 546 | 819 | '_tpt_included_brands' => $this->getIncludedProductBrands(), |
| 547 | 820 | '_tpt_included_products' => $this->getIncludedProducts(), |
| 548 | 821 | '_tpt_included_users' => $this->getIncludedUsers(), |
| @@ -574,8 +847,11 @@ | ||
| 574 | 847 | '_tpt_mix_and_match_minimum' => 'mix_and_match_minimum', |
| 575 | 848 | '_tpt_tax_status' => 'tax_status', |
| 576 | 849 | '_tpt_tax_class' => 'tax_class', |
| 577 | 850 | '_tpt_is_suspended' => 'is_suspended', |
| 851 | + '_tpt_priority' => 'priority', | |
| 852 | + '_tpt_start_date' => 'start_date', | |
| 853 | + '_tpt_end_date' => 'end_date', | |
| 578 | 854 | ); |
| 579 | 855 | $data = array(); |
| 580 | 856 | foreach ( $dataToRead as $key => $name ) { |
| 581 | 857 | $data[$name] = get_post_meta( $ruleId, $key, true ); |
| @@ -580,29 +856,27 @@ | ||
| 580 | 856 | foreach ( $dataToRead as $key => $name ) { |
| 581 | 857 | $data[$name] = get_post_meta( $ruleId, $key, true ); |
| 582 | 858 | } |
| 583 | 859 | $priceRule = self::fromArray( $data ); |
| 860 | + $priceRule->setCreatedAt( (string) get_post_field( 'post_date_gmt', $ruleId ) ); | |
| 584 | 861 | $existingRoles = wp_roles()->roles; |
| 585 | 862 | $includedCategoriesIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_included_categories', true ) ) ); |
| 586 | 863 | $includedTagsIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_included_tags', true ) ) ); |
| 587 | 864 | $includedBrandsIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_included_brands', true ) ) ); |
| 588 | 865 | $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 | - } ); | |
| 866 | + $includedUsersRole = array_filter( (array) get_post_meta( $ruleId, '_tpt_included_user_roles', true ), array(self::class, 'isSelectableRole') ); | |
| 592 | 867 | $includedUsers = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_included_users', true ) ) ); |
| 593 | 868 | $excludedCategoriesIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_excluded_categories', true ) ) ); |
| 594 | 869 | $excludedTagsIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_excluded_tags', true ) ) ); |
| 595 | 870 | $excludedBrandsIds = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_excluded_brands', true ) ) ); |
| 596 | 871 | $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 | - } ); | |
| 872 | + $excludedUsersRole = array_filter( (array) get_post_meta( $ruleId, '_tpt_excluded_user_roles', true ), array(self::class, 'isSelectableRole') ); | |
| 600 | 873 | $excludedUsers = array_filter( array_map( 'intval', (array) get_post_meta( $ruleId, '_tpt_excluded_users', true ) ) ); |
| 601 | 874 | $isSuspended = get_post_meta( $ruleId, '_tpt_is_suspended', true ) === 'yes'; |
| 602 | 875 | $priceRule->setPercentageTieredPricingRules( self::readPricingRules( 'percentage', $ruleId ) ); |
| 603 | 876 | $priceRule->setFixedTieredPricingRules( self::readPricingRules( 'fixed', $ruleId ) ); |
| 604 | 877 | $priceRule->setIncludedProductCategories( $includedCategoriesIds ); |
| 878 | + $priceRule->setIncludeSubcategories( 'yes' === get_post_meta( $ruleId, '_tpt_include_subcategories', true ) ); | |
| 605 | 879 | $priceRule->setIncludedProductTags( $includedTagsIds ); |
| 606 | 880 | $priceRule->setIncludedProductBrands( $includedBrandsIds ); |
| 607 | 881 | $priceRule->setIncludedUsers( $includedUsers ); |
| 608 | 882 | $priceRule->setIncludedUsersRole( $includedUsersRole ); |
| @@ -674,9 +948,9 @@ | ||
| 674 | 948 | if ( in_array( $product->get_id(), $excludedProducts ) || in_array( $parentProduct->get_id(), $excludedProducts ) ) { |
| 675 | 949 | return false; |
| 676 | 950 | } |
| 677 | 951 | } |
| 678 | - $excludedProductCategories = $this->translateIds( $this->getExcludedProductCategories(), 'product_cat' ); | |
| 952 | + $excludedProductCategories = $this->translateIds( $this->getEffectiveExcludedCategories(), 'product_cat' ); | |
| 679 | 953 | if ( !empty( $excludedProductCategories ) ) { |
| 680 | 954 | if ( !empty( array_intersect( $parentProduct->get_category_ids(), $excludedProductCategories ) ) ) { |
| 681 | 955 | return false; |
| 682 | 956 | } |
| @@ -706,10 +980,11 @@ | ||
| 706 | 980 | */ |
| 707 | 981 | if ( in_array( $user->ID, $this->getExcludedUsers() ) ) { |
| 708 | 982 | return false; |
| 709 | 983 | } |
| 984 | + $userRoles = self::rolesOf( $user ); | |
| 710 | 985 | foreach ( $this->getExcludedUserRoles() as $role ) { |
| 711 | - if ( in_array( $role, $user->roles ) ) { | |
| 986 | + if ( in_array( $role, $userRoles ) ) { | |
| 712 | 987 | return false; |
| 713 | 988 | } |
| 714 | 989 | } |
| 715 | 990 | /** |
| @@ -725,9 +1000,9 @@ | ||
| 725 | 1000 | if ( in_array( $product->get_id(), $includedProducts ) || in_array( $parentProduct->get_id(), $includedProducts ) ) { |
| 726 | 1001 | $productMatched = true; |
| 727 | 1002 | } |
| 728 | 1003 | } |
| 729 | - $includedProductCategories = $this->translateIds( $this->getIncludedProductCategories(), 'product_cat' ); | |
| 1004 | + $includedProductCategories = $this->translateIds( $this->getEffectiveIncludedCategories(), 'product_cat' ); | |
| 730 | 1005 | if ( !empty( $includedProductCategories ) ) { |
| 731 | 1006 | $productLimitations = true; |
| 732 | 1007 | if ( !empty( array_intersect( $parentProduct->get_category_ids(), $includedProductCategories ) ) ) { |
| 733 | 1008 | $productMatched = true; |
| @@ -769,9 +1044,9 @@ | ||
| 769 | 1044 | if ( in_array( $user->ID, $this->getIncludedUsers() ) ) { |
| 770 | 1045 | return true; |
| 771 | 1046 | } |
| 772 | 1047 | foreach ( $this->getIncludedUserRoles() as $role ) { |
| 773 | - if ( in_array( $role, $user->roles ) ) { | |
| 1048 | + if ( in_array( $role, $userRoles ) ) { | |
| 774 | 1049 | return true; |
| 775 | 1050 | } |
| 776 | 1051 | } |
| 777 | 1052 | return false; |