Passed
Push — master ( b9661c...99b851 )
by Jason
07:16 queued 23s
created

ProductPage::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.3332

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 8
ccs 4
cts 6
cp 0.6667
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 3
crap 3.3332
1
<?php
2
3
namespace Dynamic\FoxyStripe\Page;
4
5
use Bummzack\SortableFile\Forms\SortableUploadField;
6
use Dynamic\FoxyStripe\Model\FoxyCart;
7
use Dynamic\FoxyStripe\Model\FoxyStripeSetting;
8
use Dynamic\FoxyStripe\Model\OptionItem;
9
use Dynamic\FoxyStripe\Model\OrderDetail;
10
use Dynamic\FoxyStripe\Model\ProductCategory;
11
use Dynamic\FoxyStripe\Model\ProductImage;
12
use SilverStripe\AssetAdmin\Forms\UploadField;
13
use SilverStripe\Assets\Image;
14
use SilverStripe\CMS\Controllers\ContentController;
15
use SilverStripe\Control\Controller;
16
use SilverStripe\Forms\CheckboxField;
17
use SilverStripe\Forms\CurrencyField;
18
use SilverStripe\Forms\DropdownField;
19
use SilverStripe\Forms\FieldList;
20
use SilverStripe\Forms\GridField\GridField;
21
use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
22
use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor;
23
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
24
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
25
use SilverStripe\Forms\HeaderField;
26
use SilverStripe\Forms\LiteralField;
27
use SilverStripe\Forms\NumericField;
28
use SilverStripe\Forms\RequiredFields;
29
use SilverStripe\Forms\TextField;
30
use SilverStripe\Security\Member;
31
use SilverStripe\Security\Permission;
32
use SilverStripe\Security\PermissionProvider;
33
use SilverStripe\View\Requirements;
34
use Symbiote\GridFieldExtensions\GridFieldOrderableRows;
35
36
/**
37
 * Class ProductPage
38
 * @package Dynamic\FoxyStripe\Page
39
 *
40
 * @property \SilverStripe\ORM\FieldType\DBCurrency $Price
41
 * @property \SilverStripe\ORM\FieldType\DBDecimal $Weight
42
 * @property \SilverStripe\ORM\FieldType\DBVarchar $Code
43
 * @property \SilverStripe\ORM\FieldType\DBVarchar $ReceiptTitle
44
 * @property \SilverStripe\ORM\FieldType\DBBoolean $Featured
45
 * @property \SilverStripe\ORM\FieldType\DBBoolean $Available
46
 *
47
 * @property int $CategoryID
48
 * @method ProductCategory Category()
49
 *
50
 * @method \SilverStripe\ORM\HasManyList ProductOptions()
51
 * @method \SilverStripe\ORM\HasManyList OrderDetails()
52
 *
53
 * @method \SilverStripe\ORM\ManyManyList Images()
54
 *
55
 * @method \SilverStripe\ORM\ManyManyList ProductHolders()
56
 */
57
class ProductPage extends \Page implements PermissionProvider
58
{
59
    /**
60
     * @var string
61
     */
62
    private static $default_parent = ProductHolder::class;
0 ignored issues
show
introduced by
The private property $default_parent is not used, and could be removed.
Loading history...
63
64
    /**
65
     * @var bool
66
     */
67
    private static $can_be_root = false;
0 ignored issues
show
introduced by
The private property $can_be_root is not used, and could be removed.
Loading history...
68
69
    /**
70
     * @var array
71
     */
72
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
73
        'Price' => 'Currency',
74
        'Weight' => 'Decimal',
75
        'Code' => 'Varchar(100)',
76
        'ReceiptTitle' => 'HTMLVarchar(255)',
77
        'Available' => 'Boolean',
78
    ];
79
80
    /**
81
     * @var array
82
     */
83
    private static $has_one = [
0 ignored issues
show
introduced by
The private property $has_one is not used, and could be removed.
Loading history...
84
        'Category' => ProductCategory::class,
85
    ];
86
87
    /**
88
     * @var array
89
     */
