Passed
Pull Request — master (#99)
by Nic
03:02
created

AddToCartForm   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 393
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 159
c 3
b 0
f 0
dl 0
loc 393
rs 8.64
wmc 47

12 Methods

Rating   Name   Duplication   Size   Complexity  
A getProductActions() 0 16 3
A getProductVariations() 0 15 4
A getGeneratedValue() 0 13 2
A getProductOptionSet() 0 46 5
A setAvailability() 0 5 2
A getFoxyHelper() 0 7 2
B __construct() 0 29 7
A setFoxyHelper() 0 9 3
A setProduct() 0 12 3
A createVariationField() 0 31 4
C getProductFields() 0 110 11
A getProduct() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like AddToCartForm often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AddToCartForm, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Dynamic\Foxy\Form;
4
5
use Dynamic\Foxy\Extension\Shippable;
6
use Dynamic\Foxy\Model\FoxyHelper;
7
use Dynamic\Foxy\Model\OptionType;
8
use Dynamic\Foxy\Model\ProductOption;
9
use Dynamic\Foxy\Model\Variation;
10
use Dynamic\Foxy\Model\VariationType;
11
use Dynamic\Products\Page\Product;
0 ignored issues
show
Bug introduced by
The type Dynamic\Products\Page\Product was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use SilverStripe\CMS\Model\VirtualPage;
13
use SilverStripe\Forms\CompositeField;
14
use SilverStripe\Forms\DropdownField;
15
use SilverStripe\Forms\FieldList;
16
use SilverStripe\Forms\Form;
17
use SilverStripe\Forms\FormAction;
18
use SilverStripe\Forms\HeaderField;
19
use SilverStripe\Forms\HiddenField;
20
use SilverStripe\Forms\RequiredFields;
21
use SilverStripe\ORM\ArrayList;
22
use SilverStripe\ORM\DataList;
23
use SilverStripe\ORM\GroupedList;
24
use SilverStripe\ORM\HasManyList;
25
26
/**
27
 * Class AddToCartForm
28
 * @package Dynamic\Foxy\Form
29
 */
30
class AddToCartForm extends Form
31
{
32
    /**
33
     * @var
34
     */
35
    protected $helper;
36
37
    /**
38
     * @var
39
     */
40
    private $product;
41
42
    /**
43
     * @param $helper
44
     *
45
     * @return $this
46
     */
47
    public function setFoxyHelper($helper)
48
    {
49
        $helper = $helper === null ? FoxyHelper::create() : $helper;
50
        if ($helper instanceof FoxyHelper) {
51
            $this->helper = $helper;
52
53
            return $this;
54
        }
55
        throw new \InvalidArgumentException('$helper needs to be an instance of FoxyHelper.');
56
    }
57
58
    /**
59
     * @return FoxyHelper
60
     */
61
    public function getFoxyHelper()
62
    {
63
        if (!$this->helper) {
64
            $this->setFoxyHelper(FoxyHelper::create());
65
        }
66
67
        return $this->helper;
68
    }
69
70
    /**
71
     * @param $product
72
     *
73
     * @return $this
74
     */
75
    public function setProduct($product)
76
    {
77
        if ($product instanceof VirtualPage) {
78
            $product = $this->getFoxyHelper()->getProducts()->filter('ID', $product->CopyContentFromID)->first();
79
        }
80
81
        if ($product->isProduct()) {
82
            $this->product = $product;
83
84
            return $this;
85
        }
86
        throw new \InvalidArgumentException('$product needs to implement a Foxy DataExtension.');
87
    }
88
89
    /**
90
     * @return Product
91
     */
92
    public function getProduct()
93
    {
94
        return $this->product;
95
    }
96
97
    /**
98
     * AddToCartForm constructor.
99
     * @param $controller
100
     * @param $name
101
     * @param FieldList|null $fields
102
     * @param FieldList|null $actions
103
     * @param null $validator
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $validator is correct as it would always require null to be passed?
Loading history...
104
     * @param null $product
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $product is correct as it would always require null to be passed?
Loading history...
105
     * @param null $helper
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $helper is correct as it would always require null to be passed?
Loading history...
106
     * @throws \SilverStripe\ORM\ValidationException
107
     */
108
    public function __construct(
109
        $controller,
110
        $name,
111
        FieldList $fields = null,
112
        FieldList $actions = null,
113
        $validator = null,
114
        $product = null,
115
        $helper = null
116
    )
117
    {
118
        $this->setProduct($product);
119
        $this->setFoxyHelper($helper);
120
121
        $fields = ($fields != null && $fields->exists()) ?
122
            $this->getProductFields($fields) :
123
            $this->getProductFields(FieldList::create());
124
125
        $actions = ($actions != null && $actions->exists()) ?
126
            $this->getProductActions($actions) :
127
            $this->getProductActions(FieldList::create());
128
129
        $validator = (!empty($validator) || $validator != null) ? $validator : RequiredFields::create();
130
131
        parent::__construct($controller, $name, $fields, $actions, $validator);
132
133
        //have to call after parent::__construct()
134
        $this->setAttribute('action', FoxyHelper::FormActionURL());
135
        $this->disableSecurityToken();
136
        $this->setHTMLID($this->getTemplateHelper()->generateFormID($this) . "_{$product->ID}");
137
    }
138
139
    /**
140
     * @param FieldList $fields
141
     *
142
     * @return FieldList
143
     */
144
    protected function getProductFields(FieldList $fields)
145
    {
146
        $hiddenTitle = ($this->product->ReceiptTitle) ?
147
            htmlspecialchars($this->product->ReceiptTitle) :
148
            htmlspecialchars($this->product->Title);
149
        $code = $this->product->Code;
150
151
        if ($this->product->isAvailable()) {
152
            $fields->push(
153
                HiddenField::create('name')
154
                    ->setValue(
155
                        self::getGeneratedValue($code, 'name', $hiddenTitle, 'value')
156
                    )
157
            );
158
159
            $fields->push(
160
                HiddenField::create('category')
161
                    ->setValue(
162
                        self::getGeneratedValue($code, 'category', $this->product->FoxyCategory()->Code, 'value')
163
                    )
164
            );
165
166
            $fields->push(
167
                HiddenField::create('code')
168
                    ->setValue(
169
                        self::getGeneratedValue($code, 'code', $this->product->Code, 'value')
170
                    )
171
            );
172
173
            $fields->push(
174
                HiddenField::create('product_id')
175
                    ->setValue(
176
                        self::getGeneratedValue($code, 'product_id', $this->product->ID, 'value')
177
                    )
178
                    ->setName('h:product_id')
179
            );
180
181
            $fields->push(
182
                HiddenField::create('price')
183
                    ->setValue(
184
                        self::getGeneratedValue($code, 'price', $this->product->Price, 'value')
185
                    )
186
            );
187
188
            if ($this->product->hasMethod('AbsoluteLink')) {
189
                $fields->push(
190
                    HiddenField::create('url')
191
                        ->setValue(
192
                            self::getGeneratedValue($code, 'url', $this->product->AbsoluteLink(), 'value')
193
                        )
194
                );
195
            }
196
197
            if ($this->product->hasExtension(Shippable::class)) {
198
                if ($this->product->Weight > 0) {
199
                    $fields->push(
200
                        HiddenField::create('weight')
201
                            ->setValue(
202
                                self::getGeneratedValue($code, 'weight', $this->product->Weight, 'value')
203
                            )
204
                    );
205
                }
206
            }
207
208
            $image = null;
209
            if ($this->product->hasMethod('getImage')) {
210
                if ($this->product->getImage()) {
211
                    $image = $this->product->getImage()->CMSThumbnail()->absoluteURL;
212
                }
213
                if ($image) {
214
                    $fields->push(
215
                        HiddenField::create('image')
216
                            ->setValue(
217
                                self::getGeneratedValue($code, 'image', $image, 'value')
218
                            )
219
                    );
220
                }
221
            }
222
223
            if ($this->product->QuantityMax > 0) {
224
                $fields->push(
225
                    HiddenField::create('quantity_max')
226
                        ->setValue(self::getGeneratedValue($code, 'quantity_max', $this->product->QuantityMax, 'value'))
227
                );
228
            }
229
230
            //$optionsSet = $this->getProductOptionSet();
231
            //$fields->push($optionsSet);
232
            $fields->push($this->getProductVariations());
233
234
235
            if ($this->product->QuantityMax != 1) {
236
                $fields->push(QuantityField::create('x:visibleQuantity')->setTitle('Quantity')->setValue(1));
237
            }
238
            $fields->push(
239
                HiddenField::create('quantity')
240
                    ->setValue(
241
                        self::getGeneratedValue($code, 'quantity', 1, 'value')
242
                    )
243
            );
244
245
            $fields->push(
246
                HeaderField::create('submitPrice', '$' . $this->product->Price, 4)
247
                    ->addExtraClass('submit-price')
248
            );
249
        }
250
251
        $this->extend('updateProductFields', $fields);
252
253
        return $fields;
254
    }
255
256
    /**
257
     * @param FieldList $actions
258
     *
259
     * @return FieldList
260
     */
261
    protected function getProductActions(FieldList $actions)
262
    {
263
        if (!empty(trim($this->helper->getStoreCartURL())) && $this->product->isAvailable()) {
264
            $actions->push(
265
                FormAction::create(
266
                    'x:submit',
267
                    _t(__CLASS__ . '.AddToCart', 'Add to Cart')
268
                )
269
                    ->addExtraClass('fs-add-to-cart-button')
270
                    ->setName('x:submit')
271
            );
272
        }
273
274
        $this->extend('updateProductActions', $actions);
275
276
        return $actions;
277
    }
278
279
    /**
280
     * @param null $productCode
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $productCode is correct as it would always require null to be passed?
Loading history...
281
     * @param null $optionName
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $optionName is correct as it would always require null to be passed?
Loading history...
282
     * @param null $optionValue
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $optionValue is correct as it would always require null to be passed?
Loading history...
283
     * @param string $method
284
     * @param bool $output
285
     * @param bool $urlEncode
286
     *
287
     * @return null|string
288
     */
289
    // todo - Purchasable Extension or AddToCartForm? protected in Form
290
    public static function getGeneratedValue(
291
        $productCode = null,
292
        $optionName = null,
293
        $optionValue = null,
294
        $method = 'name',
295
        $output = false,
296
        $urlEncode = false
297
    )
298
    {
299
        $optionName = ($optionName !== null) ? preg_replace('/\s/', '_', $optionName) : $optionName;
0 ignored issues
show
introduced by
The condition $optionName !== null is always false.
Loading history...
300
        $helper = FoxyHelper::create();
301
302
        return $helper::fc_hash_value($productCode, $optionName, $optionValue, $method, $output, $urlEncode);
303
    }
304
305
    /**
306
     * @return CompositeField
307
     */
308
    protected function getProductOptionSet()
309
    {
310
        $options = $this->product->Options()->sort('SortOrder');
311
        $groupedOptions = new GroupedList($options);
312
        $groupedBy = $groupedOptions->groupBy('Type');
313
314
        /** @var CompositeField $optionsSet */
315
        $optionsSet = CompositeField::create();
316
317
        /** @var DataList $set */
318
        foreach ($groupedBy as $id => $set) {
319
            $group = OptionType::get()->byID($id);
320
            $title = $group->Title;
321
            $fieldName = preg_replace('/\s/', '_', $title);
322
            $disabled = [];
323
            $fullOptions = [];
324
325
            foreach ($set as $item) {
326
                $item = $this->setAvailability($item);
327
                $name = self::getGeneratedValue(
328
                    $this->product->Code,
329
                    $group->Title,
330
                    $item->getGeneratedValue(),
331
                    'value'
332
                );
333
334
                $fullOptions[$name] = $item->getGeneratedTitle();
335
                if (!$item->Availability) {
0 ignored issues
show
Bug Best Practice introduced by
The property Availability does not exist on Dynamic\Foxy\Model\ProductOption. Since you implemented __get, consider adding a @property annotation.
Loading history...
336
                    array_push($disabled, $name);
337
                }
338
            }
339
340
            $optionsSet->push(
341
                $dropdown = DropdownField::create($fieldName, $title, $fullOptions)->setTitle($title)
342
            );
343
344
            if (!empty($disabled)) {
345
                $dropdown->setDisabledItems($disabled);
346
            }
347
348
            $dropdown->addExtraClass("product-options");
349
        }
350
351
        $optionsSet->addExtraClass('foxycartOptionsContainer');
352
353
        return $optionsSet;
354
    }
355
356
    /**
357
     * @return CompositeField
358
     */
359
    protected function getProductVariations()
360
    {
361
        $types = VariationType::get();
362
363
        $variationsField = CompositeField::create();
364
365
        foreach ($types as $type) {
366
            if (($variations = $type->Variations()->filter('ProductID', $this->product->ID)) && $variations->count()) {
367
                $variationsField->push($this->createVariationField($type, $variations));
368
            }
369
        }
370
371
        $variationsField->addExtraClass('foxycartOptionsContainer');
372
373
        return $variationsField;
374
    }
375
376
    /**
377
     * @param VariationType $type
378
     * @param HasManyList $variations
379
     * @return DropdownField
380
     */
381
    protected function createVariationField(VariationType $type, HasManyList $variations)
382
    {
383
        $disabled = [];
384
        $list = [];
385
        $variationField = DropdownField::create(preg_replace('/\s/', '_', $type->Title));
386
387
        /** @var Variation $variation */
388
        foreach ($variations as $variation) {
389
            $name = self::getGeneratedValue(
390
                $this->product->Code,
391
                $type->Title,
392
                $variation->getGeneratedValue(),
393
                'value'
394
            );
395
396
            $list[$name] = $variation->getGeneratedTitle();
397
398
            if (!$variation->getAvailability()) {
399
                array_push($disabled, $name);
400
            }
401
        }
402
403
        $variationField->setSource($list)
404
            ->setTitle($type->Title)
405
            ->addExtraClass("product-options");
406
407
        if (!empty($disabled)) {
408
            $variationField->setDisabledItems($disabled);
409
        }
410
411
        return $variationField;
412
    }
413
414
    /**
415
     * @param ProductOption $option
416
     * @return ProductOption
417
     */
418
    protected function setAvailability(ProductOption $option)
419
    {
420
        $option->Available = ($option->getAvailability()) ? true : false;
0 ignored issues
show
Bug introduced by
The property Available is declared read-only in Dynamic\Foxy\Model\ProductOption.
Loading history...
421
422
        return $option;
423
    }
424
}
425