ProductVariationsExtension   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 223
Duplicated Lines 0 %

Importance

Changes 3
Bugs 1 Features 1
Metric Value
eloc 122
c 3
b 1
f 1
dl 0
loc 223
rs 10
wmc 28

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getVariationByAttributes() 0 19 3
B generateVariationsFromAttributes() 0 36 6
A updateCMSFields() 0 35 2
A PriceRange() 0 31 5
A onAfterDelete() 0 19 6
A updateFormClass() 0 4 2
A possibleValuesForAttributeType() 0 25 4
1
<?php
2
3
namespace SilverShop\Extension;
4
5
use SilverShop\Forms\VariationForm;
6
use SilverShop\Model\Variation\AttributeType;
7
use SilverShop\Model\Variation\AttributeValue;
8
use SilverShop\Model\Variation\Variation;
9
use SilverShop\ORM\FieldType\ShopCurrency;
10
use SilverShop\Page\Product;
11
use SilverStripe\Forms\FieldList;
12
use SilverStripe\Forms\GridField\GridField;
13
use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor;
14
use SilverStripe\Forms\LabelField;
15
use SilverStripe\Forms\ListboxField;
16
use SilverStripe\ORM\DataExtension;
17
use SilverStripe\ORM\DataList;
18
use SilverStripe\Versioned\Versioned;
19
use SilverStripe\View\ArrayData;
20
use Symbiote\GridFieldExtensions\GridFieldOrderableRows;
21
22
/**
23
 * Adds extra fields and relationships to Products for variations support.
24
 *
25
 * @package    silvershop
26
 * @subpackage variations
27
 */
