Completed
Pull Request — master (#358)
by Nic
09:28 queued 05:12
created

ProductPage::onAfterWrite()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

343
        if ($this->/** @scrutinizer ignore-call */ Images()->count() > 0) {
Loading history...
344
            return $this->Images()->first();
345
        }
346
    }
347
348
    public function Image()
349
    {
350
        return $this->getImage();
351
    }
352
353
    /**
354
     * @throws \Exception
355
     */
356 13
    public function onBeforeWrite()
357
    {
358 13
        parent::onBeforeWrite();
359 13
        if (!$this->CategoryID) {
360
            $default = ProductCategory::get()->filter(['Code' => 'DEFAULT'])->first();
361
            $this->CategoryID = $default->ID;
362
        }
363
364
        //update many_many lists when multi-group is on
365 13
        if (FoxyStripeSetting::current_foxystripe_setting()->MultiGroup) {
366
            $holders = $this->ProductHolders();
367
            $product = self::get()->byID($this->ID);
368
            if (isset($product->ParentID)) {
369
                $origParent = $product->ParentID;
370
            } else {
371
                $origParent = null;
372
            }
373
            $currentParent = $this->ParentID;
374
            if ($origParent != $currentParent) {
375
                if ($holders->find('ID', $origParent)) {
376
                    $holders->removeByID($origParent);
377
                }
378
            }
379
            $holders->add($currentParent);
380
        }
381
382 13
        $this->Title = trim($this->Title);
383 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...
384 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...
385
    }
386
387 13
    public function onAfterWrite()
388
    {
389 13
        parent::onAfterWrite();
390
    }
391
392 2
    public function onBeforeDelete()
393
    {
394 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...
395 2
            if ($this->ProductOptions()) {
396 2
                $options = $this->getComponents('ProductOptions');
397 2
                foreach ($options as $option) {
398
                    $option->delete();
399
                }
400
            }
401
        }
402 2
        parent::onBeforeDelete();
403
    }
404
405 5
    public function validate()
406
    {
407 5
        $result = parent::validate();
408
409
        /*if($this->ID>0){
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% 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...
410
            if($this->Price <= 0) {
411
                $result->error('Must set a positive price value');
412
            }
413
            if($this->Weight <= 0){
414
                $result->error('Must set a positive weight value');
415
            }
416
            if($this->Code == ''){
417
                $result->error('Must set a product code');
418
            }
419
        }*/
420
421 5
        return $result;
422
    }
423
424
    public function getCMSValidator()
425
    {
426
        return new RequiredFields(['CategoryID', 'Price', 'Weight', 'Code']);
427
    }
428
429
    /**
430
     * @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...
431
     * @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...
432
     * @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...
433
     * @param string $method
434
     * @param bool $output
435
     * @param bool $urlEncode
436
     *
437
     * @return null|string
438
     */
439
    public static function getGeneratedValue(
440
        $productCode = null,
441
        $optionName = null,
442
        $optionValue = null,
443
        $method = 'name',
444
        $output = false,
445
        $urlEncode = false
446
    ) {
447
        $optionName = ($optionName !== null) ? preg_replace('/\s/', '_', $optionName) : $optionName;
0 ignored issues
show
introduced by
The condition $optionName !== null is always false.
Loading history...
448
449
        return (FoxyStripeSetting::current_foxystripe_setting()->CartValidation)
450
            ? \FoxyCart_Helper::fc_hash_value($productCode, $optionName, $optionValue, $method, $output, $urlEncode) :
451
            $optionValue;
452
    }
453
454
    /**
455
     * @param Member $member
456
     *
457
     * @return bool
458
     */
459
    public function canEdit($member = null)
460
    {
461
        return Permission::check('Product_CANCRUD', 'any', $member);
462
    }
463
464
    public function canDelete($member = null)
465
    {
466
        return Permission::check('Product_CANCRUD', 'any', $member);
467
    }
468
469
    public function canCreate($member = null, $context = [])
470
    {
471
        return Permission::check('Product_CANCRUD', 'any', $member);
472
    }
473
474
    public function canPublish($member = null)
475
    {
476
        return Permission::check('Product_CANCRUD', 'any', $member);
477
    }
478
479
    public function providePermissions()
480
    {
481
        return [
482
            'Product_CANCRUD' => 'Allow user to manage Products and related objects',
483
        ];
484
    }
485
}
486