Created
December 19, 2017 15:39
-
-
Save Wiejeben/5938e29900cacfead6753ba2888353eb to your computer and use it in GitHub Desktop.
Skip form validation for disabled variants. Also skip form validation for the default variant if the product is disabled.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* User: Maarten de Graaf | |
* Date: 19/12/2017 | |
* Time: 15:36 | |
*/ | |
namespace Craft; | |
use Commerce\Interfaces\Purchasable; | |
/** | |
* This class overwrites the saving behaviour for the default variant of a product. | |
* Normally when saving an enabled element, form validation is being accounted for. | |
* However when an element is supposedly disabled, it should skip validation. | |
* Default variants cannot be disabled, therefore we need to overwrite this behaviour. | |
*/ | |
class Exact_PurchasablesService extends BaseApplicationComponent | |
{ | |
/** | |
* Saves product and bypass form validation for the default variant if the product is disabled. | |
* | |
* @param Commerce_ProductModel $product | |
* | |
* @return bool | |
* @throws Exception | |
* @throws \Exception | |
*/ | |
public function saveProduct(Commerce_ProductModel $product) | |
{ | |
$productsService = craft()->commerce_products; | |
$isNewProduct = !$product->id; | |
if (!$product->id) { | |
$record = new Commerce_ProductRecord(); | |
} else { | |
$record = Commerce_ProductRecord::model()->findById($product->id); | |
if (!$record) { | |
throw new Exception(Craft::t('No product exists with the ID “{id}”', | |
['id' => $product->id])); | |
} | |
} | |
// Fire an 'onBeforeSaveProduct' event | |
$event = new Event($productsService, [ | |
'product' => $product, | |
'isNewProduct' => $isNewProduct | |
]); | |
$productsService->onBeforeSaveProduct($event); | |
$record->postDate = $product->postDate; | |
$record->expiryDate = $product->expiryDate; | |
$record->typeId = $product->typeId; | |
$record->promotable = $product->promotable; | |
$record->freeShipping = $product->freeShipping; | |
$record->taxCategoryId = $product->taxCategoryId; | |
$record->shippingCategoryId = $product->shippingCategoryId; | |
$record->validate(); | |
$product->addErrors($record->getErrors()); | |
$productType = craft()->commerce_productTypes->getProductTypeById($product->typeId); | |
if(!$productType){ | |
throw new Exception(Craft::t('No product type exists with the ID “{id}”', | |
['id' => $product->typeId])); | |
} | |
$taxCategoryIds = array_keys($productType->getTaxCategories()); | |
if (!in_array($product->taxCategoryId, $taxCategoryIds)) | |
{ | |
$record->taxCategoryId = $product->taxCategoryId = $taxCategoryIds[0]; | |
} | |
$shippingCategoryIds = array_keys($productType->getShippingCategories()); | |
if (!in_array($product->shippingCategoryId, $shippingCategoryIds)) | |
{ | |
$record->shippingCategoryId = $product->shippingCategoryId = $shippingCategoryIds[0]; | |
} | |
// Final prep of variants and validation | |
$variantsValid = true; | |
$defaultVariant = null; | |
foreach ($product->getVariants() as $variant) { | |
// Use the product type's titleFormat if the title field is not shown | |
if (!$productType->hasVariantTitleField && $productType->hasVariants) | |
{ | |
try | |
{ | |
$variant->getContent()->title = craft()->templates->renderObjectTemplate($productType->titleFormat, $variant); | |
}catch(\Exception $e){ | |
$variant->getContent()->title = ""; | |
} | |
} | |
if(!$productType->hasVariants) | |
{ | |
// Since VariantModel::getTitle() returns the parent products title when the product has | |
// no variants, lets save the products title as the variant title anyway. | |
$variant->getContent()->title = $product->getTitle(); | |
} | |
// If we have a blank SKU, generate from product type's skuFormat | |
if(!$variant->sku){ | |
try | |
{ | |
if (!$productType->hasVariants) | |
{ | |
$variant->sku = craft()->templates->renderObjectTemplate($productType->skuFormat, $product); | |
} | |
else | |
{ | |
$variant->sku = craft()->templates->renderObjectTemplate($productType->skuFormat, $variant); | |
} | |
}catch(\Exception $e){ | |
CommercePlugin::log("Could not generate SKU format: ".$e->getMessage(), LogLevel::Warning, true); | |
$variant->sku = ""; | |
} | |
} | |
// Make the first variant (or the last one that says it isDefault) the default. | |
if ($defaultVariant === null || $variant->isDefault) | |
{ | |
$defaultVariant = $variant; | |
} | |
if ($this->shouldValidateContent($variant) && !craft()->commerce_variants->validateVariant($variant)) { | |
$variantsValid = false; | |
// If we have a title error but hide the title field, put the error onto the sku. | |
if($variant->getError('title') && !$productType->hasVariantTitleField && $productType->hasVariants){ | |
$variant->addError('sku',Craft::t('Could not generate the variant title from product type’s title format.')); | |
} | |
if($variant->getError('title') && !$productType->hasVariants){ | |
$product->addError('title',Craft::t('Title cannot be blank.')); | |
} | |
} | |
} | |
if ($product->hasErrors() || !$variantsValid) | |
{ | |
return false; | |
} | |
$transaction = craft()->db->getCurrentTransaction() === null ? craft()->db->beginTransaction() : null; | |
try { | |
$record->defaultVariantId = $product->defaultVariantId = $defaultVariant->getPurchasableId(); | |
$record->defaultSku = $product->defaultSku = $defaultVariant->getSku(); | |
$record->defaultPrice = $product->defaultPrice = (float) $defaultVariant->price; | |
$record->defaultHeight = $product->defaultHeight = (float) $defaultVariant->height; | |
$record->defaultLength = $product->defaultLength = (float) $defaultVariant->length; | |
$record->defaultWidth = $product->defaultWidth = (float) $defaultVariant->width; | |
$record->defaultWeight = $product->defaultWeight = (float) $defaultVariant->weight; | |
if ($event->performAction) | |
{ | |
$success = craft()->elements->saveElement($product); | |
if ($success) | |
{ | |
// Now that we have an element ID, save it on the other stuff | |
if ($isNewProduct) | |
{ | |
$record->id = $product->id; | |
} | |
$record->save(false); | |
$keepVariantIds = []; | |
$oldVariantIds = craft()->db->createCommand() | |
->select('id') | |
->from('commerce_variants') | |
->where('productId = :productId', [':productId' => $product->id]) | |
->queryColumn(); | |
foreach ($product->getVariants() as $variant) | |
{ | |
if ($defaultVariant === $variant) | |
{ | |
$variant->isDefault = true; | |
$variant->enabled = true; // default must always be enabled. | |
} | |
else | |
{ | |
$variant->isDefault = false; | |
} | |
$variant->setProduct($product); | |
$this->saveVariant($variant); | |
// Need to manually update the product's default variant ID now that we have a saved ID | |
if ($product->defaultVariantId === null && $defaultVariant === $variant) | |
{ | |
$product->defaultVariantId = $variant->id; | |
craft()->db->createCommand()->update('commerce_products', ['defaultVariantId' => $variant->id], ['id' => $product->id]); | |
} | |
$keepVariantIds[] = $variant->id; | |
} | |
foreach (array_diff($oldVariantIds, $keepVariantIds) as $deleteId) | |
{ | |
craft()->commerce_variants->deleteVariantById($deleteId); | |
} | |
if ($transaction !== null) | |
{ | |
$transaction->commit(); | |
} | |
} | |
}else{ | |
$success = false; | |
} | |
} catch (\Exception $e) { | |
if ($transaction !== null) | |
{ | |
$transaction->rollback(); | |
} | |
throw $e; | |
} | |
if ($success) | |
{ | |
// Fire an 'onSaveProduct' event | |
$productsService->onSaveProduct(new Event($productsService, [ | |
'product' => $product, | |
'isNewProduct' => $isNewProduct | |
])); | |
} | |
return $success; | |
} | |
/** | |
* @param Commerce_VariantModel $variant | |
* | |
* @return bool | |
*/ | |
public function validateVariant(Commerce_VariantModel $variant) | |
{ | |
$variant->clearErrors(); | |
$record = $this->_getVariantRecord($variant); | |
$this->_populateVariantRecord($record, $variant); | |
$record->validate(); | |
$variant->addErrors($record->getErrors()); | |
if (!craft()->content->validateContent($variant)) | |
{ | |
$variant->addErrors($variant->getContent()->getErrors()); | |
} | |
// If variant validation has not already found a clash check all purchasables | |
if (!$variant->getError('sku')) | |
{ | |
$existing = craft()->commerce_purchasables->getPurchasableBySku($variant->sku); | |
if ($existing) | |
{ | |
if ($existing->id != $variant->id) | |
{ | |
$variant->addError('sku', Craft::t('SKU has already been taken by another purchasable.')); | |
} | |
} | |
} | |
return !$variant->hasErrors(); | |
} | |
/** | |
* Persists a variant. | |
* | |
* @param BaseElementModel $model | |
* | |
* @return bool | |
* @throws \CDbException | |
* @throws \Exception | |
*/ | |
public function saveVariant(BaseElementModel $model) | |
{ | |
$record = $this->_getVariantRecord($model); | |
$this->_populateVariantRecord($record, $model); | |
$record->validate(); | |
$model->addErrors($record->getErrors()); | |
$transaction = craft()->db->getCurrentTransaction() === null ? craft()->db->beginTransaction() : null; | |
try | |
{ | |
if (!$model->hasErrors()) | |
{ | |
if ($this->saveElement($model)) | |
{ | |
$record->id = $model->id; | |
$record->save(false); | |
if ($transaction !== null) | |
{ | |
$transaction->commit(); | |
} | |
return true; | |
} | |
} | |
} | |
catch (\Exception $e) | |
{ | |
if ($transaction !== null) | |
{ | |
$transaction->rollback(); | |
} | |
throw $e; | |
} | |
if ($transaction !== null) | |
{ | |
$transaction->rollback(); | |
} | |
return false; | |
} | |
/** | |
* Saves the element and the purchasable. Use this function where you would usually | |
* use `craft()->elements->saveElement()` | |
* | |
* @param BaseElementModel $model | |
* | |
* @return bool | |
* @throws \Exception | |
*/ | |
public function saveElement(BaseElementModel $model) | |
{ | |
if (!$model instanceof Purchasable) { | |
throw new Exception('Trying to save a purchasable element that is not a purchasable.'); | |
} | |
$transaction = craft()->db->getCurrentTransaction() === null ? craft()->db->beginTransaction() : null; | |
try { | |
if ($success = craft()->elements->saveElement($model, $this->shouldValidateContent($model))) { | |
$id = $model->getPurchasableId(); | |
$price = $model->getPrice(); | |
$sku = $model->getSku(); | |
$purchasable = Commerce_PurchasableRecord::model()->findById($id); | |
if (!$purchasable) { | |
$purchasable = new Commerce_PurchasableRecord(); | |
} | |
$purchasable->id = $id; | |
$purchasable->price = $price; | |
$purchasable->sku = $sku; | |
$success = $purchasable->save(); | |
if (!$success) { | |
$model->addErrors($purchasable->getErrors()); | |
if ($transaction !== null) | |
{ | |
$transaction->rollback(); | |
} | |
return $success; | |
} | |
if ($transaction !== null) | |
{ | |
$transaction->commit(); | |
} | |
} | |
} catch (\Exception $e) { | |
if ($transaction !== null) | |
{ | |
$transaction->rollback(); | |
} | |
throw $e; | |
} | |
return $success; | |
} | |
/** | |
* Whether or not the contents have to be validated in order to be saved. | |
* | |
* @param BaseElementModel $model | |
* @return bool|null | |
*/ | |
public function shouldValidateContent(BaseElementModel $model) | |
{ | |
if ($model instanceof Commerce_VariantModel && $model->isDefault) { | |
return $model->getProduct()->enabled; | |
} | |
return $model->enabled; | |
} | |
/** | |
* @param BaseElementModel $model | |
* | |
* @return BaseRecord|Commerce_VariantRecord | |
* @throws HttpException | |
*/ | |
private function _getVariantRecord(BaseElementModel $model) | |
{ | |
if ($model->id) | |
{ | |
$record = Commerce_VariantRecord::model()->findById($model->id); | |
if (!$record) | |
{ | |
throw new HttpException(404); | |
} | |
} | |
else | |
{ | |
$record = new Commerce_VariantRecord(); | |
} | |
return $record; | |
} | |
/** | |
* @param $record | |
* @param Commerce_VariantModel $model | |
*/ | |
private function _populateVariantRecord($record, Commerce_VariantModel $model) | |
{ | |
$record->productId = $model->productId; | |
$record->sku = $model->sku; | |
$record->price = $model->price; | |
$record->width = (float) $model->width; | |
$record->height = (float) $model->height; | |
$record->length = (float) $model->length; | |
$record->weight = (float) $model->weight; | |
$record->minQty = $model->minQty; | |
$record->maxQty = $model->maxQty; | |
$record->stock = $model->stock; | |
$record->isDefault = $model->isDefault; | |
$record->sortOrder = $model->sortOrder; | |
$record->unlimitedStock = $model->unlimitedStock; | |
if (!$model->getProduct()->getType()->hasDimensions) | |
{ | |
$record->width = $model->width = 0; | |
$record->height = $model->height = 0; | |
$record->length = $model->length = 0; | |
$record->weight = $model->weight = 0; | |
} | |
if ($model->unlimitedStock && $record->stock == "") | |
{ | |
$model->stock = 0; | |
$record->stock = 0; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment