Passed
Push — master ( 300e34...a36352 )
by Nic
10:09 queued 06:34
created

Variation::getPriceModifierWithSymbol()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Dynamic\Foxy\Model;
4
5
use Bummzack\SortableFile\Forms\SortableUploadField;
6
use Dynamic\Foxy\Page\Product;
7
use SilverStripe\Assets\File;
8
use SilverStripe\Dev\Deprecation;
9
use SilverStripe\Forms\CheckboxField;
10
use SilverStripe\Forms\CurrencyField;
11
use SilverStripe\Forms\DropdownField;
12
use SilverStripe\Forms\FieldGroup;
13
use SilverStripe\Forms\FieldList;
14
use SilverStripe\Forms\NumericField;
15
use SilverStripe\Forms\ReadonlyField;
16
use SilverStripe\Forms\TextField;
17
use SilverStripe\ORM\DataObject;
18
use SilverStripe\ORM\ManyManyList;
19
use SilverStripe\ORM\ValidationResult;
20
use SilverStripe\Security\Permission;
21
use SilverStripe\Security\Security;
22
23
/**
24
 * Class Variation
25
 * @package Dynamic\Foxy\Model
26
 *
27
 * @property string $Title
28
 * @property bool $UseProductContent
29
 * @property string Content
30
 * @property float $WeightModifier
31
 * @property string $CodeModifier
32
 * @property float $PriceModifier
33
 * @property string $WeightModifierAction
34
 * @property string $CodeModifierAction
35
 * @property string $PriceModifierAction
36
 * @property bool $Available
37
 * @property int $Type
38
 * @property string $OptionModifierKey
39
 * @property int $SortOrder
40
 * @property float $FinalPrice
41
 * @property float $FinalWeight
42
 * @property string $FinalCode
43
 * @property bool $IsDefault
44
 * @property int $VariationTypeID
45
 * @property int $ProductID
46
 *
47
 * @method VariationType VariationType()
48
 * @method Product Product
49
 *
50
 * @method ManyManyList Images()
51
 */
