Completed
Push — master ( d712bc...aa83e2 )
by Nic
22:59 queued 10:08
created

ProductPage::canPublish()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.125

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 1
cts 2
cp 0.5
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1.125
1
<?php
2
3
/**
4
 *
5
 * @package FoxyStripe
6
 *
7
 */
8
class ProductPage extends Page implements PermissionProvider
9
{
10
11
    private static $allowed_children = 'none';
0 ignored issues
show
Unused Code introduced by
The property $allowed_children is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
12
    private static $default_parent = 'ProductHolder';
0 ignored issues
show
Unused Code introduced by
The property $default_parent is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
13
    private static $can_be_root = false;
0 ignored issues
show
Unused Code introduced by
The property $can_be_root is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
14
15
    /**
16
     * @var array
17
     */
18
    private static $db = array(
19
        'Price' => 'Currency',
20
        'Weight' => 'Decimal',
21
        'Code' => 'Varchar(100)',
22
        'ReceiptTitle' => 'HTMLVarchar(255)',
23
        'Featured' => 'Boolean',
24
        'Available' => 'Boolean',
25
    );
26
27
    /**
28
     * @var array
29
     */
30
    private static $has_one = array(
31
        'PreviewImage' => 'Image',
32
        'Category' => 'ProductCategory'
33
    );
34
35
    private static $has_many = array(
36
        'ProductImages' => 'ProductImage',
37
        'ProductOptions' => 'OptionItem',
38
        'OrderDetails' => 'OrderDetail',
39
    );
40
41
    private static $belongs_many_many = array(
42
        'ProductHolders' => 'ProductHolder'
43
    );
44
45
    private static $singular_name = 'Product';
46
    private static $plural_name = 'Products';
47
    private static $description = 'A product that can be added to the shopping cart';
48
49
    private static $indexes = array(
50
        'Code' => true // make unique
51
    );
52
53
    private static $defaults = array(
54
        'ShowInMenus' => false,
55
        'Available' => true,
56
        'Weight' => '1.0'
57
    );
58
59
    private static $summary_fields = array(
60
        'Title',
61
        'Code',
62
        'Price.Nice',
63
        'Category.Title'
64
    );
65
66
    private static $searchable_fields = array(
67
        'Title',
68
        'Code',
69
        'Featured',
70
        'Available',
71
        'Category.ID'
72
    );
73
74
    function fieldLabels($includerelations = true)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
75
    {
76
        $labels = parent::fieldLabels();
77
78
        $labels['Title'] = _t('ProductPage.TitleLabel', 'Name');
79
        $labels['Code'] = _t('ProductPage.CodeLabel', "Code");
80
        $labels['Price.Nice'] = _t('ProductPage.PriceLabel', 'Price');
81
        $labels['Featured.Nice'] = _t('ProductPage.NiceLabel', 'Featured');
82
        $labels['Available.Nice'] = _t('ProductPage.AvailableLabel', 'Available');
83
        $labels['Category.ID'] = _t('ProductPage.IDLabel', 'Category');
84
        $labels['Category.Title'] = _t('ProductPage.CategoryTitleLabel', 'Category');
85
86
        return $labels;
87
    }
88
89
    public function getCMSFields()
90
    {
91
        $fields = parent::getCMSFields();
92
93
        // allow extensions of ProductPage to override the PreviewImage field description
94
        $previewDescription = ($this->stat('customPreviewDescription')) ? $this->stat('customPreviewDescription') : _t('ProductPage.PreviewImageDescription',
95
            'Image used throughout site to represent this product');
96
97
        // Cateogry Dropdown field w/ add new
98
        $source = function () {
99
            return ProductCategory::get()->map()->toArray();
100
        };
101
        $catField = DropdownField::create('CategoryID', _t('ProductPage.Category', 'FoxyCart Category'), $source())
102
            ->setEmptyString('')
103
            ->setDescription(_t(
104
                'ProductPage.CategoryDescription',
105
                'Required, must also exist in 
106
                    <a href="https://admin.foxycart.com/admin.php?ThisAction=ManageProductCategories" target="_blank">
107
                        FoxyCart Categories
108
                    </a>.
109
                    Used to set category specific options like shipping and taxes. Managed in
110
                        <a href="admin/settings">
111
                            Settings > FoxyStripe > Categories
112
                        </a>'
113
            ));
114
        if (class_exists('QuickAddNewExtension')) {
115
            $catField->useAddNew('ProductCategory', $source);
116
        }
117
118
        // Product Images gridfield
119
        $config = GridFieldConfig_RelationEditor::create();
120
        if (class_exists('GridFieldSortableRows')) {
121
            $config->addComponent(new GridFieldSortableRows('SortOrder'));
122
        }
123
        if (class_exists('GridFieldBulkImageUpload')) {
124
            $config->addComponent(new GridFieldBulkUpload());
125
            $config->getComponentByType('GridFieldBulkUpload')->setUfConfig('folderName', 'Uploads/ProductImages');
126
        }
127
        $prodImagesField = GridField::create(
128
            'ProductImages',
129
            _t('ProductPage.ProductImages', 'Images'),
130
            $this->ProductImages(),
131
            $config
132
        );
133
134
        // Product Options field
135
        $config = GridFieldConfig_RelationEditor::create();
136
        if (class_exists('GridFieldBulkManager')) {
137
            $config->addComponent(new GridFieldBulkManager());
138
        }
139
        if (class_exists('GridFieldSortableRows')) {
140
            $config->addComponent(new GridFieldSortableRows('SortOrder'));
141
            $products = $this->ProductOptions()->sort('SortOrder');
142
        } else {
143
            $products = $this->ProductOptions();
144
        }
145
        $config->removeComponentsByType('GridFieldAddExistingAutocompleter');
146
        $prodOptField = GridField::create(
147
            'ProductOptions',
148
            _t('ProductPage.ProductOptions', 'Options'),
149
            $products,
150
            $config
151
        );
152
153
        // Details tab
154
        $fields->addFieldsToTab('Root.Details', array(
155
            HeaderField::create('DetailHD', 'Product Details', 2),
156
            CheckboxField::create('Available')
157
                ->setTitle(_t('ProductPage.Available', 'Available for purchase'))
158
                ->setDescription(_t(
159
                    'ProductPage.AvailableDescription',
160
                    'If unchecked, will remove "Add to Cart" form and instead display "Currently unavailable"'
161
                )),
162
            TextField::create('Code')
163
                ->setTitle(_t('ProductPage.Code', 'Product Code'))
164
                ->setDescription(_t(
165
                    'ProductPage.CodeDescription',
166
                    'Required, must be unique. Product identifier used by FoxyCart in transactions'
167
                )),
168
            $catField,
169
            CurrencyField::create('Price')
170
                ->setTitle(_t('ProductPage.Price', 'Price'))
171
                ->setDescription(_t(
172
                    'ProductPage.PriceDescription',
173
                    'Base price for this product. Can be modified using Product Options'
174
                )),
175
            NumericField::create('Weight')
176
                ->setTitle(_t('ProductPage.Weight', 'Weight'))
177
                ->setDescription(_t(
178
                    'ProductPage.WeightDescription',
179
                    'Base weight for this product in lbs. Can be modified using Product Options'
180
                )),
181
            CheckboxField::create('Featured')
182
                ->setTitle(_t('ProductPage.Featured', 'Featured Product')),
183
            TextField::create('ReceiptTitle')
184
                ->setTitle(_t('ProductPage.ReceiptTitle', 'Product Title for Receipt'))
185
                ->setDescription(_t(
186
                    'ProductPage.ReceiptTitleDescription', 'Optional'
187
                ))
188
        ));
189
190
        // Images tab
191
        $fields->addFieldsToTab('Root.Images', array(
192
            HeaderField::create('MainImageHD', _t('ProductPage.MainImageHD', 'Product Image'), 2),
193
            UploadField::create('PreviewImage', '')
194
                ->setDescription($previewDescription)
195
                ->setFolderName('Uploads/Products')
196
                ->setAllowedExtensions(array('jpg', 'jpeg', 'gif', 'png'))
197
                ->setAllowedMaxFileNumber(1),
198
            HeaderField::create('ProductImagesHD', _t('ProductPage.ProductImagesHD' . 'Product Image Gallery'), 2),
199
            $prodImagesField
200
                ->setDescription(_t(
201
                    'ProductPage.ProductImagesDescription',
202
                    'Additional Product Images, shown in gallery on Product page'
203
                ))
204
        ));
205
206
        // Options Tab
207
        $fields->addFieldsToTab('Root.Options', array(
208
            HeaderField::create('OptionsHD', _t('ProductPage.OptionsHD', 'Product Options'), 2),
209
            LiteralField::create('OptionsDescrip', _t(
210
                'Page.OptionsDescrip',
211
                '<p>Product Options allow products to be customized by attributes such as size or color.
212
                    Options can also modify the product\'s price, weight or code.</p>'
213
            )),
214
            $prodOptField
215
        ));
216
217 View Code Duplication
        if (FoxyCart::store_name_warning() !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
218
            $fields->addFieldToTab('Root.Main', LiteralField::create("StoreSubDomainHeaderWarning", _t(
219
                'ProductPage.StoreSubDomainHeaderWarning',
220
                "<p class=\"message error\">Store sub-domain must be entered in the <a href=\"/admin/settings/\">site settings</a></p>"
221
            )), 'Title');
222
        }
223
224
        return $fields;
225
    }
226
227
    public function onBeforeWrite()
228
    {
229
        parent::onBeforeWrite();
230 36
        if (!$this->CategoryID) {
231
            $default = ProductCategory::get()->filter(array('Code' => 'DEFAULT'))->first();
232 36
            $this->CategoryID = $default->ID;
233 36
        }
234
235
        //update many_many lists when multi-group is on
236
        if (SiteConfig::current_site_config()->MultiGroup) {
237
            $holders = $this->ProductHolders();
238
            $product = ProductPage::get()->byID($this->ID);
239 36
            if (isset($product->ParentID)) {
240
                $origParent = $product->ParentID;
241
            } else {
242
                $origParent = null;
243
            }
244
            $currentParent = $this->ParentID;
245
            if ($origParent != $currentParent) {
246
                if ($holders->find('ID', $origParent)) {
247
                    $holders->removeByID($origParent);
248
                }
249
250
            }
251
            $holders->add($currentParent);
252
        }
253
254
        $title = ltrim($this->Title);
255
        $title = rtrim($title);
256
        $this->Title = $title;
257 36
258 36
259 36
    }
260
261
    public function onAfterWrite()
262 36
    {
263
        parent::onAfterWrite();
264 36
265
266 36
    }
267
268
    public function onBeforeDelete()
269 36
    {
270
        if ($this->Status != "Published") {
271 1
            if ($this->ProductOptions()) {
272
                $options = $this->getComponents('ProductOptions');
273 1
                foreach ($options as $option) {
274 1
                    $option->delete();
275 1
                }
276 1
            }
277
            if ($this->ProductImages()) {
278 1
                //delete product image dataobjects, not the images themselves.
279 1
                $images = $this->getComponents('ProductImages');
280 1
                foreach ($images as $image) {
281
                    $image->delete();
282 1
                }
283 1
            }
284
        }
285 1
        parent::onBeforeDelete();
286 1
    }
287 1
288 1
    public function validate()
289 1
    {
290
        $result = parent::validate();
291 4
292
        /*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...
293 4
            if($this->Price <= 0) {
294
                $result->error('Must set a positive price value');
295
            }
296
            if($this->Weight <= 0){
297
                $result->error('Must set a positive weight value');
298
            }
299
            if($this->Code == ''){
300
                $result->error('Must set a product code');
301
            }
302
        }*/
303
304
        return $result;
305
    }
306
307 4
    public function getCMSValidator()
308
    {
309
        return new RequiredFields(array('CategoryID', 'Price', 'Weight', 'Code'));
310
    }
311
312
    public static function getGeneratedValue(
313
        $productCode = null,
314
        $optionName = null,
315
        $optionValue = null,
316
        $method = 'name',
317
        $output = false,
318
        $urlEncode = false
319
    ) {
320
        $optionName = ($optionName !== null) ? preg_replace('/\s/', '_', $optionName) : $optionName;
321
        return (SiteConfig::current_site_config()->CartValidation)
322
            ? FoxyCart_Helper::fc_hash_value($productCode, $optionName, $optionValue, $method, $output, $urlEncode) :
323
            $optionValue;
324
    }
325
326
    // get FoxyCart Store Name for JS call
327
    public function getCartScript()
328
    {
329
        return '<script src="https://cdn.foxycart.com/' . FoxyCart::getFoxyCartStoreName() . '/loader.js" async defer></script>';
330
    }
331
332
    /**
333
     * @param Member $member
334
     * @return boolean
335
     */
336
    public function canEdit($member = null)
337
    {
338
        return Permission::check('Product_CANCRUD');
339
    }
340
341
    public function canDelete($member = null)
342
    {
343
        return Permission::check('Product_CANCRUD');
344
    }
345
346
    public function canCreate($member = null)
347
    {
348
        return Permission::check('Product_CANCRUD');
349
    }
350
351
    public function canPublish($member = null)
352
    {
353
        return Permission::check('Product_CANCRUD');
354 4
    }
355
356 4
    public function providePermissions()
357
    {
358
        return array(
359
            'Product_CANCRUD' => 'Allow user to manage Products and related objects'
360
        );
361
    }
362
363
}
364
365
class ProductPage_Controller extends Page_Controller
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
366
{
367
368
    private static $allowed_actions = array(
369
        'PurchaseForm'
370
    );
371
372
    public function init()
373
    {
374
        parent::init();
375
        Requirements::javascript("framework/thirdparty/jquery/jquery.js");
376
        if ($this->data()->Available && $this->ProductOptions()->exists()) {
377
            $formName = $this->PurchaseForm()->FormName();
378
            Requirements::javascriptTemplate(
379
                "foxystripe/javascript/out_of_stock.js",
380
                [
381
                    'FormName' => $formName,
382
                ],
383
                'foxystripe.out_of_stock'
384
            );
385
            Requirements::javascriptTemplate(
386
                'foxystripe/javascript/product_options.js',
387
                [
388
                    'FormName' => $formName,
389
                ],
390
                'foxystripe.product_options'
391
            );
392
        }
393
394
        Requirements::customScript(<<<JS
395
		var productID = {$this->data()->ID};
396
JS
397
        );
398
    }
399
400
    /**
401
     * @return FoxyStripePurchaseForm
402
     */
403
    public function PurchaseForm()
404
    {
405
406
        $form = FoxyStripePurchaseForm::create($this, __FUNCTION__, null, null, null, $this->data());
407
408
        $this->extend('updateFoxyStripePurchaseForm', $form);
409
410
        return $form;
411
412
    }
413
}
414