90
    private static $has_many = [
0 ignored issues
show
introduced by
The private property $has_many is not used, and could be removed.
Loading history...
91
        'ProductOptions' => OptionItem::class,
92
        'OrderDetails' => OrderDetail::class,
93
    ];
94
95
    /**
96
     * @var array
97
     */
98
    private static $many_many = [
0 ignored issues
show
introduced by
The private property $many_many is not used, and could be removed.
Loading history...
99
        'Images' => Image::class,
100
    ];
101
102
    /**
103
     * @var array
104
     */
105
    private static $many_many_extraFields = [
0 ignored issues
show
introduced by
The private property $many_many_extraFields is not used, and could be removed.
Loading history...
106
        'Images' => [
107
            'SortOrder' => 'Int',
108
        ],
109
    ];
110
111
    /**
112
     * @var array
113
     */
114
    private static $owns = [
0 ignored issues
show
introduced by
The private property $owns is not used, and could be removed.
Loading history...
115
        'Images',
116
    ];
117
118
    /**
119
     * @var array
120
     */
121
    private static $belongs_many_many = [
0 ignored issues
show
introduced by
The private property $belongs_many_many is not used, and could be removed.
Loading history...
122
        'ProductHolders' => ProductHolder::class,
123
    ];
124
125
    /**
126
     * @var string
127
     */
128
    private static $singular_name = 'Product';
0 ignored issues
show
introduced by
The private property $singular_name is not used, and could be removed.
Loading history...
129
130
    /**
131
     * @var string
132
     */
133
    private static $plural_name = 'Products';
0 ignored issues
show
introduced by
The private property $plural_name is not used, and could be removed.
Loading history...
134
135
    /**
136
     * @var string
137
     */
138
    private static $description = 'A product that can be added to the shopping cart';
0 ignored issues
show
introduced by
The private property $description is not used, and could be removed.
Loading history...
139
140
    /**
141
     * @var array
142
     */
143
    private static $indexes = [
0 ignored issues
show
introduced by
The private property $indexes is not used, and could be removed.
Loading history...
144
        'Code' => [
145
            'type' => 'unique',
146
            'columns' => ['Code'],
147
        ],
148
    ];
149
150
    /**
151
     * @var array
152
     */
153
    private static $defaults = [
0 ignored issues
show
introduced by
The private property $defaults is not used, and could be removed.
Loading history...
154
        'ShowInMenus' => false,
155
        'Available' => true,
156
        'Weight' => '0.0',
157
    ];
158
159
    /**
160
     * @var array
161
     */
162
    private static $summary_fields = [
0 ignored issues
show
introduced by
The private property $summary_fields is not used, and could be removed.
Loading history...
163
        'Image.CMSThumbnail',
164
        'Title',
165
        'Code',
166
        'Price.Nice',
167
        'Category.Title',
168
    ];
169
170
    /**
171
     * @var array
172
     */
173
    private static $searchable_fields = [
0 ignored issues
show
introduced by
The private property $searchable_fields is not used, and could be removed.
Loading history...
174
        'Title',
175
        'Code',
176
        'Available',
177
        'Category.ID',
178
    ];
179
180
    /**
181
     * @var string
182
     */
183
    private static $table_name = 'ProductPage';
0 ignored issues
show
introduced by
The private property $table_name is not used, and could be removed.
Loading history...
184
185
    /**
186
     * Construct a new ProductPage.
187
     *
188
     * @param array|null $record Used internally for rehydrating an object from database content.
189
     *                           Bypasses setters on this class, and hence should not be used
190
     *                           for populating data on new records.
191
     * @param boolean $isSingleton This this to true if this is a singleton() object, a stub for calling methods.
192
     *                             Singletons don't have their defaults set.
193
     * @param array $queryParams List of DataQuery params necessary to lazy load, or load related objects.
194
     */
195 14
    public function __construct($record = null, $isSingleton = false, $queryParams = [])
