Completed
Pull Request — master (#369)
by Nic
03:37
created

ProductPage::getImage()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 6
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 PreviewImageID
48
 * @method Image PreviewImage
49
 * @property int CategoryID
50
 * @method ProductCategory Category
51
 *
52
 *
53
 * @method \SilverStripe\ORM\HasManyList ProductImages
54
 * @method \SilverStripe\ORM\HasManyList ProductOptions
55
 * @method \SilverStripe\ORM\HasManyList OrderDetails
56
 *
57
 * @method \SilverStripe\ORM\ManyManyList ProductHolders
58
 */
59
class ProductPage extends \Page implements PermissionProvider
60
{
61
    /**
62
     * @var string
63
     */
64
    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...
65
66
    /**
67
     * @var bool
68
     */
69
    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...
70
71
    /**
72
     * @var array
73
     */
74
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
75
        'Price' => 'Currency',
76
        'Weight' => 'Decimal',
77
        'Code' => 'Varchar(100)',
78
        'ReceiptTitle' => 'HTMLVarchar(255)',
79
        'Available' => 'Boolean',
80
    ];
81
82
    /**
83
     * @var array
84
     */
85
    private static $has_one = [
0 ignored issues
show
introduced by
The private property $has_one is not used, and could be removed.
Loading history...
86
        'Category' => ProductCategory::class,
87
    ];
88
89
    /**
90
     * @var array
91
     */
92
    private static $has_many = [
0 ignored issues
show
introduced by
The private property $has_many is not used, and could be removed.
Loading history...
93
        'ProductOptions' => OptionItem::class,
94
        'OrderDetails' => OrderDetail::class,
95
    ];
96
97
    /**
98
     * @var array
99
     */
100
    private static $many_many = [
0 ignored issues
show
introduced by
The private property $many_many is not used, and could be removed.
Loading history...
101
        'Images' => Image::class,
102
    ];
103
104
    /**
105
     * @var array
106
     */
107
    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...
108
        'Images' => [
109
            'SortOrder' => 'Int',
110
        ],
111
    ];
112
113
    /**
114
     * @var array
115
     */
116
    private static $owns = [
0 ignored issues
show
introduced by
The private property $owns is not used, and could be removed.
Loading history...
117
        'Images',
118
    ];
119
120
    /**
121
     * @var array
122
     */
123
    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...
124
        'ProductHolders' => ProductHolder::class,
125
    ];
126
127
    /**
128
     * @var string
129
     */
130
    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...
131
132
    /**
133
     * @var string
134
     */
135
    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...
136
137
    /**
138
     * @var string
139
     */
140
    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...
141
142
    /**
143
     * @var array
144
     */
145
    private static $indexes = [
0 ignored issues
show
introduced by
The private property $indexes is not used, and could be removed.
Loading history...
146
        'Code' => true, // make unique
147
    ];
148
149
    /**
150
     * @var array
151
     */
152
    private static $defaults = [
0 ignored issues
show
introduced by
The private property $defaults is not used, and could be removed.
Loading history...
153
        'ShowInMenus' => false,
154
        'Available' => true,
155
        'Weight' => '1.0',
156
    ];
157
158
    /**
159
     * @var array
160
     */
161
    private static $summary_fields = [
0 ignored issues
show
introduced by
The private property $summary_fields is not used, and could be removed.
Loading history...
162
        'Image.CMSThumbnail',
163
        'Title',
164
        'Code',
165
        'Price.Nice',
166
        'Category.Title',
167
    ];
168
169
    /**
170
     * @var array
171
     */
172
    private static $searchable_fields = [
0 ignored issues
show
introduced by
The private property $searchable_fields is not used, and could be removed.
Loading history...
173
        'Title',
174
        'Code',
175
        'Available',
176
        'Category.ID',
177
    ];
178
179
    /**
180
     * @var string
181
     */
182
    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...
183
184
    /**
185
     * Construct a new ProductPage.
186
     *
187
     * @param array|null $record Used internally for rehydrating an object from database content.
188
     *                           Bypasses setters on this class, and hence should not be used
189
     *                           for populating data on new records.
190
     * @param boolean $isSingleton This this to true if this is a singleton() object, a stub for calling methods.
191
     *                             Singletons don't have their defaults set.
192
     * @param array $queryParams List of DataQuery params necessary to lazy load, or load related objects.
193
     */
194 14
    public function __construct($record = null, $isSingleton = false, $queryParams = [])
195
    {
196 14
        parent::__construct($record, $isSingleton, $queryParams);
197
198 14
        if (Controller::curr() instanceof ContentController) {
199
            Requirements::javascript('dynamic/foxystripe: javascript/quantity.js');
200
            Requirements::css('dynamic/foxystripe: client/dist/css/quantityfield.css');
201
        }
202
    }
203
204
    /**
205
     * @param bool $includerelations
206
     *
207
     * @return array
208
     */
209
    public function fieldLabels($includerelations = true)
210
    {
211
        $labels = parent::fieldLabels();
212
213
        $labels['Title'] = _t('ProductPage.TitleLabel', 'Name');
214
        $labels['Code'] = _t('ProductPage.CodeLabel', 'Code');
215
        $labels['Price.Nice'] = _t('ProductPage.PriceLabel', 'Price');
216
        $labels['Available.Nice'] = _t('ProductPage.AvailableLabel', 'Available');
217
        $labels['Category.ID'] = _t('ProductPage.IDLabel', 'Category');
218
        $labels['Category.Title'] = _t('ProductPage.CategoryTitleLabel', 'Category');
219
        $labels['Image.CMSThumbnail'] = _t('ProductPage.ImageLabel', 'Image');
220
221
        return $labels;
222
    }
223
224
    /**
225
     * @return \SilverStripe\Forms\FieldList
226
     */
227
    public function getCMSFields()
228
    {
229
        $this->beforeUpdateCMSFields(function (FieldList $fields) {
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
336
        return parent::getCMSFields();
337
    }
338
339
    /**
340
     * @return mixed
341
     */
342
    public function getImage()
343
    {
344
        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

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