Completed
Push — master ( df2857...2b4027 )
by Nic
05:19
created

ProductPage::Image()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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