196
    {
197 14
        parent::__construct($record, $isSingleton, $queryParams);
198
199 14
        if (Controller::has_curr()) {
200 14
            if (Controller::curr() instanceof ContentController) {
201
                Requirements::javascript('dynamic/foxystripe: javascript/quantity.js');
202
                Requirements::css('dynamic/foxystripe: client/dist/css/quantityfield.css');
203
            }
204
        }
205
    }
206
207
    /**
208
     * @param bool $includerelations
209
     *
210
     * @return array
211
     */
212
    public function fieldLabels($includerelations = true)
213
    {
214
        $labels = parent::fieldLabels();
215
216
        $labels['Title'] = _t('ProductPage.TitleLabel', 'Name');
217
        $labels['Code'] = _t('ProductPage.CodeLabel', 'Code');
218
        $labels['Price.Nice'] = _t('ProductPage.PriceLabel', 'Price');
219
        $labels['Available.Nice'] = _t('ProductPage.AvailableLabel', 'Available');
220
        $labels['Category.ID'] = _t('ProductPage.IDLabel', 'Category');
221
        $labels['Category.Title'] = _t('ProductPage.CategoryTitleLabel', 'Category');
222
        $labels['Image.CMSThumbnail'] = _t('ProductPage.ImageLabel', 'Image');
223
224
        return $labels;
225
    }
226
227
    /**
228
     * @return \SilverStripe\Forms\FieldList
229
     */
230
    public function getCMSFields()
231
    {
232
        $this->beforeUpdateCMSFields(function (FieldList $fields) {
233
            // Cateogry Dropdown field w/ add new
234
            $source = function () {
235
                return ProductCategory::get()->map()->toArray();
236
            };
237
            $catField = DropdownField::create('CategoryID', _t('ProductPage.Category', 'FoxyCart Category'), $source())
238
                ->setEmptyString('')
239
                ->setDescription(_t(
240
                    'ProductPage.CategoryDescription',
241
                    'Required, must also exist in 
242
                    <a href="https://admin.foxycart.com/admin.php?ThisAction=ManageProductCategories" target="_blank">
243
                        FoxyCart Categories
244
                    </a>.
245
                    Used to set category specific options like shipping and taxes. Managed in
246
                        <a href="admin/settings">
247
                            Settings > FoxyStripe > Categories
248
                        </a>'
249
                ));
250
            if (class_exists('QuickAddNewExtension')) {
251
                $catField->useAddNew('ProductCategory', $source);
252
            }
253
254
            $fields->addFieldsToTab(
255
                'Root.Main',
256
                [
257
                    TextField::create('Code')
258
                        ->setTitle(_t('ProductPage.Code', 'Product Code'))
259
                        ->setDescription(_t(
260
                            'ProductPage.CodeDescription',
261
                            'Required, must be unique. Product identifier used by FoxyCart in transactions'
262
                        )),
263
                    CurrencyField::create('Price')
264
                        ->setTitle(_t('ProductPage.Price', 'Price'))
265
                        ->setDescription(_t(
266
                            'ProductPage.PriceDescription',
267
                            'Base price for this product. Can be modified using Product Options'
268
                        )),
269
                    NumericField::create('Weight')
270
                        ->setTitle(_t('ProductPage.Weight', 'Weight'))
271
                        ->setDescription(_t(
272
                            'ProductPage.WeightDescription',
273
                            'Base weight for this product in lbs. Can be modified using Product Options'
274
                        ))
275
                        ->setScale(2),
276
                    $catField,
277
                ],
278
                'Content'
279
            );
280
281
            // Product Options field
282
            $config = GridFieldConfig_RelationEditor::create();
283
            $config->addComponent(new GridFieldOrderableRows('SortOrder'));
284
            $products = $this->ProductOptions()->sort('SortOrder');
285
            $config->removeComponentsByType(GridFieldAddExistingAutocompleter::class);
286
            $prodOptField = GridField::create(
287
                'ProductOptions',
288
                _t('ProductPage.ProductOptions', 'Options'),
289
                $products,
290
                $config
291
            );
292
293
            // Details tab
294
            $fields->addFieldsToTab('Root.Details', [
295
                CheckboxField::create('Available')
296
                    ->setTitle(_t('ProductPage.Available', 'Available for purchase'))
297
                    ->setDescription(_t(
298
                        'ProductPage.AvailableDescription',
299
                        'If unchecked, will remove "Add to Cart" form and instead display "Currently unavailable"'
300
                    )),
301
                TextField::create('ReceiptTitle')
302
                    ->setTitle(_t('ProductPage.ReceiptTitle', 'Product Title for Receipt'))
303
                    ->setDescription(_t(
304
                        'ProductPage.ReceiptTitleDescription',
305
                        'Optional'
306
                    )),
307
            ]);
308
309
            // Options Tab
310
            $fields->addFieldsToTab('Root.Options', [
311
                $prodOptField
312
                    ->setDescription(_t(
313
                        'Page.OptionsDescrip',
314
                        '<p>Product Options allow products to be customized by attributes such as size or color.
315
                    Options can also modify the product\'s price, weight or code.<br></p>'
316
                    )),
317
            ]);
318
319
            // Images tab
320
            $images = SortableUploadField::create('Images')
321
                ->setSortColumn('SortOrder')
322
                ->setIsMultiUpload(true)
323
                ->setAllowedFileCategories('image')
324
                ->setFolderName('Uploads/Products/Images');
325
326
            $fields->addFieldsToTab('Root.Images', [
327
                $images,
328
            ]);
329
330
            if (FoxyCart::store_name_warning() !== null) {
331
                $fields->addFieldToTab('Root.Main', LiteralField::create('StoreSubDomainHeaderWarning', _t(
332
                    'ProductPage.StoreSubDomainHeaderWarning',
333
                    '<p class="message error">Store sub-domain must be entered in the 
334
                        <a href="/admin/settings/">site settings</a></p>'
335
                )), 'Title');
336
            }
337
        });
338
339
        return parent::getCMSFields();
340
    }
