AddToCartForm::getProductVariations()   B
last analyzed

Complexity

Conditions 7
Paths 3

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
c 0
b 0
f 0
dl 0
loc 20
rs 8.8333
cc 7
nc 3
nop 0
1
<?php
2
3
namespace Dynamic\Foxy\Form;
4
5
use Dynamic\Foxy\Model\FoxyHelper;
6
use Dynamic\Foxy\Model\Variation;
7
use Dynamic\Foxy\Model\VariationType;
8
use Dynamic\Foxy\Page\Product;
9
use Dynamic\Foxy\Page\ShippableProduct;
10
use SilverStripe\CMS\Model\VirtualPage;
11
use SilverStripe\Forms\CompositeField;
12
use SilverStripe\Forms\DropdownField;
13
use SilverStripe\Forms\FieldList;
14
use SilverStripe\Forms\Form;
15
use SilverStripe\Forms\FormAction;
16
use SilverStripe\Forms\FormField;
17
use SilverStripe\Forms\HeaderField;
18
use SilverStripe\Forms\HiddenField;
19
use SilverStripe\Forms\RequiredFields;
20
use SilverStripe\ORM\HasManyList;
21
use SilverStripe\ORM\ValidationException;
22
23
/**
24
 * Class AddToCartForm
25
 * @package Dynamic\Foxy\Form
26
 */
