Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasbnielsen authored Nov 9, 2017
1 parent 47aefa5 commit 301fbf9
Show file tree
Hide file tree
Showing 13 changed files with 766 additions and 0 deletions.
16 changes: 16 additions & 0 deletions README.MD
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
21 changes: 21 additions & 0 deletions _config/config.yml
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"
103 changes: 103 additions & 0 deletions code/ShoppingCart.php
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;
}
}
84 changes: 84 additions & 0 deletions code/extensions/OrderExtension.php
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;
}
}
33 changes: 33 additions & 0 deletions code/extensions/OrderItemExtension.php
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;
}
}
}
54 changes: 54 additions & 0 deletions code/extensions/ProductOptionsExtension.php
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';
}
}
}
65 changes: 65 additions & 0 deletions code/forms/SimpleOptionsForm.php
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);
}
}
Loading

0 comments on commit 301fbf9

Please sign in to comment.