-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
47aefa5
commit 301fbf9
Showing
13 changed files
with
766 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Simple product options for Silvershop | ||
User can create a simple options called something like "Size", and add values to it like XL, L, S, etc. These can then be chosen in the frontend, on products, and will be shown in cart etc. | ||
|
||
Each value can also have a price defined which gets added on top of the product price. | ||
|
||
|
||
This module was inspired by Magento's product options functionality | ||
|
||
### Why not variations ### | ||
Its much easier to create these instead of variations (UI wise). Variations have their place, but might be too complex for **some** clients. | ||
|
||
### IMPORTANT ### | ||
This form of variations on a product does require the shop owner to handle any combination of options that actually dont exist in real life. | ||
|
||
## ANOTHER IMPORTANT ### | ||
This modul was created a bit fast and might not be completely stable. Use at own risk pls |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
--- | ||
Name: silvershop-simple-options | ||
After: | ||
- silvershop/* | ||
--- | ||
Product: | ||
order_item: SimpleOptionOrderItem | ||
extensions: | ||
- "Silvershop\SimpleOptions\Extensions\ProductOptionsExtension" | ||
OrderItem: | ||
extensions: | ||
- "Silvershop\SimpleOptions\Extensions\OrderItemExtension" | ||
Order: | ||
extensions: | ||
- "Silvershop\SimpleOptions\Extensions\OrderExtension" | ||
|
||
|
||
### INJECTOR | ||
Injector: | ||
ShoppingCart: | ||
class: "Silvershop\SimpleOptions\ShoppingCart" |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
<?php | ||
|
||
namespace Silvershop\SimpleOptions; | ||
|
||
use Silvershop\SimpleOptions\Extensions\OrderExtension; | ||
|
||
class ShoppingCart extends \ShoppingCart | ||
{ | ||
/** | ||
* Finds an existing order item. | ||
* | ||
* This method is originally responsible for ensuring unique order items are created correctly | ||
* | ||
* However, if we want to check for simple options, we have to do some extra checks | ||
* | ||
* | ||
* @param \Buyable $buyable | ||
* @param array $customfilter | ||
* | ||
* @return \OrderItem | bool (the item requested, or false) | ||
*/ | ||
public function get(\Buyable $buyable, $customfilter = array()) | ||
{ | ||
$order = $this->current(); | ||
if (!$buyable || !$order) { | ||
return false; | ||
} | ||
|
||
$buyable = $this->getCorrectBuyable($buyable); | ||
|
||
$filter = array( | ||
'OrderID' => $order->ID, | ||
); | ||
$itemclass = \Config::inst()->get(get_class($buyable), 'order_item'); | ||
$relationship = \Config::inst()->get($itemclass, 'buyable_relationship'); | ||
$filter[$relationship . "ID"] = $buyable->ID; | ||
$required = array('Order', $relationship); | ||
if (is_array($itemclass::config()->required_fields)) { | ||
$required = array_merge($required, $itemclass::config()->required_fields); | ||
} | ||
$query = new \MatchObjectFilter($itemclass, array_merge($customfilter, $filter), $required); | ||
|
||
// ==== From here on the code might be different from default class | ||
|
||
$items = $itemclass::get()->where($query->getFilter()); | ||
|
||
$item = $items->first(); | ||
|
||
if (!$item) { | ||
return $this->error(_t("ShoppingCart.ItemNotFound", "Item not found.")); | ||
} | ||
|
||
// do check if buyable even has module or options, else no point in doing more code | ||
if (!$buyable->hasExtension("Silvershop\SimpleOptions\Extensions\ProductOptionsExtension") || !$buyable->ProductOptions()->exists()) { | ||
return $item; | ||
} | ||
|
||
// might be bool on cart creation.. | ||
if (!$item instanceof \OrderItem) { | ||
return $item; | ||
} | ||
|
||
return $this->handleItemsOptionsData($items, $customfilter); | ||
} | ||
|
||
protected function handleItemsOptionsData($items, $customfilter) | ||
{ | ||
$item = $items->first(); | ||
|
||
$submittedOptions = array_column(OrderExtension::filterDataForSimpleOptions($customfilter), "ValueID"); | ||
sort($submittedOptions); | ||
|
||
// default behavior just selects items with correct ProductID, there might be multiple | ||
// so check if an item exists with the same options as submitted options | ||
// If so, that item should be selected | ||
foreach($items as $orderitem){ | ||
$values = $orderitem->OrderOptionDataItems()->column("SimpleProductOptionValueID"); | ||
sort($values); | ||
// compare options | ||
if($submittedOptions == $values){ | ||
$item = $orderitem; | ||
break; | ||
} | ||
} | ||
|
||
// if it has no options set, then whatever | ||
if (!$item->OrderOptionDataItems()->exists()) { | ||
return $item; | ||
} | ||
|
||
$existingValues = $item->OrderOptionDataItems()->column("SimpleProductOptionValueID"); | ||
|
||
sort($existingValues); | ||
|
||
// if the submitted options are the same as existing attached options, then just return the item | ||
// if submitted options are different, please create a new order item | ||
if ($submittedOptions != $existingValues) { | ||
return $this->error(_t("ShoppingCart.ItemNotFound", "Item not found.")); | ||
} | ||
|
||
return $item; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
<?php | ||
/** | ||
* Created by PhpStorm. | ||
* User: sanderhagenaars | ||
* Date: 07/11/2017 | ||
* Time: 14.30 | ||
*/ | ||
|
||
namespace Silvershop\SimpleOptions\Extensions; | ||
|
||
|
||
class OrderExtension extends \DataExtension | ||
{ | ||
public function afterAdd($item, $buyable, $quantity, $filter) | ||
{ | ||
$items = $this->createOrderOptionDataItems($filter, $item); | ||
} | ||
|
||
protected function createOrderOptionDataItems($data, \OrderItem $item) | ||
{ | ||
$items = \ArrayList::create(); | ||
|
||
// figure out which options were submitted and values chosen | ||
$optionDataList = self::filterDataForSimpleOptions($data); | ||
if (empty($optionDataList)) { | ||
return $items; | ||
} | ||
|
||
// remove any existing options | ||
if($item->OrderOptionDataItems()){ | ||
$item->OrderOptionDataItems()->removeAll(); | ||
} | ||
foreach ($optionDataList as $arr) { | ||
$option = \DataObject::get_by_id("SimpleProductOption", $arr['OptionID']); | ||
$value = $option->Values()->filter(['ID' => $arr['ValueID']])->first(); | ||
|
||
$i = \OrderOptionDataItem::create([ | ||
'SimpleProductOptionID' => $option->ID, | ||
'SimpleProductOptionValueID' => $value->ID, | ||
'OptionTitle' => $option->Title, | ||
'ValueTitle' => $value->Title, | ||
'UnitPrice' => $value->Price | ||
]); | ||
$i->write(); | ||
|
||
$item->OrderOptionDataItems()->add($i); | ||
} | ||
|
||
// trigger recalc | ||
$item->forceChange(); | ||
$item->write(); | ||
|
||
return $items; | ||
} | ||
|
||
/** | ||
* Check associative array for any keys like "ProductOptions_{$ID}". | ||
* This $ID would be the ID of a SimpleProductOption and this key points to the ID of SimpleProductOptionValue | ||
* | ||
* method returns an array like | ||
* [ | ||
* "OptionID" => {$ID}, | ||
* "ValueID" => {$ID} | ||
* ] | ||
* | ||
* @param $data | ||
* @return array | ||
*/ | ||
public static function filterDataForSimpleOptions($data) | ||
{ | ||
$optionDataList = []; | ||
foreach ($data as $field => $val) { | ||
$parts = explode('_', $field); | ||
if ($parts[0] != 'ProductOptions') { | ||
continue; | ||
} | ||
if (isset($parts[1])) { | ||
$optionDataList[] = ["OptionID" => $parts[1], "ValueID" => $val]; | ||
} | ||
} | ||
|
||
return $optionDataList; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
/** | ||
* Created by PhpStorm. | ||
* User: sanderhagenaars | ||
* Date: 07/11/2017 | ||
* Time: 13.22 | ||
*/ | ||
|
||
namespace Silvershop\SimpleOptions\Extensions; | ||
|
||
|
||
class OrderItemExtension extends \DataExtension | ||
{ | ||
/** | ||
* List of one-to-many relationships. {@link DataObject::$has_many} | ||
* | ||
* @var array | ||
*/ | ||
private static $has_many = array( | ||
'OrderOptionDataItems' => 'OrderOptionDataItem' | ||
); | ||
|
||
public function updateUnitPrice(&$unitprice) | ||
{ | ||
if(!$this->owner->OrderOptionDataItems()->exists()){ | ||
return; | ||
} | ||
|
||
foreach($this->owner->OrderOptionDataItems() as $item){ | ||
$unitprice += $item->UnitPrice; | ||
} | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<?php | ||
/** | ||
* Created by PhpStorm. | ||
* User: sanderhagenaars | ||
* Date: 07/11/2017 | ||
* Time: 10.38 | ||
*/ | ||
|
||
namespace Silvershop\SimpleOptions\Extensions; | ||
|
||
|
||
class ProductOptionsExtension extends \DataExtension | ||
{ | ||
/** | ||
* List of one-to-many relationships. {@link DataObject::$has_many} | ||
* | ||
* @var array | ||
*/ | ||
private static $has_many = array( | ||
'ProductOptions' => 'SimpleProductOption' | ||
); | ||
|
||
public function updateCMSFields(\FieldList $fields) | ||
{ | ||
if($this->canUseSimpleOptions()){ | ||
$fields->addFieldToTab('Root.SimpleOptions', \LiteralField::create("vanotice", "<p style=\"color:red;\">You can not apply options when you've applied variations</p>")); | ||
} | ||
if(!$this->canUseSimpleOptions()){ | ||
$config = \GridFieldConfig_RecordEditor::create(10); | ||
$config->addComponent(new \GridFieldOrderableRows("Sort")); | ||
$gridfield = \GridField::create('ProductOptions', 'Simple Options', $this->owner->ProductOptions(), $config); | ||
$fields->addFieldToTab('Root.SimpleOptions', $gridfield); | ||
|
||
$fields->addFieldToTab('Root.Variations', \LiteralField::create("vanotice", "<p style=\"color:red;\">You can not apply variations when you've applied options</p>")); | ||
$fields->removeFieldFromTab('Root.Variations', "Variations"); | ||
$fields->removeFieldFromTab('Root.Variations', "VariationAttributeTypes"); | ||
} | ||
} | ||
|
||
/** | ||
* @return bool | ||
*/ | ||
public function canUseSimpleOptions() | ||
{ | ||
return $this->owner->hasExtension('ProductVariationsExtension') && $this->owner->Variations()->exists(); | ||
} | ||
|
||
public function contentcontrollerInit($controller) | ||
{ | ||
if ($this->owner->ProductOptions()->exists()) { | ||
$controller->formclass = 'SimpleOptionsForm'; | ||
} | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<?php | ||
|
||
/** | ||
* Created by PhpStorm. | ||
* User: sanderhagenaars | ||
* Date: 07/11/2017 | ||
* Time: 15.05 | ||
*/ | ||
class SimpleOptionsForm extends AddProductForm | ||
{ | ||
protected $requiredFields = []; | ||
|
||
public function __construct($controller, $name = "SimpleOptionsForm") | ||
{ | ||
parent::__construct($controller, $name); | ||
|
||
$this->extend('updateSimpleOptionsForm'); | ||
} | ||
|
||
protected function getFormFields($controller = null) | ||
{ | ||
$fields = parent::getFormFields($controller); | ||
|
||
if (!$controller) { | ||
return $fields; | ||
} | ||
|
||
$p = $this->getBuyable(); | ||
|
||
if (!$p || !$p->ProductOptions()->exists()) { | ||
return $fields; | ||
} | ||
|
||
// add simple options field | ||
foreach ($p->ProductOptions() as $option) { | ||
$fields->push($option->getFormField()); | ||
$this->requiredFields[] = "ProductOptions_".$option->ID; | ||
} | ||
|
||
return $fields; | ||
} | ||
|
||
public function getBuyable($data = null) | ||
{ | ||
if(!$this->controller){ | ||
$this->controller = Controller::curr(); // this somehow bugged out where no controller was found | ||
} | ||
if ($this->controller->dataRecord instanceof Buyable) { | ||
return $this->controller->dataRecord; | ||
} | ||
return DataObject::get_by_id('Product', (int)$this->request->postVar("BuyableID")); //TODO: get buyable | ||
} | ||
|
||
/** | ||
* @return Validator Validator for this form. | ||
*/ | ||
protected function getFormValidator() | ||
{ | ||
$validator = parent::getFormValidator(); | ||
|
||
$f = RequiredFields::create($this->requiredFields); | ||
|
||
return $validator->appendRequiredFields($f); | ||
} | ||
} |
Oops, something went wrong.