Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 75 additions & 12 deletions src/elements/db/ProductQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,12 @@ protected function afterPrepare(): bool
// Store dependent related joins to the sub query need to be done after the `elements_sites` is joined in the base `ElementQuery` class.
$customerId = Craft::$app->getUser()->getIdentity()?->id;

$hasStoreTables = $this->_storeTablesExist();

if (!$hasStoreTables) {
return parent::afterPrepare();
}

$this->subQuery->leftJoin(['sitestores' => Table::SITESTORES], '[[elements_sites.siteId]] = [[sitestores.siteId]]');

if (Plugin::getInstance()->getCatalogPricingRules()->hasCatalogPricingRules()) {
Expand All @@ -771,6 +777,8 @@ protected function afterPrepare(): bool
protected function beforePrepare(): bool
{
$this->_normalizeTypeId();
$hasStoreTables = $this->_storeTablesExist();
$hasPurchasableDimensionColumns = $this->_purchasableDimensionColumnsExist();

// See if 'type' were set to invalid handles
if ($this->typeId === []) {
Expand All @@ -784,36 +792,67 @@ protected function beforePrepare(): bool
'commerce_products.typeId',
'commerce_products.postDate',
'commerce_products.expiryDate',
'purchasablesstores.basePrice as defaultBasePrice',
'purchasablesstores.basePromotionalPrice as defaultBasePromotionalPrice',
'commerce_products.defaultVariantId',
'purchasables.sku as defaultSku',
'purchasables.weight as defaultWeight',
'purchasables.length as defaultLength',
'purchasables.width as defaultWidth',
'purchasables.height as defaultHeight',
'sitestores.storeId',
]);

if ($hasPurchasableDimensionColumns) {
$this->query->addSelect([
'purchasables.weight as defaultWeight',
'purchasables.length as defaultLength',
'purchasables.width as defaultWidth',
'purchasables.height as defaultHeight',
]);
} else {
$this->query->addSelect([
new Expression('NULL as [[defaultWeight]]'),
new Expression('NULL as [[defaultLength]]'),
new Expression('NULL as [[defaultWidth]]'),
new Expression('NULL as [[defaultHeight]]'),
]);
}

if ($hasStoreTables) {
$this->query->addSelect([
'purchasablesstores.basePrice as defaultBasePrice',
'purchasablesstores.basePromotionalPrice as defaultBasePromotionalPrice',
'sitestores.storeId',
]);
} else {
$this->query->addSelect([
new Expression('NULL as [[defaultBasePrice]]'),
new Expression('NULL as [[defaultBasePromotionalPrice]]'),
new Expression('NULL as [[storeId]]'),
]);
}

// Join in sites stores to get product's store for current request
$this->query->leftJoin(['sitestores' => Table::SITESTORES], '[[elements_sites.siteId]] = [[sitestores.siteId]]');
$this->query->leftJoin(['purchasables' => Table::PURCHASABLES], '[[purchasables.id]] = [[commerce_products.defaultVariantId]]');
$this->query->leftJoin(['purchasablesstores' => Table::PURCHASABLES_STORES], '[[purchasablesstores.purchasableId]] = [[commerce_products.defaultVariantId]] and [[sitestores.storeId]] = [[purchasablesstores.storeId]]');
if ($hasStoreTables) {
$this->query->leftJoin(['sitestores' => Table::SITESTORES], '[[elements_sites.siteId]] = [[sitestores.siteId]]');
$this->query->leftJoin(['purchasablesstores' => Table::PURCHASABLES_STORES], '[[purchasablesstores.purchasableId]] = [[commerce_products.defaultVariantId]] and [[sitestores.storeId]] = [[purchasablesstores.storeId]]');
}

// Tailor the query based on whether or not there is catalog pricing rules
if (Plugin::getInstance()->getCatalogPricingRules()->hasCatalogPricingRules()) {
if ($hasStoreTables && Plugin::getInstance()->getCatalogPricingRules()->hasCatalogPricingRules()) {
$this->query->addSelect(['subquery.price as defaultPrice']);
$this->subQuery->addSelect(['catalogprices.price']);

if (isset($this->defaultPrice)) {
$this->subQuery->andWhere(Db::parseParam('catalogprices.price', $this->defaultPrice));
}
} else {
} elseif ($hasStoreTables) {
$this->query->addSelect(['purchasablesstores.basePrice as defaultPrice']);

if (isset($this->defaultPrice)) {
$this->subQuery->andWhere(Db::parseParam('purchasablesstores.basePrice', $this->defaultPrice));
}
} else {
$this->query->addSelect([new Expression('NULL as [[defaultPrice]]')]);

if (isset($this->defaultPrice)) {
return false;
}
}

if (isset($this->postDate)) {
Expand All @@ -826,7 +865,13 @@ protected function beforePrepare(): bool

$this->_applyProductTypeIdParam();

if (isset($this->defaultHeight) || isset($this->defaultLength) || isset($this->defaultWidth) || isset($this->defaultWeight) || isset($this->defaultSku)) {
if (isset($this->defaultHeight) || isset($this->defaultLength) || isset($this->defaultWidth) || isset($this->defaultWeight)) {
if (!$hasPurchasableDimensionColumns) {
return false;
}

$this->subQuery->leftJoin(['purchasables' => Table::PURCHASABLES], '[[purchasables.id]] = [[commerce_products.defaultVariantId]]');
} elseif (isset($this->defaultSku)) {
$this->subQuery->leftJoin(['purchasables' => Table::PURCHASABLES], '[[purchasables.id]] = [[commerce_products.defaultVariantId]]');
}

Expand Down Expand Up @@ -1053,4 +1098,22 @@ protected function cacheTags(): array

return $tags;
}

private function _storeTablesExist(): bool
{
$db = Craft::$app->getDb();

return $db->tableExists(Table::SITESTORES) &&
$db->tableExists(Table::PURCHASABLES_STORES);
}

private function _purchasableDimensionColumnsExist(): bool
{
$db = Craft::$app->getDb();

return $db->columnExists(Table::PURCHASABLES, 'weight') &&
$db->columnExists(Table::PURCHASABLES, 'length') &&
$db->columnExists(Table::PURCHASABLES, 'width') &&
$db->columnExists(Table::PURCHASABLES, 'height');
}
}
161 changes: 135 additions & 26 deletions src/elements/db/PurchasableQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -655,11 +655,15 @@ public function onPromotion(bool|null $value = true): static
protected function afterPrepare(): bool
{
// Store dependent related joins to the sub query need to be done after the `elements_sites` is joined in the base `ElementQuery` class.
$this->subQuery->leftJoin(['sitestores' => Table::SITESTORES], '[[elements_sites.siteId]] = [[sitestores.siteId]]');
$this->subQuery->leftJoin(['purchasables_stores' => Table::PURCHASABLES_STORES], '[[purchasables_stores.storeId]] = [[sitestores.storeId]] AND [[purchasables_stores.purchasableId]] = [[commerce_purchasables.id]]');
$hasStoreTables = $this->_storeTablesExist();

if ($hasStoreTables) {
$this->subQuery->leftJoin(['sitestores' => Table::SITESTORES], '[[elements_sites.siteId]] = [[sitestores.siteId]]');
$this->subQuery->leftJoin(['purchasables_stores' => Table::PURCHASABLES_STORES], '[[purchasables_stores.storeId]] = [[sitestores.storeId]] AND [[purchasables_stores.purchasableId]] = [[commerce_purchasables.id]]');
}

// Only do the extra catalog pricing query join if we have catalog pricing rules.
if (Plugin::getInstance()->getCatalogPricingRules()->hasCatalogPricingRules()) {
if ($hasStoreTables && Plugin::getInstance()->getCatalogPricingRules()->hasCatalogPricingRules()) {
$customerId = $this->forCustomer;
if ($customerId === null) {
$customerId = Craft::$app->getUser()->getIdentity()?->id;
Expand All @@ -675,7 +679,9 @@ protected function afterPrepare(): bool
$this->subQuery->leftJoin(['catalogprices' => $catalogPricesQuery], '[[catalogprices.purchasableId]] = [[commerce_purchasables.id]] AND [[catalogprices.storeId]] = [[sitestores.storeId]]');
}

$this->subQuery->leftJoin(['inventoryitems' => Table::INVENTORYITEMS], '[[inventoryitems.purchasableId]] = [[commerce_purchasables.id]]');
if (Craft::$app->getDb()->tableExists(Table::INVENTORYITEMS)) {
$this->subQuery->leftJoin(['inventoryitems' => Table::INVENTORYITEMS], '[[inventoryitems.purchasableId]] = [[commerce_purchasables.id]]');
}

return parent::afterPrepare();
}
Expand All @@ -685,33 +691,73 @@ protected function afterPrepare(): bool
*/
protected function beforePrepare(): bool
{
$hasStoreTables = $this->_storeTablesExist();
$hasInventoryTable = Craft::$app->getDb()->tableExists(Table::INVENTORYITEMS);
$hasPurchasableDetailColumns = $this->_purchasableDetailColumnsExist();

$this->joinElementTable('commerce_purchasables');
$this->query->addSelect([
'commerce_purchasables.sku',
'commerce_purchasables.width',
'commerce_purchasables.height',
'commerce_purchasables.length',
'commerce_purchasables.weight',
'commerce_purchasables.taxCategoryId',
'purchasables_stores.availableForPurchase',
'purchasables_stores.basePrice',
'purchasables_stores.basePromotionalPrice',
'purchasables_stores.freeShipping',
'purchasables_stores.maxQty',
'purchasables_stores.minQty',
'purchasables_stores.inventoryTracked',
'purchasables_stores.allowOutOfStockPurchases',
'purchasables_stores.promotable',
'purchasables_stores.shippingCategoryId',
'inventoryitems.id as inventoryItemId',
]);

$this->query->leftJoin(Table::SITESTORES . ' sitestores', '[[elements_sites.siteId]] = [[sitestores.siteId]]');
$this->query->leftJoin(Table::PURCHASABLES_STORES . ' purchasables_stores', '[[purchasables_stores.storeId]] = [[sitestores.storeId]] AND [[purchasables_stores.purchasableId]] = [[commerce_purchasables.id]]');
$this->query->leftJoin(['inventoryitems' => Table::INVENTORYITEMS], '[[inventoryitems.purchasableId]] = [[commerce_purchasables.id]]');
if ($hasPurchasableDetailColumns) {
$this->query->addSelect([
'commerce_purchasables.width',
'commerce_purchasables.height',
'commerce_purchasables.length',
'commerce_purchasables.weight',
'commerce_purchasables.taxCategoryId',
]);
} else {
$this->query->addSelect([
new Expression('NULL as [[width]]'),
new Expression('NULL as [[height]]'),
new Expression('NULL as [[length]]'),
new Expression('NULL as [[weight]]'),
new Expression('NULL as [[taxCategoryId]]'),
]);
}

if ($hasStoreTables) {
$this->query->addSelect([
'purchasables_stores.availableForPurchase',
'purchasables_stores.basePrice',
'purchasables_stores.basePromotionalPrice',
'purchasables_stores.freeShipping',
'purchasables_stores.maxQty',
'purchasables_stores.minQty',
'purchasables_stores.inventoryTracked',
'purchasables_stores.allowOutOfStockPurchases',
'purchasables_stores.promotable',
'purchasables_stores.shippingCategoryId',
]);

$this->query->leftJoin(Table::SITESTORES . ' sitestores', '[[elements_sites.siteId]] = [[sitestores.siteId]]');
$this->query->leftJoin(Table::PURCHASABLES_STORES . ' purchasables_stores', '[[purchasables_stores.storeId]] = [[sitestores.storeId]] AND [[purchasables_stores.purchasableId]] = [[commerce_purchasables.id]]');
} else {
$this->query->addSelect([
new Expression('1 as [[availableForPurchase]]'),
new Expression('NULL as [[basePrice]]'),
new Expression('NULL as [[basePromotionalPrice]]'),
new Expression('0 as [[freeShipping]]'),
new Expression('NULL as [[maxQty]]'),
new Expression('NULL as [[minQty]]'),
new Expression('0 as [[inventoryTracked]]'),
new Expression('0 as [[allowOutOfStockPurchases]]'),
new Expression('0 as [[promotable]]'),
new Expression('NULL as [[shippingCategoryId]]'),
]);
}

if ($hasInventoryTable) {
$this->query->addSelect(['inventoryitems.id as inventoryItemId']);
$this->query->leftJoin(['inventoryitems' => Table::INVENTORYITEMS], '[[inventoryitems.purchasableId]] = [[commerce_purchasables.id]]');
} else {
$this->query->addSelect([new Expression('NULL as [[inventoryItemId]]')]);
}

// Only do the extra catalog pricing query join if we have catalog pricing rules.
if (Plugin::getInstance()->getCatalogPricingRules()->hasCatalogPricingRules()) {
if ($hasStoreTables && Plugin::getInstance()->getCatalogPricingRules()->hasCatalogPricingRules()) {
$this->query->addSelect([
'subquery.price',
'subquery.promotionalPrice as promotionalPrice',
Expand Down Expand Up @@ -743,7 +789,7 @@ protected function beforePrepare(): bool
if (isset($this->salePrice)) {
$this->subQuery->andWhere(Db::parseNumericParam('catalogprices.salePrice' , $this->salePrice));
}
} else {
} elseif ($hasStoreTables) {
// If Catalog pricing rules are not being used
$this->query->addSelect([
'purchasables_stores.basePrice as price',
Expand Down Expand Up @@ -777,6 +823,17 @@ protected function beforePrepare(): bool
if (isset($this->salePrice)) {
$this->subQuery->andWhere(Db::parseNumericParam(new Expression('CASE WHEN [[purchasables_stores.basePromotionalPrice]] < [[purchasables_stores.basePrice]] THEN [[purchasables_stores.basePromotionalPrice]] ELSE [[purchasables_stores.basePrice]] END') , $this->salePrice));
}
} else {
$this->query->addSelect([
new Expression('NULL as [[price]]'),
new Expression('NULL as [[promotionalPrice]]'),
new Expression('NULL as [[salePrice]]'),
new Expression('NULL as [[catalogPricingRuleId]]'),
]);

if ($this->_hasStoreTableParams()) {
return false;
}
}

if (isset($this->sku)) {
Expand Down Expand Up @@ -811,6 +868,10 @@ protected function beforePrepare(): bool
}

if (isset($this->taxCategoryId)) {
if (!$hasPurchasableDetailColumns) {
return false;
}

if ($this->taxCategoryId instanceof Query) {
$taxCategoryWhere = ['exists', $this->taxCategoryId];
} else {
Expand All @@ -821,6 +882,10 @@ protected function beforePrepare(): bool
}

if ($this->width !== false) {
if (!$hasPurchasableDetailColumns) {
return false;
}

if ($this->width === null) {
$this->subQuery->andWhere(['commerce_purchasables.width' => $this->width]);
} else {
Expand All @@ -829,6 +894,10 @@ protected function beforePrepare(): bool
}

if ($this->height !== false) {
if (!$hasPurchasableDetailColumns) {
return false;
}

if ($this->height === null) {
$this->subQuery->andWhere(['commerce_purchasables.height' => $this->height]);
} else {
Expand All @@ -837,6 +906,10 @@ protected function beforePrepare(): bool
}

if ($this->length !== false) {
if (!$hasPurchasableDetailColumns) {
return false;
}

if ($this->length === null) {
$this->subQuery->andWhere(['commerce_purchasables.length' => $this->length]);
} else {
Expand All @@ -845,6 +918,10 @@ protected function beforePrepare(): bool
}

if ($this->weight !== false) {
if (!$hasPurchasableDetailColumns) {
return false;
}

if ($this->weight === null) {
$this->subQuery->andWhere(['commerce_purchasables.weight' => $this->weight]);
} else {
Expand Down Expand Up @@ -880,7 +957,7 @@ protected function beforePrepare(): bool
*/
public function populate($rows): array
{
if (!empty($rows) && Plugin::getInstance()->getCatalogPricingRules()->hasCatalogPricingRules()) {
if (!empty($rows) && $this->_storeTablesExist() && Plugin::getInstance()->getCatalogPricingRules()->hasCatalogPricingRules()) {
$row = ArrayHelper::firstValue($rows);
$store = Plugin::getInstance()->getStores()->getStoreBySiteId($row['siteId']);
$purchasableIds = ArrayHelper::getColumn($rows, 'id');
Expand Down Expand Up @@ -920,4 +997,36 @@ public function populate($rows): array

return parent::populate($rows);
}

private function _storeTablesExist(): bool
{
$db = Craft::$app->getDb();

return $db->tableExists(Table::SITESTORES) &&
$db->tableExists(Table::PURCHASABLES_STORES);
}

private function _hasStoreTableParams(): bool
{
return isset($this->price) ||
isset($this->promotionalPrice) ||
isset($this->onPromotion) ||
isset($this->salePrice) ||
isset($this->stock) ||
isset($this->inventoryTracked) ||
isset($this->availableForPurchase) ||
isset($this->shippingCategoryId) ||
isset($this->hasStock);
}

private function _purchasableDetailColumnsExist(): bool
{
$db = Craft::$app->getDb();

return $db->columnExists(Table::PURCHASABLES, 'width') &&
$db->columnExists(Table::PURCHASABLES, 'height') &&
$db->columnExists(Table::PURCHASABLES, 'length') &&
$db->columnExists(Table::PURCHASABLES, 'weight') &&
$db->columnExists(Table::PURCHASABLES, 'taxCategoryId');
}
}
Loading
Loading