341
342
    /**
343
     * @return RequiredFields
344
     */
345
    public function getCMSValidator()
346
    {
347
        return new RequiredFields(['CategoryID', 'Price', 'Weight', 'Code']);
348
    }
349
350
    /**
351
     * @return \SilverStripe\ORM\ValidationResult
352
     */
353 5
    public function validate()
354
    {
355 5
        $result = parent::validate();
356
357 5
        if (ProductPage::get()->filter('Code', $this->Code)->exclude('ID', $this->ID)->first()) {
358
            $result->addError('Code must be unique for each product.');
359
        }
360
361
        /*if($this->ID>0){
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
362
            if ($this->Price <= 0) {
363
                $result->addError('Price must be a positive value');
364
            }
365
            if($this->Weight <= 0){
366
                $result->error('Must set a positive weight value');
367
            }
368
            if($this->Code == ''){
369
                $result->error('Must set a product code');
370
            }
371
        }*/
372
373 5
        return $result;
374
    }
375
376
    /**
377
     * @return \SilverStripe\ORM\ManyManyList
378
     */
379
    public function getSortedImages()
380
    {
381
        return $this->Images()->Sort('SortOrder');
382
    }
383
384
    /**
385
     * @return \SilverStripe\ORM\ManyManyList
386
     */
387
    public function SortedImages()
388
    {
389
        return $this->getSortedImages();
390
    }
391
392
    /**
393
     * @return Image|bool
394
     */
395
    public function getImage()
396
    {
397
        if ($this->getSortedImages()->count() > 0) {
398
            return $this->getSortedImages()->first();
399
        }
400
401
        return false;
402
    }
403
404
    /**
405
     * @return Image|bool
406
     */
407
    public function Image()
408
    {
409
        return $this->getImage();
410
    }
411
412
    /**
413
     * @throws \Exception
414
     */
415 13
    public function onBeforeWrite()