27
class AddToCartForm extends Form
28
{
29
    /**
30
     * @var FoxyHelper
31
     */
32
    protected FoxyHelper $helper;
33
34
    /**
35
     * @var Product
36
     */
37
    private Product $product;
38
39
    /**
40
     * @var string If there are no variation types defined this value will be used in place of type title
41
     */
42
    private static $default_options_title = 'Options';
0 ignored issues
show
introduced by
The private property $default_options_title is not used, and could be removed.
Loading history...
43
44
    /**
45
     * @param $helper
46
     *
47
     * @return $this
48
     */
49
    public function setFoxyHelper($helper): self
50
    {
51
        $helper = $helper === null ? FoxyHelper::create() : $helper;
52
        if ($helper instanceof FoxyHelper) {
53
            $this->helper = $helper;
54
55
            return $this;
56
        }
57
        throw new \InvalidArgumentException('$helper needs to be an instance of FoxyHelper.');
58
    }
59
60
    /**
61
     * @return FoxyHelper
62
     */
63
    public function getFoxyHelper(): FoxyHelper
64
    {
65
        if (!$this->helper) {
66
            $this->setFoxyHelper(FoxyHelper::create());
67
        }
68
69
        return $this->helper;
70
    }
71
72
    /**
73
     * @param $product
74
     *
75
     * @return $this
76
     */
77
    public function setProduct($product): self
78
    {
79
        if ($product instanceof VirtualPage) {
80
            if (!$product = Product::get_by_id(Product::class, $product->CopyContentFromID)) {
81
                throw new \InvalidArgumentException(sprintf('$product needs to be a descendant of %s, or a Virtual Page copied from a %s descendant.', Product::class, Product::class));
82
            }
83
        }
84
85
        $this->product = $product;
86
87
        return $this;
88
    }
89
90
    /**
91
     * @return Product
92
     */
93
    public function getProduct(): Product
94
    {
95
        return $this->product;
96
    }
97
98
    /**
99
     * AddToCartForm constructor.
100
     * @param $controller
101
     * @param $name
102
     * @param FieldList|null $fields
103
     * @param FieldList|null $actions
104
     * @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...
105
     * @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...
106
     * @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...
107
     * @throws ValidationException
108
     */
109
    public function __construct(
110
        $controller,
111
        $name,
112
        FieldList $fields = null,
113
        FieldList $actions = null,
114
        $validator = null,
115
        $product = null,
116
        $helper = null
117
    )
118
    {
119
        $this->setProduct($product);
120
        $this->setFoxyHelper($helper);
121
122
        $fields = ($fields != null && $fields->exists()) ?
123
            $this->getProductFields($fields) :
124
            $this->getProductFields(FieldList::create());
125
126
        $actions = ($actions != null && $actions->exists()) ?
127
            $this->getProductActions($actions) :
128
            $this->getProductActions(FieldList::create());
129
130
        $validator = (!empty($validator) || $validator != null) ? $validator : RequiredFields::create();
131
132
        parent::__construct($controller, $name, $fields, $actions, $validator);
133
134
        //have to call after parent::__construct()
135
        $this->setAttribute('action', FoxyHelper::FormActionURL());
136
        $this->disableSecurityToken();
137
        $this->setHTMLID($this->getTemplateHelper()->generateFormID($this) . "_{$product->ID}");
138
    }
139
140
    /**
141
     * @param FieldList $fields
142
     *
143
     * @return FieldList
144
     */
145
    protected function getProductFields(FieldList $fields): FieldList
146
    {
147
        $hiddenTitle = ($this->product->ReceiptTitle) ?
148
            htmlspecialchars($this->product->ReceiptTitle) :
149
            htmlspecialchars($this->product->Title);
150
        $code = $this->product->Code;
151
152
        if ($this->product->getIsAvailable()) {
153
            $fields->push(
154
                HiddenField::create('name')
155
                    ->setValue(
156
                        self::getGeneratedValue($code, 'name', $hiddenTitle, 'value')
157
                    )
158
            );
159
160
            $fields->push(
161
                HiddenField::create('category')
162
                    ->setValue(
163
                        self::getGeneratedValue($code, 'category', $this->product->FoxyCategory()->Code, 'value')
164
                    )
165
            );
166
167
            $fields->push(
168
                HiddenField::create('code')
169
                    ->setValue(
170
                        self::getGeneratedValue($code, 'code', $this->product->Code, 'value')
171
                    )
172
            );
173
174
            $fields->push(
175
                HiddenField::create('product_id')
176
                    ->setValue(
177
                        self::getGeneratedValue($code, 'product_id', $this->product->ID, 'value')
178
                    )
179
                    ->setName('h:product_id')
180
            );
181
182
            $fields->push(
183
                HiddenField::create('price')
184
                    ->setValue(
185
                        self::getGeneratedValue($code, 'price', $this->product->Price, 'value')
186
                    )
187
            );
188
189
            if ($this->product->hasMethod('AbsoluteLink')) {
190
                $fields->push(
191
                    HiddenField::create('url')
192
                        ->setValue(
193
                            self::getGeneratedValue($code, 'url', $this->product->AbsoluteLink(), 'value')
194
                        )
195
                );
196
            }
197
198
            if ($this->product instanceof ShippableProduct) {
199
                if ($this->product->Weight > 0) {
0 ignored issues
show
Bug Best Practice introduced by
The property Weight does not exist on Dynamic\Foxy\Page\Product. Since you implemented __get, consider adding a @property annotation.
Loading history...
200
                    $fields->push(
201
                        HiddenField::create('weight')
202
                            ->setValue(
203
                                self::getGeneratedValue($code, 'weight', $this->product->Weight, 'value')
204
                            )
205
                    );
206
                }
207
            }
208
209
            $image = null;
210
            if ($this->product->hasMethod('getImage')) {
211
                if ($this->product->getImage()) {
0 ignored issues
show
Bug introduced by
The method getImage() does not exist on Dynamic\Foxy\Page\Product. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

211
                if ($this->product->/** @scrutinizer ignore-call */ getImage()) {
Loading history...
212
                    $image = $this->product->getImage()->CMSThumbnail()->absoluteURL;
213
                }
214
                if ($image) {
215
                    $fields->push(
216
                        HiddenField::create('image')
217
                            ->setValue(
218
                                self::getGeneratedValue($code, 'image', $image, 'value')
219
                            )
220
                    );
221
                }
222
            }
223
224
            if ($this->product->QuantityMax > 0) {
225
                $fields->push(
226
                    HiddenField::create('quantity_max')
227
                        ->setValue(self::getGeneratedValue($code, 'quantity_max', $this->product->QuantityMax, 'value'))
228
                );
229
            }
230
231
            if ($variationsField = $this->getProductVariations()) {
232
                $fields->push($variationsField);
0 ignored issues
show
Bug introduced by
It seems like $variationsField can also be of type true; however, parameter $item of SilverStripe\Forms\FieldList::push() does only seem to accept SilverStripe\Forms\FormField, maybe add an additional type check? ( Ignorable by Annotation )

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

232
                $fields->push(/** @scrutinizer ignore-type */ $variationsField);
Loading history...
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): FieldList
262
    {
263
        if (!empty(trim($this->helper->getStoreCartURL())) && $this->product->getIsAvailable()) {
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
    public static function getGeneratedValue(
290
        $productCode = null,
291
        $optionName = null,
292
        $optionValue = null,
293
        $method = 'name',
294
        $output = false,
295
        $urlEncode = false
296
    ): string
297
    {
298
        $optionName = ($optionName !== null) ? preg_replace('/\s/', '_', $optionName) : $optionName;
0 ignored issues
show
introduced by
The condition $optionName !== null is always false.
Loading history...
299
        $helper = FoxyHelper::create();
300
301
        return $helper::fc_hash_value($productCode, $optionName, $optionValue, $method, $output, $urlEncode);
302
    }
303
304
    /**
305
     * @return CompositeField|FormField|DropdownField|bool
306
     */
307
    protected function getProductVariations()
308
    {
309
        // we have 1 variant (the default) so we don't need to do anything
310
        if ($this->getProduct()->Variations()->count() == 1) {
311
            return false;
312
        }
313
314
        if (($types = VariationType::get()) && $types->count()) {
315
            $variationsField = CompositeField::create();
316
317
            foreach ($types as $type) {
318
                if (($variations = $type->Variations()->filter('ProductID', $this->product->ID)) && $variations->count()) {
319
                    $variationsField->push($this->createVariationField($variations, $type));
320
                }
321
            }
322
        } else {
323
            $variationsField = $this->createVariationField($this->getProduct()->Variations());
324
        }
325
326
        return $variationsField;
327
    }
328
329
    /**
330
     * @param HasManyList $variations
331
     * @param VariationType|null $type
332
     * @return DropdownField
333
     */
334
    protected function createVariationField(HasManyList $variations, VariationType $type = null): DropdownField
335
    {
336
        $disabled = [];
337
        $list = [];
338
339
        $title = $type === null
340
            ? $this->config()->get('default_options_title')
341
            : $type->Title;
342
343
        $variationField = DropdownField::create(preg_replace('/\s/', '_', $title));
344
345
        /** @var Variation $variation */
346
        foreach ($variations as $variation) {
347
            $name = self::getGeneratedValue(
348
                $this->product->Code,
349
                $title,
0 ignored issues
show
Bug introduced by
It seems like $title can also be of type string; however, parameter $optionName of Dynamic\Foxy\Form\AddToC...rm::getGeneratedValue() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

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

349
                /** @scrutinizer ignore-type */ $title,
Loading history...
350
                $variation->getGeneratedValue(),
351
                'value'
352
            );
353
354
            $list[$name] = $variation->getGeneratedTitle();
355
356
            if (!$variation->getIsAvailable()) {
357
                array_push($disabled, $name);
358
            }
359
        }
360
361
        $variationField->setSource($list)
362
            ->setTitle($title)
363
            ->addExtraClass("product-options");
364
365
        if (!empty($disabled)) {
366
            $variationField->setDisabledItems($disabled);
367
        }
368
369
        return $variationField;
370
    }
371
}
372