52
class Variation extends DataObject
53
{
54
    /**
55
     * @var array
56
     */
57
    protected $default_read_only_fields = [
58
        'WeightModifierAction',
59
        'CodeModifierAction',
60
        'PriceModifierAction',
61
        'CodeModifier',
62
    ];
63
64
    /**
65
     * @var string
66
     */
67
    private static $table_name = 'Variation';
68
69
    /**
70
     * @var string
71
     */
72
    private static $singular_name = 'Variation';
73
74
    /**
75
     * @var string
76
     */
77
    private static $plural_name = 'Variations';
78
79
    /**
80
     * @var bool
81
     */
82
    private static $code_trim_right_space = true;
83
84
    /**
85
     * @var bool
86
     */
87
    private static $code_enforce_single_spaces = true;
88
89
    /**
90
     * @var bool
91
     */
92
    private static $code_trim_left_spaces = false;
93
94
    /**
95
     * Mapping from a Product's field to the Variation's field for a default variation
96
     *
97
     * @var string[]
98
     */
99
    private static $default_variation_mapping = [
100
        'Title' => 'Title',
101
        'Available' => 'Available',
102
    ];
103
104
    /**
105
     * @var string[]
106
     */
107
    private static $db = [
108
        'Title' => 'Varchar(255)',
109
        'UseProductContent' => 'Boolean',
110
        'Content' => 'HTMLText',
111
        'WeightModifier' => 'Decimal(9,3)',
112
        'CodeModifier' => 'Text',
113
        'PriceModifier' => 'Currency',
114
        'WeightModifierAction' => "Enum('Add,Subtract,Set', null)",
115
        'CodeModifierAction' => "Enum('Add,Subtract,Set', null)",
116
        'PriceModifierAction' => "Enum('Add,Subtract,Set', null)",
117
        'Available' => 'Boolean',
118
        'Type' => 'Int',
119
        'OptionModifierKey' => 'Varchar(255)',
120
        'SortOrder' => 'Int',
121
        'FinalPrice' => 'Currency',
122
        'FinalWeight' => 'Decimal(9,3)',
123
        'FinalCode' => 'Varchar(255)',
124
        'IsDefault' => 'Boolean',
125
        'ReceiptTitle' => 'HTMLVarchar(255)',
126
        'QuantityMax' => 'Int',
127
        'InventoryLevel' => 'Int',
128
    ];
129
130
    /**
131
     * @var string[]
132
     */
133
    private static $indexes = [
134
        'FinalPrice' => true,
135
        'FinalWeight' => true,
136
        'FinalCode' => true,
137
    ];
138
139
    /**
140
     * @var string[]
141
     */
142
    private static $has_one = [
143
        'VariationType' => VariationType::class,
144
        'Product' => Product::class,
145
    ];
146
147
    /**
148
     * @var array
149
     */
150
    private static $many_many = [
151
        'Images' => File::class,
152
    ];
153
154
    /**
155
     * @var \string[][]
156
     */
157
    private static $many_many_extraFields = [
158
        'Images' => [
159
            'SortOrder' => 'Int',
160
        ],
161
    ];
162
163
    /**
164
     * @var string[]
165
     */
166
    private static $owns = [
167
        'Images',
168
    ];
169
170
    /**
171
     * @var string[]
172
     */
173
    private static $default_sort = 'SortOrder';
174
175
    /**
176
     * The relation name was established before requests for videos.
177
     * The relation has subsequently been updated from Image::class to File::class
178
     * to allow for additional file types such as mp4
179
     *
180
     * @var array
181
     */
182
    private static $allowed_images_extensions = [
183
        'gif',
184
        'jpeg',
185
        'jpg',
186
        'png',
187
        'bmp',
188
        'ico',
189
        'mp4',
190
    ];
191
192
    /**
193
     * @return FieldList
194
     */
195
    public function getCMSFields()
196
    {
197
        $this->beforeUpdateCMSFields(function (FieldList $fields) {
198
            $fields->removeByName([
199
                'Images',
200
                'WeightModifier',
201
                'CodeModifier',
202
                'PriceModifier',
203
                'WeightModifierAction',
204
                'CodeModifierAction',
205
                'PriceModifierAction',
206
                'Available',
207
                'Type',
208
                'OptionModifierKey',
209
                'SortOrder',
210
                'ProductID',
211
                'FinalPrice',
212
                'FinalWeight',
213
                'FinalCode',
214
                'IsDefault',
215
            ]);
216
217
            $fields->insertBefore(
218
                'Content',
219
                CheckboxField::create('Available')
220
                    ->setTitle('Available for purchase')
221
            );
222
223
            $fields->insertBefore(
224
                'Content',
225
                $fields->dataFieldByName('VariationTypeID')
226
            );
227
228
            $fields->insertBefore(
229
                'Content',
230
                $fields->dataFieldByName('UseProductContent')
231
            );
232
233
            $content = $fields->dataFieldByName('Content');
234
235
            $content->hideUnless('UseProductContent')->isNotChecked()->end();
236
237
            if ($this->exists()) {
238
                $fields->addFieldToTab(
239
                    'Root.ProductModifications',
240
                    ReadonlyField::create('OptionModifierKey')
241
                        ->setTitle(_t('Variation.ModifierKey', 'Modifier Key'))
242
                );
243
            }
244
245
            if ($this->Product()->hasDatabaseField('Weight')) {
246
                $fields->addFieldtoTab(
247
                    'Root.ProductModifications',
248
                    FieldGroup::create(
249
                        DropdownField::create(
250
                            'WeightModifierAction',
251
                            _t('Variation.WeightModifierAction', 'Weight Modification Type'),
252
                            [
253
                                'Add' => _t(
254
                                    'Variation.WeightAdd',
255
                                    'Add to Base Weight',
256
                                    'Add to weight'
257
                                ),
258
                                'Subtract' => _t(
259
                                    'Variation.WeightSubtract',
260
                                    'Subtract from Base Weight',
261
                                    'Subtract from weight'
262
                                ),
263
                                'Set' => _t('Variation.WeightSet', 'Set as a new Weight'),
264
                            ]
265
                        )
266
                            ->setEmptyString('')
267
                            ->setDescription(_t(
268
                                'Variation.WeightDescription',
269
                                'Does weight modify or replace base weight?'
270
                            )),
271
                        NumericField::create("WeightModifier")
272
                            ->setTitle(_t('Variation.WeightModifier', 'Weight'))
273
                            ->setScale(3)
274
                            ->setDescription(_t(
275
                                'Variation.WeightDescription',
276
                                'Only supports up to 3 decimal places'
277
                            ))->displayIf('WeightModifierAction')->isNotEmpty()->end(),
278
                        NumericField::create('FinalWeight')
279
                            ->setTitle('Final Modified Weight')
280
                            ->setDescription("Product's weight is {$this->Product()->Weight}")
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...
281
                            ->performDisabledTransformation()
282
                    )->setTitle('Weight Modification')
283
                );
284
            }
285
286
            $fields->addFieldsToTab(
287
                'Root.ProductModifications',
288
                [
289
                    // Price Modifier Fields
290
                    //HeaderField::create('PriceHD', _t('Variation.PriceHD', 'Modify Price'), 4),
291
                    FieldGroup::create(
292
                        DropdownField::create(
293
                            'PriceModifierAction',
294
                            _t('Variation.PriceModifierAction', 'Price Modification Type'),
295
                            [
296
                                'Add' => _t(
297
                                    'Variation.PriceAdd',
298
                                    'Add to Base Price',
299
                                    'Add to price'
300
                                ),
301
                                'Subtract' => _t(
302
                                    'Variation.PriceSubtract',
303
                                    'Subtract from Base Price',
304
                                    'Subtract from price'
305
                                ),
306
                                'Set' => _t('Variation.PriceSet', 'Set as a new Price'),
307
                            ]
308
                        )
309
                            ->setEmptyString('')
310
                            ->setDescription(_t('Variation.PriceDescription', 'Does price modify or replace base price?')),
311
                        CurrencyField::create('PriceModifier')
312
                            ->setTitle(_t('Variation.PriceModifier', 'Price'))
313
                            ->displayIf('PriceModifierAction')->isNotEmpty()->end(),
314
                        CurrencyField::create('FinalPrice')
315
                            ->setTitle('Final Modified Price')
316
                            ->setDescription("Product's price is {$this->Product()->Price}")
317
                            ->performDisabledTransformation()
318
                    )->setTitle('Price Modifications'),
319
320
                    // Code Modifier Fields
321
                    //HeaderField::create('CodeHD', _t('Variation.CodeHD', 'Modify Code'), 4),
322
                    FieldGroup::create(
323
                        DropdownField::create(
324
                            'CodeModifierAction',
325
                            _t('Variation.CodeModifierAction', 'Code Modification Type'),
326
                            [
327
                                'Add' => _t(
328
                                    'Variation.CodeAdd',
329
                                    'Add to Base Code',
330
                                    'Add to code'
331
                                ),
332
                                'Subtract' => _t(
333
                                    'Variation.CodeSubtract',
334
                                    'Subtract from Base Code',
335
                                    'Subtract from code'
336
                                ),
337
                                'Set' => _t('Variation.CodeSet', 'Set as a new Code'),
338
                            ]
339
                        )
340
                            ->setEmptyString('')
341
                            ->setDescription(_t('Variation.CodeDescription', 'Does code modify or replace base code?')),
342
                        TextField::create('CodeModifier')
343
                            ->setTitle(_t('Variation.CodeModifier', 'Code'))
344
                            ->displayIf('CodeModifierAction')->isNotEmpty()->end(),
345
                        TextField::create('FinalCode')
346
                            ->setTitle('Final Modified Code')
347
                            ->setDescription("Product's code is {$this->Product()->Code}")
348
                            ->performDisabledTransformation()
349
                    )->setTitle('Code Modification'),
350
                ]
351
            );
352
353
            // Images tab
354
            $images = SortableUploadField::create('Images')
355
                ->setSortColumn('SortOrder')
356
                ->setIsMultiUpload(true)
357
                ->setAllowedExtensions($this->config()->get('allowed_images_extensions'))
358
                ->setFolderName('Uploads/Products/Images');
359
360
            $fields->addFieldsToTab('Root.Images', [
361
                $images,
362
            ]);
363
        });
364
365
        $fields = parent::getCMSFields();
366
367
        $this->applyReadOnlyTransformations($fields);
368
369
        return $fields;
370
    }
371
372
    /**
373
     * @param $fields
374
     * @return void
375
     */
376
    protected function applyReadonlyTransformations(&$fields)
377
    {
378
        if ($this->IsDefault) {
379
            foreach ($this->getDefaultReadOnlyFields() as $fieldName) {
380
                if ($field = $fields->dataFieldByName($fieldName)) {
381
                    if ($field instanceof DropdownField) {
382
                        $field->setDisabled(true);
383
                    } else {
384
                        $field->setReadOnly(true);
385
                    }
386
                }
387
            }
388
        }
389
    }
390
391
    /**
392
     * @return array
393
     */
394
    protected function getDefaultReadOnlyFields()
395
    {
396
        $fields = $this->default_read_only_fields;
397
398
        $this->extend('updateDefaultReadOnlyFields', $fields);
399
400
        return $fields;
401
    }
402
403
    /**
404
     *
405
     */
406
    public function onBeforeWrite()
407
    {
408
        parent::onBeforeWrite();
409
410
        $modifierKeyField = 'OptionModifierKey';
411
        $this->{$modifierKeyField} = $this->getGeneratedValue();
412
413
        $codeModifierField = 'CodeModifier';
414
        $codeModifier = $this->{$codeModifierField};
415
416
        switch ($this->CodeModifierAction) {
417
            case 'Subtract':
418
            case 'Add':
419
                if (static::config()->get('code_trim_left_spaces')) {
420
                    //remove duplicate leading spaces
421
                    if (strpos($codeModifier, ' ') == 0) {
422
                        $codeModifier = ltrim($codeModifier);
423
                    }
424
                }
425
                if (static::config()->get('code_trim_right_space')) {
426
                    $codeModifier = rtrim($codeModifier);
427
                }
428
                break;
429
            case 'Set':
430
                //We must trim for Foxy
431
                $codeModifier = trim($codeModifier);
432
                break;
433
        }
434
435
        if (static::config()->get('code_enforce_single_spaces')) {
436
            //replace duplicate spaces
437
            $codeModifier = preg_replace('/\s+/', ' ', $codeModifier);
438
        }
439
440
        /**
441
         * This method supersedes configs code_trim_right_space and code_enforce_single_spaces
442
         */
443
        if (static::config()->get('code_remove_spaces')) {
444
            // replace duplicate spaces
445
            $codeModifier = preg_replace('/\s+/', '', $codeModifier);
446
        }
447
448
        //can be used for backwards compatibility (do your own logic)
449
        $this->extend('updateCodeModifier', $this, $codeModifier);
450
451
        $this->{$codeModifierField} = $codeModifier;
452
453
        $this->FinalPrice = $this->calculateFinalPrice();
454
        $this->FinalCode = $this->calculateFinalCode();
455
456
        if ($this->Product()->hasDatabaseField('Weight')) {
457
            $this->FinalWeight = $this->calculateFinalWeight();
458
        }
459
    }
460
461
    /**
462
     * @return ValidationResult
463
     */
464
    public function validate()
465
    {
466
        $validate = parent::validate();
467
        $product = $this->Product();
468
469
        if (!$this->Title) {
470
            $validate->addFieldError('Title', 'A title is required');
471
        }
472
473
        /*if (!$this->VariationTypeID) {
474
            $validate->addFieldError('VariationTypeID', 'A variation type is required');
475
        }//*/
476
477
        if ($this->PriceModifierAction == 'Subtract' && $this->PriceModifier > $product->Price) {
478
            $validate->addFieldError('PriceModifier', "You can't subtract more than the price of the product ({$product->Price})");
479
        }
480
481
        if ($this->WeightModifierAction == 'Subtract' && $this->WeightModifier > $product->Weight) {
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...
482
            $validate->addFieldError('WeightModifier', "You can't subtract more than the weight of the product ({$product->Weight})");
483
        }
484
485
        return $validate;
486
    }
487
488
    /**
489
     * @return string
490
     */
491
    public function getGeneratedValue()
492
    {
493
        $modPrice = ($this->PriceModifier) ? (string)$this->PriceModifier : '0';
494
        $modPriceWithSymbol = self::getOptionModifierActionSymbol($this->PriceModifierAction) . $modPrice;
495
        $modWeight = ($this->WeightModifier) ? (string)$this->WeightModifier : '0';
496
        $modWeight = self::getOptionModifierActionSymbol($this->WeightModifierAction) . $modWeight;
497
        $modCode = self::getOptionModifierActionSymbol($this->CodeModifierAction) . $this->CodeModifier;
498
499
        return $this->Title . '{p' . $modPriceWithSymbol . '|w' . $modWeight . '|c' . $modCode . '}';
500
    }
501
502
    /**
503
     * @param $oma
504
     * @param bool $returnWithOnlyPlusMinus
505
     *
506
     * @return string
507
     */
508
    public static function getOptionModifierActionSymbol($oma, $returnWithOnlyPlusMinus = false)
509
    {
510
        switch ($oma) {
511
            case 'Subtract':
512
                $symbol = '-';
513
                break;
514
            case 'Set':
515
                $symbol = ($returnWithOnlyPlusMinus) ? '' : ':';
516
                break;
517
            default:
518
                $symbol = '+';
519
        }
520
521
        return $symbol;
522
    }
523
524
    /**
525
     * @return string
526
     */
527
    protected function getWeightModifierWithSymbol()
528
    {
529
        return $this->getOptionModifierActionSymbol($this->WeightModifierAction) . $this->WeightModifier;
530
    }
531
532
    /**
533
     * @return string
534
     */
535
    protected function getPriceModifierWithSymbol()
536
    {
537
        return $this->getOptionModifierActionSymbol($this->PriceModifierAction) . $this->PriceModifier;
538
    }
539
540
    /**
541
     * @return string
542
     */
543
    protected function getCodeModifierWithSymbol()
544
    {
545
        return $this->getOptionModifierActionSymbol($this->CodeModifierAction) . $this->CodeModifier;
546
    }
547
548
    /**
549
     * @return float
550
     */
551
    protected function calculateFinalPrice()
552
    {
553
        $product = $this->Product();// this relation is set by a developer's data extension
554
555
        if ($this->PriceModifierAction == 'Add') {
556
            return $this->PriceModifier + $product->Price;
557
        } elseif ($this->PriceModifierAction == 'Subtract') {
558
            return $product->Price - $this->PriceModifier;
559
        } elseif ($this->PriceModifierAction == 'Set') {
560
            return $this->PriceModifier;
561
        }
562
563
        return $product->Price;
564
    }
565
566
    /**
567
     * @return float
568
     */
569
    protected function calculateFinalWeight()
570
    {
571
        $product = $this->Product();// this relation is set by a developer's data extension
572
573
        if ($this->WeightModifierAction == 'Add') {
574
            return $this->WeightModifier + $product->Weight;
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...
575
        } elseif ($this->WeightModifierAction == 'Subtract') {
576
            return $product->Weight - $this->WeightModifier;
577
        } elseif ($this->WeightModifierAction == 'Set') {
578
            return $this->WeightModifier;
579
        }
580
581
        return $product->Weight;
582
    }
583
584
    /**
585
     * @return string
586
     */
587
    protected function calculateFinalCode()
588
    {
589
        $product = $this->Product();// this relation is set by a developer's data extension
590
591
        if ($this->CodeModifierAction == 'Add') {
592
            return "{$product->Code}{$this->CodeModifier}";
593
        } elseif ($this->CodeModifierAction == 'Subtract') {
594
            return rtrim($product->Code, $this->CodeModifier);
595
        } elseif ($this->CodeModifierAction == 'Set') {
596
            return $this->CodeModifier;
597
        }
598
599
        return $product->Code;
600
    }
601
602
    /**
603
     * @return mixed|string
604
     */
605
    public function getGeneratedTitle()
606
    {
607
        $modPrice = ($this->PriceModifier) ? (string)$this->PriceModifier : '0';
608
        $title = $this->Title;
609
        $title .= ($this->PriceModifier != 0) ?
610
            ': (' . self::getOptionModifierActionSymbol(
611
                $this->PriceModifierAction,
612
                $returnWithOnlyPlusMinus = true
613
            ) . '$' . $modPrice . ')' :
614
            '';
615
616
        return $title;
617
    }
618
619
    /**
620
     * @return bool
621
     */
622
    public function getIsAvailable()
623
    {
624
        $available = true;
625
626
        if (!$this->Available) {
627
            $available = false;
628
        }
629
630
        $this->extend('updateGetIsAvailable', $available);
631
632
        return $available;
633
    }
634
635
    /**
636
     * @return bool
637
     */
638
    public function getAvailability()
639
    {
640
        Deprecation::notice('1.4', 'Use getIsAvailable() instead');
641
        return $this->getIsAvailable();
642
    }
643
644
    /**
645
     * @param $member
646
     * @return bool|int|void
647
     */
648
    public function canCreate($member = null, $context = [])
649
    {
650
        if (!$member) {
651
            $member = Security::getCurrentUser();
652
        }
653
654
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
655
    }
656
657
    /**
658
     * @param $member
659
     * @return bool|int|void|null
660
     */
661
    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

661
    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...
662
    {
663
        if (!$member) {
664
            $member = Security::getCurrentUser();
665
        }
666
667
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
668
    }
669
670
    /**
671
     * @param $member
672
     * @return bool|int|void
673
     */
674
    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

674
    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...
675
    {
676
        if (!$member) {
677
            $member = Security::getCurrentUser();
678
        }
679
680
        if ($this->IsDefault) {
681
            return false;
682
        }
683
684
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
685
    }
686
}
687