Passed
Push — master ( f6b8eb...4dcbe5 )
by Jason
02:36
created

Variation::validate()   A

Complexity

Conditions 6
Paths 8

Size

Total Lines 22
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 9
c 1
b 0
f 0
nc 8
nop 0
dl 0
loc 22
rs 9.2222
1
<?php
2
3
namespace Dynamic\Foxy\Model;
4
5
use Bummzack\SortableFile\Forms\SortableUploadField;
6
use SilverStripe\Assets\File;
7
use SilverStripe\Dev\Deprecation;
8
use SilverStripe\Forms\CheckboxField;
9
use SilverStripe\Forms\CurrencyField;
10
use SilverStripe\Forms\DropdownField;
11
use SilverStripe\Forms\FieldGroup;
12
use SilverStripe\Forms\FieldList;
13
use SilverStripe\Forms\NumericField;
14
use SilverStripe\Forms\ReadonlyField;
15
use SilverStripe\Forms\TextField;
16
use SilverStripe\ORM\DataObject;
17
use SilverStripe\ORM\ManyManyList;
18
use SilverStripe\ORM\ValidationResult;
19
use SilverStripe\Security\Permission;
20
use SilverStripe\Security\Security;
21
22
/**
23
 * Class Variation
24
 * @package Dynamic\Foxy\Model
25
 *
26
 * @property string $Title
27
 * @property bool $UseProductContent
28
 * @property string Content
29
 * @property float $WeightModifier
30
 * @property string $CodeModifier
31
 * @property float $PriceModifier
32
 * @property string $WeightModifierAction
33
 * @property string $CodeModifierAction
34
 * @property string $PriceModifierAction
35
 * @property bool $Available
36
 * @property int $Type
37
 * @property string $OptionModifierKey
38
 * @property int $SortOrder
39
 * @property float $FinalPrice
40
 * @property float $FinalWeight
41
 * @property string $FinalCode
42
 * @property int $VariationTypeID
43
 *
44
 * @method VariationType VariationType()
45
 *
46
 * @method ManyManyList Images()
47
 */
