ProductPage::__construct()   A
last analyzed

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 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 8
ccs 4
cts 6
cp 0.6667
rs 10
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){
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