Completed
Pull Request — master (#374)
by Jason
14:28
created

ProductPage::populateDefaults()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 5
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
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 $summary_fields = [
0 ignored issues
show
introduced by
The private property $summary_fields is not used, and could be removed.
Loading history...
154
        'Image.CMSThumbnail',
155
        'Title',
156
        'Code',
157
        'Price.Nice',
158
        'Category.Title',
159
    ];
160
161
    /**
162
     * @var array
163
     */
164
    private static $searchable_fields = [
0 ignored issues
show
introduced by
The private property $searchable_fields is not used, and could be removed.
Loading history...
165
        'Title',
166
        'Code',
167
        'Available',
168
        'Category.ID',
169
    ];
170
171
    /**
172
     * @var string
173
     */
174
    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...
175
176
    /**
177
     * @var array
178
     */
179
    private static $defaults = [
0 ignored issues
show
introduced by
The private property $defaults is not used, and could be removed.
Loading history...
180
        'ShowInMenus' => false,
181
        'Available' => true,
182
        'Weight' => '0.0',
183
    ];
184
185
    /**
186
     * @return \SilverStripe\ORM\DataObject|void
187
     */
188
    public function populateDefaults()
189
    {
190
        $this->CategoryID = ProductCategory::get()->filter('Code', 'DEFAULT')->first()->ID;
191
192
        parent::populateDefaults();
193
    }
194
195 14
    /**
196
     * Construct a new ProductPage.
197 14
     *
198
     * @param array|null $record Used internally for rehydrating an object from database content.
199 14
     *                           Bypasses setters on this class, and hence should not be used
200
     *                           for populating data on new records.
201
     * @param boolean $isSingleton This this to true if this is a singleton() object, a stub for calling methods.
202
     *                             Singletons don't have their defaults set.
203
     * @param array $queryParams List of DataQuery params necessary to lazy load, or load related objects.
204
     */
205
    public function __construct($record = null, $isSingleton = false, $queryParams = [])
206
    {
207
        parent::__construct($record, $isSingleton, $queryParams);
208
209
        if (Controller::curr() instanceof ContentController) {
210
            Requirements::javascript('dynamic/foxystripe: javascript/quantity.js');
211
            Requirements::css('dynamic/foxystripe: client/dist/css/quantityfield.css');
212
        }
213
    }
214
215
    /**
216
     * @param bool $includerelations
217
     *
218
     * @return array
219
     */
220
    public function fieldLabels($includerelations = true)
221
    {
222
        $labels = parent::fieldLabels();
223
224
        $labels['Title'] = _t('ProductPage.TitleLabel', 'Product Name');
225
        $labels['Code'] = _t('ProductPage.CodeLabel', 'Code');
226
        $labels['Price.Nice'] = _t('ProductPage.PriceLabel', 'Price');
227
        $labels['Available.Nice'] = _t('ProductPage.AvailableLabel', 'Available');
228
        $labels['Category.ID'] = _t('ProductPage.IDLabel', 'Category');
229
        $labels['Category.Title'] = _t('ProductPage.CategoryTitleLabel', 'Category');
230
        $labels['Image.CMSThumbnail'] = _t('ProductPage.ImageLabel', 'Image');
231
232
        return $labels;
233
    }
234
235
    /**
236
     * @return \SilverStripe\Forms\FieldList
237
     */
238
    public function getCMSFields()
239
    {
240
        $this->beforeUpdateCMSFields(function (FieldList $fields) {
241
            $fields->addFieldsToTab(
242
                'Root.Main',
243
                [
244
                    TextField::create('Code')
245
                        ->setTitle(_t('ProductPage.Code', 'Product Code'))
246
                        ->setDescription(_t(
247
                            'ProductPage.CodeDescription',
248
                            'Required, must be unique. Product identifier used by FoxyCart in transactions'
249
                        )),
250
                    CurrencyField::create('Price')
251
                        ->setTitle(_t('ProductPage.Price', 'Price'))
252
                        ->setDescription(_t(
253
                            'ProductPage.PriceDescription',
254
                            'Base price for this product. Can be modified using Product Options'
255
                        )),
256
                    NumericField::create('Weight')
257
                        ->setTitle(_t('ProductPage.Weight', 'Weight'))
258
                        ->setDescription(_t(
259
                            'ProductPage.WeightDescription',
260
                            'Base weight for this product in lbs. Can be modified using Product Options'
261
                        ))
262
                        ->setScale(2),
263
                    DropdownField::create(
264
                        'CategoryID',
265
                        _t('ProductPage.Category', 'Product Category'),
266
                        ProductCategory::get()->map()->toArray()
267
                    )
268
                        ->setDescription(_t(
269
                            'ProductPage.CategoryDescription',
270
                            'Required, must also exist in 
271
                    <a href="https://admin.foxycart.com/admin.php?ThisAction=ManageProductCategories" target="_blank">
272
                        FoxyCart Categories
273
                    </a>.
274
                    Used to set category specific options like shipping and taxes. Managed in
275
                        <a href="admin/foxystripe">
276
                            FoxyStripe > Categories
277
                        </a>'
278
                        )),
279
                ],
280
                'Content'
281
            );
282
283
            // Product Options field
284
            $config = GridFieldConfig_RelationEditor::create();
285
            $config->addComponent(new GridFieldOrderableRows('SortOrder'));
286
            $products = $this->ProductOptions()->sort('SortOrder');
287
            $config->removeComponentsByType(GridFieldAddExistingAutocompleter::class);
288
            $prodOptField = GridField::create(
289
                'ProductOptions',
290
                _t('ProductPage.ProductOptions', 'Options'),
291
                $products,
292
                $config
293
            );
294
295
            // Details tab
296
            $fields->addFieldsToTab('Root.Details', [
297
                CheckboxField::create('Available')
298
                    ->setTitle(_t('ProductPage.Available', 'Available for purchase'))
299
                    ->setDescription(_t(
300
                        'ProductPage.AvailableDescription',
301
                        'If unchecked, will remove "Add to Cart" form and instead display "Currently unavailable"'
302
                    )),
303
                TextField::create('ReceiptTitle')
304
                    ->setTitle(_t('ProductPage.ReceiptTitle', 'Product Title for Receipt'))
305
                    ->setDescription(_t(
306
                        'ProductPage.ReceiptTitleDescription',
307
                        'Optional'
308
                    )),
309
            ]);
310
311
            // Options Tab
312
            $fields->addFieldsToTab('Root.Options', [
313
                $prodOptField
314
                    ->setDescription(_t(
315
                        'Page.OptionsDescrip',
316
                        '<p>Product Options allow products to be customized by attributes such as size or color.
317
                    Options can also modify the product\'s price, weight or code.<br></p>'
318
                    )),
319
            ]);
320
321
            // Images tab
322
            $images = SortableUploadField::create('Images')
323
                ->setSortColumn('SortOrder')
324
                ->setIsMultiUpload(true)
325
                ->setAllowedFileCategories('image')
326
                ->setFolderName('Uploads/Products/Images');
327
328
            $fields->addFieldsToTab('Root.Images', [
329
                $images,
330
            ]);
331
332
            if (FoxyCart::store_name_warning() !== null) {
333
                $fields->addFieldToTab('Root.Main', LiteralField::create('StoreSubDomainHeaderWarning', _t(
334
                    'ProductPage.StoreSubDomainHeaderWarning',
335
                    '<p class="message error">Store sub-domain must be entered in the 
336
                        <a href="/admin/settings/">site settings</a></p>'
337
                )), 'Title');
338
            }
339
        });
340
341
        $fields = parent::getCMSFields();
342
343
        $fields->dataFieldByName('Content')->setTitle('Description');
344
345
        return $fields;
346
    }
347
348
    /**
349
     * @return RequiredFields
350
     */
351 5
    public function getCMSValidator()
352
    {
353 5
        return new RequiredFields(['CategoryID', 'Price', 'Code']);
354
    }
355 5
356
    /**
357
     * @return \SilverStripe\ORM\ValidationResult
358
     */
359
    public function validate()
360
    {
361
        $result = parent::validate();
362
363
        if (ProductPage::get()->filter('Code', $this->Code)->exclude('ID', $this->ID)->first()) {
364
            $result->addError('Code must be unique for each product.');
365
        }
366
367
        /*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...
368
            if ($this->Price <= 0) {
369
                $result->addError('Price must be a positive value');
370
            }
371 5
            if($this->Weight <= 0){
372
                $result->error('Must set a positive weight value');
373
            }
374
            if($this->Code == ''){
375
                $result->error('Must set a product code');
376
            }
377
        }*/
378
379
        return $result;
380
    }
381
382
    /**
383
     * @return \SilverStripe\ORM\ManyManyList
384
     */
385
    public function getSortedImages()
386
    {
387
        return $this->Images()->Sort('SortOrder');
388
    }
389
390
    /**
391
     * @return \SilverStripe\ORM\ManyManyList
392
     */
393
    public function SortedImages()
394
    {
395
        return $this->getSortedImages();
396
    }
397
398
    /**
399
     * @return Image|bool
400
     */
401
    public function getImage()
402
    {
403
        if ($this->getSortedImages()->count() > 0) {
404
            return $this->getSortedImages()->first();
405
        }
406
        return false;
407
    }
408
409
    /**
410
     * @return Image|bool
411
     */
412 13
    public function Image()
413
    {
414 13
        return $this->getImage();
415 13
    }
416
417
    /**
418
     * @throws \Exception
419
     */
420
    public function onBeforeWrite()
421 13
    {
422
        parent::onBeforeWrite();
423
        if (!$this->CategoryID) {
424
            $default = ProductCategory::get()->filter(['Code' => 'DEFAULT'])->first();
425
            $this->CategoryID = $default->ID;
426
        }
427
428
        //update many_many lists when multi-group is on
429
        if (FoxyStripeSetting::current_foxystripe_setting()->MultiGroup) {
430
            $holders = $this->ProductHolders();
431
            $product = self::get()->byID($this->ID);
432
            if (isset($product->ParentID)) {
433
                $origParent = $product->ParentID;
434
            } else {
435
                $origParent = null;
436
            }
437
            $currentParent = $this->ParentID;
438 13
            if ($origParent != $currentParent) {
439 13
                if ($holders->find('ID', $origParent)) {
440 13
                    $holders->removeByID($origParent);
441
                }
442
            }
443 13
            $holders->add($currentParent);
444
        }
445 13
446
        $this->Title = trim($this->Title);
447
        $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...
448 2
        $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...
449
    }
450 2
451 2
    public function onAfterWrite()
452 2
    {
453 2
        parent::onAfterWrite();
454
    }
455
456
    public function onBeforeDelete()
457
    {
458 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...
459
            if ($this->ProductOptions()) {
460
                $options = $this->getComponents('ProductOptions');
461
                foreach ($options as $option) {
462
                    $option->delete();
463
                }
464
            }
465
        }
466
        parent::onBeforeDelete();
467
    }