48
class Variation extends DataObject
49
{
50
    /**
51
     * @var string
52
     */
53
    private static $table_name = 'Variation';
54
55
    /**
56
     * @var string
57
     */
58
    private static $singular_name = 'Variation';
59
60
    /**
61
     * @var string
62
     */
63
    private static $plural_name = 'Variations';
64
65
    /**
66
     * @var string[]
67
     */
68
    private static $db = [
69
        'Title' => 'Varchar(255)',
70
        'UseProductContent' => 'Boolean',
71
        'Content' => 'HTMLText',
72
        'WeightModifier' => 'Decimal(9,3)',
73
        'CodeModifier' => 'Text',
74
        'PriceModifier' => 'Currency',
75
        'WeightModifierAction' => "Enum('Add,Subtract,Set', null)",
76
        'CodeModifierAction' => "Enum('Add,Subtract,Set', null)",
77
        'PriceModifierAction' => "Enum('Add,Subtract,Set', null)",
78
        'Available' => 'Boolean',
79
        'Type' => 'Int',
80
        'OptionModifierKey' => 'Varchar(255)',
81
        'SortOrder' => 'Int',
82
        'FinalPrice' => 'Currency',
83
        'FinalWeight' => 'Decimal(9,3)',
84
        'FinalCode' => 'Varchar(255)',
85
    ];
86
87
    /**
88
     * @var string[]
89
     */
90
    private static $indexes = [
91
        'FinalPrice' => true,
92
        'FinalWeight' => true,
93
        'FinalCode' => true,
94
    ];
95
96
    /**
97
     * @var string[]
98
     */
99
    private static $has_one = [
100
        'VariationType' => VariationType::class,
101
    ];
102
103
    /**
104
     * @var array
105
     */
106
    private static $many_many = [
107
        'Images' => File::class,
108
    ];
109
110
    /**
111
     * @var \string[][]
112
     */
113
    private static $many_many_extraFields = [
114
        'Images' => [
115
            'SortOrder' => 'Int',
116
        ],
117
    ];
118
119
    /**
120
     * @var string[]
121
     */
122
    private static $owns = [
123
        'Images',
124
    ];
125
126
    /**
127
     * @var string[]
128
     */
129
    private static $default_sort = 'SortOrder';
130
131
    /**
132
     * The relation name was established before requests for videos.
133
     * The relation has subsequently been updated from Image::class to File::class
134
     * to allow for additional file types such as mp4
135
     *
136
     * @var array
137
     */
138
    private static $allowed_images_extensions = [
139
        'gif',
140
        'jpeg',
141
        'jpg',
142
        'png',
143
        'bmp',
144
        'ico',
145
        'mp4',
146
    ];
147
148
    /**
149
     * @return FieldList
150
     */
151
    public function getCMSFields()
152
    {
153
        $this->beforeUpdateCMSFields(function (FieldList $fields) {
154
            $fields->removeByName([
155
                'Images',
156
                'WeightModifier',
157
                'CodeModifier',
158
                'PriceModifier',
159
                'WeightModifierAction',
160
                'CodeModifierAction',
161
                'PriceModifierAction',
162
                'Available',
163
                'Type',
164
                'OptionModifierKey',
165
                'SortOrder',
166
                'ProductID',
167
                'FinalPrice',
168
                'FinalWeight',
169
                'FinalCode',
170
            ]);
171
172
            $fields->insertBefore(
173
                'Content',
174
                CheckboxField::create('Available')
175
                    ->setTitle('Available for purchase')
176
            );
177
178
            $fields->insertBefore(
179
                'Content',
180
                $fields->dataFieldByName('VariationTypeID')
181
            );
182
183
            $fields->insertBefore(
184
                'Content',
185
                $fields->dataFieldByName('UseProductContent')
186
            );
187
188
            $content = $fields->dataFieldByName('Content');
189
190
            $content->hideUnless('UseProductContent')->isNotChecked()->end();
191
192
            if ($this->exists()) {
193
                $fields->addFieldToTab(
194
                    'Root.ProductModifications',
195
                    ReadonlyField::create('OptionModifierKey')
196
                        ->setTitle(_t('Variation.ModifierKey', 'Modifier Key'))
197
                );
198
            }
199
200
            if ($this->Product()->hasDatabaseField('Weight')) {
0 ignored issues
show
Bug introduced by
The method Product() does not exist on Dynamic\Foxy\Model\Variation. 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

200
            if ($this->/** @scrutinizer ignore-call */ Product()->hasDatabaseField('Weight')) {
Loading history...
201
                $fields->addFieldtoTab(
202
                    'Root.ProductModifications',
203
                    FieldGroup::create(
204
                        DropdownField::create(
205
                            'WeightModifierAction',
206
                            _t('Variation.WeightModifierAction', 'Weight Modification Type'),
207
                            [
208
                                'Add' => _t(
209
                                    'Variation.WeightAdd',
210
                                    'Add to Base Weight',
211
                                    'Add to weight'
212
                                ),
213
                                'Subtract' => _t(
214
                                    'Variation.WeightSubtract',
215
                                    'Subtract from Base Weight',
216
                                    'Subtract from weight'
217
                                ),
218
                                'Set' => _t('Variation.WeightSet', 'Set as a new Weight'),
219
                            ]
220
                        )
221
                            ->setEmptyString('')
222
                            ->setDescription(_t(
223
                                'Variation.WeightDescription',
224
                                'Does weight modify or replace base weight?'
225
                            )),
226
                        NumericField::create("WeightModifier")
227
                            ->setTitle(_t('Variation.WeightModifier', 'Weight'))
228
                            ->setScale(3)
229
                            ->setDescription(_t(
230
                                'Variation.WeightDescription',
231
                                'Only supports up to 3 decimal places'
232
                            ))->displayIf('WeightModifierAction')->isNotEmpty()->end(),
233
                        NumericField::create('FinalWeight')
234
                            ->setTitle('Final Modified Weight')
235
                            ->setDescription("Product's weight is {$this->Product()->Weight}")
236
                            ->performDisabledTransformation()
237
                    )->setTitle('Weight Modification')
238
                );
239
            }
240
241
            $fields->addFieldsToTab(
242
                'Root.ProductModifications',
243
                [
244
                    // Price Modifier Fields
245
                    //HeaderField::create('PriceHD', _t('Variation.PriceHD', 'Modify Price'), 4),
246
                    FieldGroup::create(
247
                        DropdownField::create(
248
                            'PriceModifierAction',
249
                            _t('Variation.PriceModifierAction', 'Price Modification Type'),
250
                            [
251
                                'Add' => _t(
252
                                    'Variation.PriceAdd',
253
                                    'Add to Base Price',
254
                                    'Add to price'
255
                                ),
256
                                'Subtract' => _t(
257
                                    'Variation.PriceSubtract',
258
                                    'Subtract from Base Price',
259
                                    'Subtract from price'
260
                                ),
261
                                'Set' => _t('Variation.PriceSet', 'Set as a new Price'),
262
                            ]
263
                        )
264
                            ->setEmptyString('')
265
                            ->setDescription(_t('Variation.PriceDescription', 'Does price modify or replace base price?')),
266
                        CurrencyField::create('PriceModifier')
267
                            ->setTitle(_t('Variation.PriceModifier', 'Price'))
268
                            ->displayIf('PriceModifierAction')->isNotEmpty()->end(),
269
                        CurrencyField::create('FinalPrice')
270
                            ->setTitle('Final Modified Price')
271
                            ->setDescription("Product's price is {$this->Product()->Price}")
272
                            ->performDisabledTransformation()
273
                    )->setTitle('Price Modifications'),
274
275
                    // Code Modifier Fields
276
                    //HeaderField::create('CodeHD', _t('Variation.CodeHD', 'Modify Code'), 4),
277
                    FieldGroup::create(
278
                        DropdownField::create(
279
                            'CodeModifierAction',
280
                            _t('Variation.CodeModifierAction', 'Code Modification Type'),
281
                            [
282
                                'Add' => _t(
283
                                    'Variation.CodeAdd',
284
                                    'Add to Base Code',
285
                                    'Add to code'
286
                                ),
287
                                'Subtract' => _t(
288
                                    'Variation.CodeSubtract',
289
                                    'Subtract from Base Code',
290
                                    'Subtract from code'
291
                                ),
292
                                'Set' => _t('Variation.CodeSet', 'Set as a new Code'),
293
                            ]
294
                        )
295
                            ->setEmptyString('')
296
                            ->setDescription(_t('Variation.CodeDescription', 'Does code modify or replace base code?')),
297
                        TextField::create('CodeModifier')
298
                            ->setTitle(_t('Variation.CodeModifier', 'Code'))
299
                            ->displayIf('CodeModifierAction')->isNotEmpty()->end(),
300
                        TextField::create('FinalCode')
301
                            ->setTitle('Final Modified Code')
302
                            ->setDescription("Product's code is {$this->Product()->Code}")
303
                            ->performDisabledTransformation()
304
                    )->setTitle('Code Modification'),
305
                ]
306
            );
307
308
            // Images tab
309
            $images = SortableUploadField::create('Images')
310
                ->setSortColumn('SortOrder')
311
                ->setIsMultiUpload(true)
312
                ->setAllowedExtensions($this->config()->get('allowed_images_extensions'))
313
                ->setFolderName('Uploads/Products/Images');
314
315
            $fields->addFieldsToTab('Root.Images', [
316
                $images,
317
            ]);
318
        });
319
320
        return parent::getCMSFields();
321
    }
322
323
    /**
324
     *
325
     */
326
    public function onBeforeWrite()
327
    {
328
        parent::onBeforeWrite();
329
330
        $modifierKeyField = 'OptionModifierKey';
331
        $this->{$modifierKeyField} = $this->getGeneratedValue();
332
333
        $codeModifierField = 'CodeModifier';
334
        switch ($this->CodeModifierAction) {
335
            case 'Subtract':
336
            case 'Add':
337
                if ($this->config()->get('trimAllWhitespace') == false) {
338
                    // trim the right of the code - some companies use spaces to denote options
339
                    $trimmed = rtrim($this->{$codeModifierField});
340
                    // replace duplicate spaces
341
                    $this->{$codeModifierField} = preg_replace('/\s+/', ' ', $trimmed);
342
                    break;
343
                }
344
            /* falls through */
345
            case 'Set':
346
                $trimmed = trim($this->{$codeModifierField});
347
                $this->{$codeModifierField} = preg_replace('/\s+/', ' ', $trimmed);
348
                break;
349
        }
350
351
        $this->FinalPrice = $this->calculateFinalPrice();
352
        $this->FinalCode = $this->calculateFinalCode();
353
354
        if ($this->Product()->hasDatabaseField('Weight')) {
355
            $this->FinalWeight = $this->calculateFinalWeight();
356
        }
357
    }
358
359
    /**
360
     * @return ValidationResult
361
     */
362
    public function validate()
363
    {
364
        $validate = parent::validate();
365
        $product = $this->Product();
366
367
        if (!$this->Title) {
368
            $validate->addFieldError('Title', 'A title is required');
369
        }
370
371
        /*if (!$this->VariationTypeID) {
372
            $validate->addFieldError('VariationTypeID', 'A variation type is required');
373
        }//*/
374
375
        if ($this->PriceModifierAction == 'Subtract' && $this->PriceModifier > $product->Price) {
376
            $validate->addFieldError('PriceModifier', "You can't subtract more than the price of the product ({$product->Price})");
377
        }
378
379
        if ($this->WeightModifierAction == 'Subtract' && $this->WeightModifier > $product->Weight) {
380
            $validate->addFieldError('WeightModifier', "You can't subtract more than the weight of the product ({$product->Weight})");
381
        }
382
383
        return $validate;
384
    }
385
386
    /**
387
     * @return string
388
     */
389
    public function getGeneratedValue()
390
    {
391
        $modPrice = ($this->PriceModifier) ? (string)$this->PriceModifier : '0';
392
        $modPriceWithSymbol = self::getOptionModifierActionSymbol($this->PriceModifierAction) . $modPrice;
393
        $modWeight = ($this->WeightModifier) ? (string)$this->WeightModifier : '0';
394
        $modWeight = self::getOptionModifierActionSymbol($this->WeightModifierAction) . $modWeight;
395
        $modCode = self::getOptionModifierActionSymbol($this->CodeModifierAction) . $this->CodeModifier;
396
397
        return $this->Title . '{p' . $modPriceWithSymbol . '|w' . $modWeight . '|c' . $modCode . '}';
398
    }
399
400
    /**
401
     * @param $oma
402
     * @param bool $returnWithOnlyPlusMinus
403
     *
404
     * @return string
405
     */
406
    public static function getOptionModifierActionSymbol($oma, $returnWithOnlyPlusMinus = false)
407
    {
408
        switch ($oma) {
409
            case 'Subtract':
410
                $symbol = '-';
411
                break;
412
            case 'Set':
413
                $symbol = ($returnWithOnlyPlusMinus) ? '' : ':';
414
                break;
415
            default:
416
                $symbol = '+';
417
        }
418
419
        return $symbol;
420
    }
421
422
    /**
423
     * @return string
424
     */
425
    protected function getWeightModifierWithSymbol()
426
    {
427
        return $this->getOptionModifierActionSymbol($this->WeightModifierAction) . $this->WeightModifier;
428
    }
429
430
    /**
431
     * @return string
432
     */
433
    protected function getPriceModifierWithSymbol()
434
    {
435
        return $this->getOptionModifierActionSymbol($this->PriceModifierAction) . $this->PriceModifier;
436
    }
437
438
    /**
439
     * @return string
440
     */
441
    protected function getCodeModifierWithSymbol()
442
    {
443
        return $this->getOptionModifierActionSymbol($this->CodeModifierAction) . $this->CodeModifier;
444
    }
445
446
    /**
447
     * @return float
448
     */
449
    protected function calculateFinalPrice()
450
    {
451
        $product = $this->Product();// this relation is set by a developer's data extension
452
453
        if ($this->PriceModifierAction == 'Add') {
454
            return $this->PriceModifier + $product->Price;
455
        } elseif ($this->PriceModifierAction == 'Subtract') {
456
            return $product->Price - $this->PriceModifier;
457
        } elseif ($this->PriceModifierAction == 'Set') {
458
            return $this->PriceModifier;
459
        }
460
461
        return $product->Price;
462
    }
463
464
    /**
465
     * @return float
466
     */
467
    protected function calculateFinalWeight()
468
    {
469
        $product = $this->Product();// this relation is set by a developer's data extension
470
471
        if ($this->WeightModifierAction == 'Add') {
472
            return $this->WeightModifier + $product->Weight;
473
        } elseif ($this->WeightModifierAction == 'Subtract') {
474
            return $product->Weight - $this->WeightModifier;
475
        } elseif ($this->WeightModifierAction == 'Set') {
476
            return $this->WeightModifier;
477
        }
478
479
        return $product->Weight;
480
    }
481
482
    /**
483
     * @return string
484
     */
485
    protected function calculateFinalCode()
486
    {
487
        $product = $this->Product();// this relation is set by a developer's data extension
488
489
        if ($this->CodeModifierAction == 'Add') {
490
            return "{$product->Code}{$this->CodeModifier}";
491
        } elseif ($this->CodeModifierAction == 'Subtract') {
492
            return rtrim($product->Code, $this->CodeModifier);
493
        } elseif ($this->CodeModifierAction == 'Set') {
494
            return $this->CodeModifier;
495
        }
496
497
        return $product->Code;
498
    }
499
500
    /**
501
     * @return mixed|string
502
     */
503
    public function getGeneratedTitle()
504
    {
505
        $modPrice = ($this->PriceModifier) ? (string)$this->PriceModifier : '0';
506
        $title = $this->Title;
507
        $title .= ($this->PriceModifier != 0) ?
508
            ': (' . self::getOptionModifierActionSymbol(
509
                $this->PriceModifierAction,
510
                $returnWithOnlyPlusMinus = true
511
            ) . '$' . $modPrice . ')' :
512
            '';
513
514
        return $title;
515
    }
516
517
    /**
518
     * @return bool
519
     */
520
    public function getIsAvailable()
521
    {
522
        $available = true;
523
524
        if (!$this->Available) {
525
            $available = false;
526
        }
527
528
        $this->extend('updateGetIsAvailable', $available);
529
530
        return $available;
531
    }
532
533
    /**
534
     * @return bool
535
     */
536
    public function getAvailability()
537
    {
538
        Deprecation::notice('1.4', 'Use getIsAvailable() instead');
539
        return $this->getIsAvailable();
540
    }
541
542
    /**
543
     * @param $member
544
     * @return bool|int|void
545
     */
546
    public function canCreate($member = null, $context = [])
547
    {
548
        if (!$member) {
549
            $member = Security::getCurrentUser();
550
        }
551
552
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
553
    }
554
555
    /**
556
     * @param $member
557
     * @return bool|int|void|null
558
     */
559
    public function canEdit($member = null, $context = [])
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

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

559
    public function canEdit($member = null, /** @scrutinizer ignore-unused */ $context = [])

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
560
    {
561
        if (!$member) {
562
            $member = Security::getCurrentUser();
563
        }
564
565
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
566
    }
567
568
    /**
569
     * @param $member
570
     * @return bool|int|void
571
     */
572
    public function canDelete($member = null, $context = [])
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

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

572
    public function canDelete($member = null, /** @scrutinizer ignore-unused */ $context = [])

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
573
    {
574
        if (!$member) {
575
            $member = Security::getCurrentUser();
576
        }
577
578
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
579
    }
580
}
581