28
class ProductVariationsExtension extends DataExtension
29
{
30
    private static $has_many = [
0 ignored issues
show
introduced by
The private property $has_many is not used, and could be removed.
Loading history...
31
        'Variations' => Variation::class,
32
    ];
33
34
    private static $many_many = [
0 ignored issues
show
introduced by
The private property $many_many is not used, and could be removed.
Loading history...
35
        'VariationAttributeTypes' => AttributeType::class,
36
    ];
37
38
    /**
39
     * Adds variations specific fields to the CMS.
40
     */
41
    public function updateCMSFields(FieldList $fields)
42
    {
43
        $fields->addFieldsToTab('Root.Variations', [
44
            ListboxField::create(
45
                'VariationAttributeTypes',
46
                _t(__CLASS__ . '.Attributes', "Attributes"),
47
                AttributeType::get()->map('ID', 'Title')->toArray()
48
            )
49
                ->setDescription(_t(
50
                    __CLASS__ . '.AttributesDescription',
51
                    'These are fields to indicate the way(s) each variation varies. Once selected, they can be edited on each variation.'
52
                )),
53
            $variationsGridField = GridField::create(
54
                'Variations',
55
                _t(__CLASS__ . '.Variations', 'Variations'),
56
                $this->owner->Variations(),
57
                GridFieldConfig_RecordEditor::create(100)
58
            )
59
        ]);
60
        
61
        $variationsGridField->getConfig()->addComponent($sort = new GridFieldOrderableRows('Sort'));
62
63
        if ($this->owner->Variations()->exists()) {
64
            $fields->addFieldToTab(
65
                'Root.Pricing',
66
                LabelField::create(
67
                    'variationspriceinstructinos',
68
                    _t(
69
                        __CLASS__ . '.VariationsInfo',
70
                        'Price - Because you have one or more variations, the price can be set in the "Variations" tab.'
71
                    )
72
                )
73
            );
74
            $fields->removeFieldFromTab('Root.Pricing', 'BasePrice');
75
            $fields->removeFieldFromTab('Root.Main', 'InternalItemID');
76
        }
77
    }
78
79
    public function PriceRange()
80
    {
81
        $variations = $this->owner->Variations();
82
83
        if (!Product::config()->allow_zero_price) {
84
            $variations = $variations->filter('Price:GreaterThan', 0);
85
        }
86
87
        if (!$variations->exists() || !$variations->Count()) {
88
            return null;
89
        }
90
91
        $prices = $variations->map('ID', 'SellingPrice')->toArray();
92
        $pricedata = [
93
            'HasRange' => false,
94
            'Max' => ShopCurrency::create(),
95
            'Min' => ShopCurrency::create(),
96
            'Average' => ShopCurrency::create(),
97
        ];
98
        $count = count($prices);
99
        $sum = array_sum($prices);
100
        $maxprice = max($prices);
101
        $minprice = min($prices);
102
        $pricedata['HasRange'] = ($minprice != $maxprice);
103
        $pricedata['Max']->setValue($maxprice);
104
        $pricedata['Min']->setValue($minprice);
105
        if ($count > 0) {
106
            $pricedata['Average']->setValue($sum / $count);
107
        }
108
109
        return ArrayData::create($pricedata);
110
    }
111
112
    /**
113
     * Pass an array of attribute ids to query for the appropriate variation.
114
     *
115
     * @param array $attributes
116
     *
117
     * @return Variation|null
118
     */
119
    public function getVariationByAttributes(array $attributes)
120
    {
121
        if (!is_array($attributes)) {
0 ignored issues
show
introduced by
The condition is_array($attributes) is always true.
Loading history...
122
            return null;
123
        }
124
125
        $attrs = array_filter(array_values($attributes));
126
        $set = Variation::get()->filter('ProductID', $this->owner->ID);
127
128
        foreach ($attrs as $i => $valueid) {
129
            $alias = "A$i";
130
            $set = $set->innerJoin(
131
                'SilverShop_Variation_AttributeValues',
132
                "\"SilverShop_Variation\".\"ID\" = \"$alias\".\"SilverShop_VariationID\"",
133
                $alias
134
            )->where(["\"$alias\".\"SilverShop_AttributeValueID\" = ?" => $valueid]);
135
        }
136
137
        return $set->first();
138
    }
139
140
    /**
141
     * Generates variations based on selected attributes.
142
     *
143
     * @param  AttributeType $attributetype
144
     * @param  array         $values
145
     * @throws \SilverStripe\ORM\ValidationException
146
     */
147
    public function generateVariationsFromAttributes(AttributeType $attributetype, array $values)
148
    {
149
        //TODO: introduce transactions here, in case objects get half made etc
150
        //if product has variation attribute types
151
        if (!empty($values)) {
152
            //TODO: get values dataobject set
153
            $avalues = $attributetype->convertArrayToValues($values);
154
            $existingvariations = $this->owner->Variations();
155
            if ($existingvariations->exists()) {
156
                //delete old variation, and create new ones - to prevent modification of exising variations
157
                foreach ($existingvariations as $oldvariation) {
158
                    $oldvalues = $oldvariation->AttributeValues();
159
                    foreach ($avalues as $value) {
160
                        $newvariation = $oldvariation->duplicate();
161
                        $newvariation->InternalItemID = $this->owner->InternalItemID . '-' . $newvariation->ID;
162
                        $newvariation->AttributeValues()->addMany($oldvalues);
163
                        $newvariation->AttributeValues()->add($value);
164
                        $newvariation->write();
165
                        $existingvariations->add($newvariation);
166
                    }
167
                    $existingvariations->remove($oldvariation);
168
                    $oldvariation->AttributeValues()->removeAll();
169
                    $oldvariation->delete();
170
                    $oldvariation->destroy();
171
                    //TODO: check that old variations actually stick around, as they will be needed for past orders etc
172
                }
173
            } else {
174
                foreach ($avalues as $value) {
175
                    $variation = Variation::create();
176
                    $variation->ProductID = $this->owner->ID;
177
                    $variation->Price = $this->owner->BasePrice;
178
                    $variation->write();
179
                    $variation->InternalItemID = $this->owner->InternalItemID . '-' . $variation->ID;
180
                    $variation->AttributeValues()->add($value);
181
                    $variation->write();
182
                    $existingvariations->add($variation);
183
                }
184
            }
185
        }
186
    }
187
188
    /**
189
     * Get all the {@link ProductAttributeValue} for a given attribute type,
190
     * based on this product's variations.
191
     *
192
     * @return DataList
193
     */
194
    public function possibleValuesForAttributeType($type)
195
    {
196
        if (!is_numeric($type)) {
197
            $type = $type->ID;
198
        }
199
200
        if (!$type) {
201
            return null;
202
        }
203
204
        $list = AttributeValue::get()
205
            ->innerJoin(
206
                'SilverShop_Variation_AttributeValues',
207
                '"SilverShop_AttributeValue"."ID" = "SilverShop_Variation_AttributeValues"."SilverShop_AttributeValueID"'
208
            )->innerJoin(
209
                'SilverShop_Variation',
210
                '"SilverShop_Variation_AttributeValues"."SilverShop_VariationID" = "SilverShop_Variation"."ID"'
211
            )->where(
212
                "TypeID = $type AND \"SilverShop_Variation\".\"ProductID\" = " . $this->owner->ID
213
            )->sort('"SilverShop_Variation"."Sort" ASC');
214
215
        if (!Product::config()->allow_zero_price) {
216
            $list = $list->where('"SilverShop_Variation"."Price" > 0');
217
        }
218
        return $list;
219
    }
220
221
    /**
222
     * Make sure variations are deleted with product.
223
     */
224
    public function onAfterDelete()
225
    {
226
        $remove = false;
227
        // if a record is staged or live, leave it's variations alone.
228
        if (!property_exists($this, 'owner')) {
229
            $remove = true;
230
        } else {
231
            $staged = Versioned::get_by_stage($this->owner->ClassName, 'Stage')
232
                ->byID($this->owner->ID);
233
            $live = Versioned::get_by_stage($this->owner->ClassName, 'Live')
234
                ->byID($this->owner->ID);
235
            if (!$staged && !$live) {
236
                $remove = true;
237
            }
238
        }
239
        if ($remove) {
240
            foreach ($this->owner->Variations() as $variation) {
241
                $variation->delete();
242
                $variation->destroy();
243
            }
244
        }
245
    }
246
247
    public function updateFormClass(&$formClass)
248
    {
249
        if ($this->owner->Variations()->exists()) {
250
            $formClass = VariationForm::class;
251
        }
252
    }
253
}
254