Completed
Push — master ( 7ec5ba...85071e )
by Matthew
03:39
created

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