Passed
Push — master ( eb8b5a...80a24d )
by Nic
02:40
created

Variation::canEdit()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 7
rs 10
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 bool
67
     */
68
    private static $code_trim_right_space = true;
69
70
    /**
71
     * @var bool
72
     */
73
    private static $code_enforce_single_spaces = true;
74
75
    /**
76
     * @var bool
77
     */
78
    private static $code_trim_left_spaces = false;
79
80
    /**
81
     * @var string[]
82
     */
83
    private static $db = [
84
        'Title' => 'Varchar(255)',
85
        'UseProductContent' => 'Boolean',
86
        'Content' => 'HTMLText',
87
        'WeightModifier' => 'Decimal(9,3)',
88
        'CodeModifier' => 'Text',
89
        'PriceModifier' => 'Currency',
90
        'WeightModifierAction' => "Enum('Add,Subtract,Set', null)",
91
        'CodeModifierAction' => "Enum('Add,Subtract,Set', null)",
92
        'PriceModifierAction' => "Enum('Add,Subtract,Set', null)",
93
        'Available' => 'Boolean',
94
        'Type' => 'Int',
95
        'OptionModifierKey' => 'Varchar(255)',
96
        'SortOrder' => 'Int',
97
        'FinalPrice' => 'Currency',
98
        'FinalWeight' => 'Decimal(9,3)',
99
        'FinalCode' => 'Varchar(255)',
100
    ];
101
102
    /**
103
     * @var string[]
104
     */
105
    private static $indexes = [
106
        'FinalPrice' => true,
107
        'FinalWeight' => true,
108
        'FinalCode' => true,
109
    ];
110
111
    /**
112
     * @var string[]
113
     */
114
    private static $has_one = [
115
        'VariationType' => VariationType::class,
116
    ];
117
118
    /**
119
     * @var array
120
     */
121
    private static $many_many = [
122
        'Images' => File::class,
123
    ];
124
125
    /**
126
     * @var \string[][]
127
     */
128
    private static $many_many_extraFields = [
129
        'Images' => [
130
            'SortOrder' => 'Int',
131
        ],
132
    ];
133
134
    /**
135
     * @var string[]
136
     */
137
    private static $owns = [
138
        'Images',
139
    ];
140
141
    /**
142
     * @var string[]
143
     */
144
    private static $default_sort = 'SortOrder';
145
146
    /**
147
     * The relation name was established before requests for videos.
148
     * The relation has subsequently been updated from Image::class to File::class
149
     * to allow for additional file types such as mp4
150
     *
151
     * @var array
152
     */
153
    private static $allowed_images_extensions = [
154
        'gif',
155
        'jpeg',
156
        'jpg',
157
        'png',
158
        'bmp',
159
        'ico',
160
        'mp4',
161
    ];
162
163
    /**
164
     * @return FieldList
165
     */
166
    public function getCMSFields()
167
    {
168
        $this->beforeUpdateCMSFields(function (FieldList $fields) {
169
            $fields->removeByName([
170
                'Images',
171
                'WeightModifier',
172
                'CodeModifier',
173
                'PriceModifier',
174
                'WeightModifierAction',
175
                'CodeModifierAction',
176
                'PriceModifierAction',
177
                'Available',
178
                'Type',
179
                'OptionModifierKey',
180
                'SortOrder',
181
                'ProductID',
182
                'FinalPrice',
183
                'FinalWeight',
184
                'FinalCode',
185
            ]);
186
187
            $fields->insertBefore(
188
                'Content',
189
                CheckboxField::create('Available')
190
                    ->setTitle('Available for purchase')
191
            );
192
193
            $fields->insertBefore(
194
                'Content',
195
                $fields->dataFieldByName('VariationTypeID')
196
            );
197
198
            $fields->insertBefore(
199
                'Content',
200
                $fields->dataFieldByName('UseProductContent')
201
            );
202
203
            $content = $fields->dataFieldByName('Content');
204
205
            $content->hideUnless('UseProductContent')->isNotChecked()->end();
206
207
            if ($this->exists()) {
208
                $fields->addFieldToTab(
209
                    'Root.ProductModifications',
210
                    ReadonlyField::create('OptionModifierKey')
211
                        ->setTitle(_t('Variation.ModifierKey', 'Modifier Key'))
212
                );
213
            }
214
215
            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

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

596
    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...
597
    {
598
        if (!$member) {
599
            $member = Security::getCurrentUser();
600
        }
601
602
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
603
    }
604
605
    /**
606
     * @param $member
607
     * @return bool|int|void
608
     */
609
    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

609
    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...
610
    {
611
        if (!$member) {
612
            $member = Security::getCurrentUser();
613
        }
614
615
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
616
    }
617
}
618