468
469
    /**
470
     * @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...
471
     * @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...
472
     * @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...
473
     * @param string $method
474
     * @param bool $output
475
     * @param bool $urlEncode
476
     *
477
     * @return null|string
478
     */
479
    public static function getGeneratedValue(
480
        $productCode = null,
481
        $optionName = null,
482
        $optionValue = null,
483
        $method = 'name',
484
        $output = false,
485
        $urlEncode = false
486
    ) {
487
        $optionName = ($optionName !== null) ? preg_replace('/\s/', '_', $optionName) : $optionName;
0 ignored issues
show
introduced by
The condition $optionName !== null is always false.
Loading history...
488
489
        return (FoxyStripeSetting::current_foxystripe_setting()->CartValidation)
490
            ? \FoxyCart_Helper::fc_hash_value($productCode, $optionName, $optionValue, $method, $output, $urlEncode) :
491
            $optionValue;
492
    }
493
494
    /**
495
     * @param Member $member
496
     *
497
     * @return bool
498
     */
499
    public function canEdit($member = null)
500
    {
501
        return Permission::check('Product_CANCRUD', 'any', $member);
502
    }
503
504
    public function canDelete($member = null)
505
    {
506
        return Permission::check('Product_CANCRUD', 'any', $member);
507
    }
508
509
    public function canCreate($member = null, $context = [])
510
    {
511
        return Permission::check('Product_CANCRUD', 'any', $member);
512
    }
513
514
    public function canPublish($member = null)
515
    {
516
        return Permission::check('Product_CANCRUD', 'any', $member);
517
    }
518
519
    public function providePermissions()
520
    {
521
        return [
522
            'Product_CANCRUD' => 'Allow user to manage Products and related objects',
523
        ];
524
    }
525
}
526