416
    {
417 13
        parent::onBeforeWrite();
418 13
        if (!$this->CategoryID) {
419
            $default = ProductCategory::get()->filter(['Code' => 'DEFAULT'])->first();
420
            $this->CategoryID = $default->ID;
421
        }
422
423
        //update many_many lists when multi-group is on
424 13
        if (FoxyStripeSetting::current_foxystripe_setting()->MultiGroup) {
425
            $holders = $this->ProductHolders();
426
            $product = self::get()->byID($this->ID);
427
            if (isset($product->ParentID)) {
428
                $origParent = $product->ParentID;
429
            } else {
430
                $origParent = null;
431
            }
432
            $currentParent = $this->ParentID;
433
            if ($origParent != $currentParent) {
434
                if ($holders->find('ID', $origParent)) {
435
                    $holders->removeByID($origParent);
436
                }
437
            }
438
            $holders->add($currentParent);
439
        }
440
441 13
        $this->Title = trim($this->Title);
442 13
        $this->Code = trim($this->Code);
0 ignored issues
show
Documentation Bug introduced by
It seems like trim($this->Code) of type string is incompatible with the declared type SilverStripe\ORM\FieldType\DBVarchar of property $Code.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
443 13
        $this->ReceiptTitle = trim($this->ReceiptTitle);
0 ignored issues
show
Documentation Bug introduced by
It seems like trim($this->ReceiptTitle) of type string is incompatible with the declared type SilverStripe\ORM\FieldType\DBVarchar of property $ReceiptTitle.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
444
    }
445
446 13
    public function onAfterWrite()
447
    {
448 13
        parent::onAfterWrite();
449
    }
450
451 2
    public function onBeforeDelete()
452
    {
453 2
        if ($this->Status != 'Published') {
0 ignored issues
show
Bug Best Practice introduced by
The property Status does not exist on Dynamic\FoxyStripe\Page\ProductPage. Since you implemented __get, consider adding a @property annotation.
Loading history...
454 2
            if ($this->ProductOptions()) {
455 2
                $options = $this->getComponents('ProductOptions');
456 2
                foreach ($options as $option) {
457
                    $option->delete();
458
                }
459
            }
460
        }
461 2
        parent::onBeforeDelete();
462
    }
463
464
    /**
465
     * @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...
466
     * @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...
467
     * @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...
468
     * @param string $method
469
     * @param bool $output
470
     * @param bool $urlEncode
471
     *
472
     * @return null|string
473
     */
474
    public static function getGeneratedValue(
475
        $productCode = null,
476
        $optionName = null,
477
        $optionValue = null,
478
        $method = 'name',
479
        $output = false,
480
        $urlEncode = false
481
    ) {
482
        $optionName = ($optionName !== null) ? preg_replace('/\s/', '_', $optionName) : $optionName;
0 ignored issues
show
introduced by
The condition $optionName !== null is always false.
Loading history...
483
484
        return (FoxyStripeSetting::current_foxystripe_setting()->CartValidation)
485
            ? \FoxyCart_Helper::fc_hash_value($productCode, $optionName, $optionValue, $method, $output, $urlEncode) :
486
            $optionValue;
487
    }
488
489
    /**
490
     * @param Member $member
491
     *
492
     * @return bool
493
     */
494
    public function canEdit($member = null)
495
    {
496
        return Permission::check('Product_CANCRUD', 'any', $member);
497
    }
498
499
    public function canDelete($member = null)
500
    {
501
        return Permission::check('Product_CANCRUD', 'any', $member);
502
    }
503
504
    public function canCreate($member = null, $context = [])
505
    {
506
        return Permission::check('Product_CANCRUD', 'any', $member);
507
    }
508
509
    public function canPublish($member = null)
510
    {
511
        return Permission::check('Product_CANCRUD', 'any', $member);
512
    }
513
514
    public function providePermissions()
515
    {
516
        return [
517
            'Product_CANCRUD' => 'Allow user to manage Products and related objects',
518
        ];
519
    }
520
521
    /**
522
     * @return bool
523
     */
524
    public function getIsAvailable()
525
    {
526
        if (!$this->Available) {
527
            return false;
528
        }
529
530
        if (!$this->ProductOptions()->exists()) {
531
            return true;
532
        }
533
534
        foreach ($this->ProductOptions() as $option) {
535
            if ($option->Available) {
536
                return true;
537
            }
538
        }
539
540
        return false;
541
    }
542
}
543