Completed
Push — master ( ecaba6...f0c386 )
by Roman
13s
created

getVariationByAttributes()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 12
nc 3
nop 1
dl 0
loc 19
rs 9.4285
c 0
b 0
f 0
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
21
/**
22
 * Adds extra fields and relationships to Products for variations support.
23
 *
24
 * @package    silvershop
25
 * @subpackage variations
26
 */
27
class ProductVariationsExtension extends DataExtension
28
{
29
    private static $has_many = [
0 ignored issues
show
introduced by
The private property $has_many is not used, and could be removed.
Loading history...
30
        'Variations' => Variation::class,
31
    ];
32
33
    private static $many_many = [
0 ignored issues
show
introduced by
The private property $many_many is not used, and could be removed.
Loading history...
34
        'VariationAttributeTypes' => AttributeType::class,
35
    ];
36
37
    /**
38
     * Adds variations specific fields to the CMS.
39
     */
40
    public function updateCMSFields(FieldList $fields)
41
    {
42
        $fields->addFieldsToTab('Root.Variations', [
43
            ListboxField::create(
44
                'VariationAttributeTypes',
0 ignored issues
show
Bug introduced by
'VariationAttributeTypes' of type string is incompatible with the type array expected by parameter $args of SilverStripe\View\ViewableData::create(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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