|
17 | 17 | use craft\commerce\Plugin; |
18 | 18 | use craft\commerce\records\Sale; |
19 | 19 | use craft\db\Query; |
| 20 | +use craft\db\QueryAbortedException; |
20 | 21 | use craft\db\Table as CraftTable; |
21 | 22 | use craft\helpers\ArrayHelper; |
22 | 23 | use craft\helpers\Db; |
| 24 | +use craft\helpers\StringHelper; |
23 | 25 | use DateTime; |
24 | 26 | use yii\base\InvalidArgumentException; |
25 | 27 | use yii\base\InvalidConfigException; |
@@ -95,6 +97,13 @@ class VariantQuery extends PurchasableQuery |
95 | 97 | */ |
96 | 98 | public mixed $ownerId = null; |
97 | 99 |
|
| 100 | + /** |
| 101 | + * @var array|string|null The status the owner product must have. |
| 102 | + * @used-by productStatus() |
| 103 | + * @since 5.5.0 |
| 104 | + */ |
| 105 | + public array|string|null $productStatus = null; |
| 106 | + |
98 | 107 | /** |
99 | 108 | * @var mixed |
100 | 109 | */ |
@@ -257,6 +266,43 @@ public function ownerId(mixed $value): VariantQuery |
257 | 266 | return $this; |
258 | 267 | } |
259 | 268 |
|
| 269 | + /** |
| 270 | + * Narrows the query results based on the {elements}’ product’s statuses. |
| 271 | + * |
| 272 | + * Possible values include: |
| 273 | + * |
| 274 | + * | Value | Fetches {elements}… |
| 275 | + * | - | - |
| 276 | + * | `'enabled'` _(default)_ | that are enabled. |
| 277 | + * | `'disabled'` | that are disabled. |
| 278 | + * | `['not', 'disabled']` | that are not disabled. |
| 279 | + * |
| 280 | + * --- |
| 281 | + * |
| 282 | + * ```twig |
| 283 | + * {# Fetch {elements} with disabled products #} |
| 284 | + * {% set {elements-var} = {twig-method} |
| 285 | + * .productStatus('disabled') |
| 286 | + * .all() %} |
| 287 | + * ``` |
| 288 | + * |
| 289 | + * ```php |
| 290 | + * // Fetch {elements} with disabled products |
| 291 | + * ${elements-var} = {php-method} |
| 292 | + * ->productStatus('disabled') |
| 293 | + * ->all(); |
| 294 | + * ``` |
| 295 | + * |
| 296 | + * @param string|string[]|null $value The property value |
| 297 | + * @return static self reference |
| 298 | + * @since 5.5.0 |
| 299 | + */ |
| 300 | + public function productStatus(array|string|null $value): VariantQuery |
| 301 | + { |
| 302 | + $this->productStatus = $value; |
| 303 | + return $this; |
| 304 | + } |
| 305 | + |
260 | 306 | /** |
261 | 307 | * Narrows the query results based on the variants’ product types, per their IDs. |
262 | 308 | * |
@@ -460,6 +506,10 @@ protected function beforePrepare(): bool |
460 | 506 | $this->subQuery->andWhere(['commerce_variants.primaryOwnerId' => $this->productId]); |
461 | 507 | } |
462 | 508 |
|
| 509 | + if (isset($this->productStatus)) { |
| 510 | + $this->_applyProductStatusParam(); |
| 511 | + } |
| 512 | + |
463 | 513 | if (isset($this->isDefault)) { |
464 | 514 | $this->subQuery->andWhere(Db::parseBooleanParam('isDefault', $this->isDefault, false)); |
465 | 515 | } |
@@ -689,6 +739,21 @@ protected function beforePrepare(): bool |
689 | 739 | return parent::beforePrepare(); |
690 | 740 | } |
691 | 741 |
|
| 742 | + protected function afterPrepare(): bool |
| 743 | + { |
| 744 | + if (!parent::afterPrepare()) { |
| 745 | + return false; |
| 746 | + } |
| 747 | + |
| 748 | + // Due to how the element sites table are joined in the subquery we need to do this later in the process |
| 749 | + if ($this->productStatus) { |
| 750 | + $this->subQuery->leftJoin(CraftTable::ELEMENTS . ' product_elements', '[[product_elements.id]] = [[commerce_variants.primaryOwnerId]]'); |
| 751 | + $this->subQuery->leftJoin(CraftTable::ELEMENTS_SITES . ' product_elements_sites', '[[product_elements_sites.elementId]] = [[commerce_variants.primaryOwnerId]] and [[product_elements_sites.siteId]] = [[elements_sites.siteId]]'); |
| 752 | + } |
| 753 | + |
| 754 | + return true; |
| 755 | + } |
| 756 | + |
692 | 757 | /** |
693 | 758 | * Normalizes the primaryOwnerId param to an array of IDs or null |
694 | 759 | * |
@@ -737,6 +802,76 @@ private function _applyHasProductParam(): void |
737 | 802 | $this->subQuery->andWhere(['commerce_variants.primaryOwnerId' => $productQuery]); |
738 | 803 | } |
739 | 804 |
|
| 805 | + /** |
| 806 | + * Applies the 'productStatus' param to the query being prepared. |
| 807 | + * |
| 808 | + * @since 5.5.0 |
| 809 | + */ |
| 810 | + private function _applyProductStatusParam(): void |
| 811 | + { |
| 812 | + if (!$this->productStatus) { |
| 813 | + return; |
| 814 | + } |
| 815 | + |
| 816 | + // Normalize the product status param |
| 817 | + if (!is_array($this->productStatus)) { |
| 818 | + $this->productStatus = StringHelper::split($this->productStatus); |
| 819 | + } |
| 820 | + |
| 821 | + $statuses = array_merge($this->productStatus); |
| 822 | + |
| 823 | + $firstVal = strtolower(reset($statuses)); |
| 824 | + if (in_array($firstVal, ['not', 'or'])) { |
| 825 | + $glue = $firstVal; |
| 826 | + array_shift($statuses); |
| 827 | + if (!$statuses) { |
| 828 | + return; |
| 829 | + } |
| 830 | + } else { |
| 831 | + $glue = 'or'; |
| 832 | + } |
| 833 | + |
| 834 | + if ($negate = ($glue === 'not')) { |
| 835 | + $glue = 'and'; |
| 836 | + } |
| 837 | + |
| 838 | + $condition = [$glue]; |
| 839 | + |
| 840 | + foreach ($statuses as $status) { |
| 841 | + $status = strtolower($status); |
| 842 | + |
| 843 | + // Logic comes from the `statusCondition()` method in `craft\base\ElementQuery` |
| 844 | + // but duplicated here to make editing the table naming more verbose. |
| 845 | + $statusCondition = match ($status) { |
| 846 | + Element::STATUS_ENABLED => [ |
| 847 | + 'product_elements.enabled' => true, |
| 848 | + 'product_elements_sites.enabled' => true, |
| 849 | + ], |
| 850 | + Element::STATUS_DISABLED => [ |
| 851 | + 'or', |
| 852 | + ['product_elements.enabled' => false], |
| 853 | + ['product_elements_sites.enabled' => false], |
| 854 | + ], |
| 855 | + Element::STATUS_ARCHIVED => ['product_elements.archived' => true], |
| 856 | + default => false, |
| 857 | + }; |
| 858 | + |
| 859 | + if ($statusCondition === false) { |
| 860 | + throw new QueryAbortedException('Unsupported status: ' . $status); |
| 861 | + } |
| 862 | + |
| 863 | + if ($statusCondition !== null) { |
| 864 | + if ($negate) { |
| 865 | + $condition[] = ['not', $statusCondition]; |
| 866 | + } else { |
| 867 | + $condition[] = $statusCondition; |
| 868 | + } |
| 869 | + } |
| 870 | + } |
| 871 | + |
| 872 | + $this->subQuery->andWhere($condition); |
| 873 | + } |
| 874 | + |
740 | 875 | /** |
741 | 876 | * @inheritdoc |
742 | 877 | * @since 3.5.0 |
|
0 commit comments