Completed
Push — master ( a140bd...5dca79 )
by Nicolaas
03:30
created

Product_Controller::PreviousProduct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
eloc 9
nc 3
nop 0
1
<?php
2
/**
3
 * This is a standard Product page-type with fields like
4
 * Price, Weight, Model and basic management of
5
 * groups.
6
 *
7
 * It also has an associated Product_OrderItem class,
8
 * an extension of OrderItem, which is the mechanism
9
 * that links this page type class to the rest of the
10
 * eCommerce platform. This means you can add an instance
11
 * of this page type to the shopping cart.
12
 *
13
 *
14
 * @authors: Nicolaas [at] Sunny Side Up .co.nz
15
 * @package: ecommerce
16
 * @sub-package: buyables
17
 * @inspiration: Silverstripe Ltd, Jeremy
18
 * @todo: Ask the silverstripe gods why $default_sort won't work with FullSiteTreeSort
19
 **/
20
class Product extends Page implements BuyableModel
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
21
{
22
    /**
23
     * Standard SS variable.
24
     */
25
    private static $api_access = array(
0 ignored issues
show
Unused Code introduced by
The property $api_access 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...
26
        'view' => array(
27
            'Title',
28
            'Price',
29
            'Weight',
30
            'Model',
31
            'Quantifier',
32
            'FeaturedProduct',
33
            'AllowPurchase',
34
            'InternalItemID', //ie SKU, ProductID etc (internal / existing recognition of product)
35
            'NumberSold', //store number sold, so it doesn't have to be computed on the fly. Used for determining popularity.
36
            'Version',
37
        ),
38
    );
39
40
    /**
41
     * Standard SS variable.
42
     */
43
    private static $db = array(
0 ignored issues
show
Unused Code introduced by
The property $db 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...
44
        'Price' => 'Currency',
45
        'Weight' => 'Float',
46
        'Model' => 'Varchar(30)',
47
        'Quantifier' => 'Varchar(30)',
48
        'FeaturedProduct' => 'Boolean',
49
        'AllowPurchase' => 'Boolean',
50
        'InternalItemID' => 'Varchar(30)', //ie SKU, ProductID etc (internal / existing recognition of product)
51
        'NumberSold' => 'Int', //store number sold, so it doesn't have to be computed on the fly. Used for determining popularity.
52
        'FullSiteTreeSort' => 'Decimal(64, 0)', //store the complete sort numbers from current page up to level 1 page, for sitetree sorting
53
        'FullName' => 'Varchar(255)', //Name for look-up lists
54
        'ShortDescription' => 'Varchar(255)', //For use in lists.
55
    );
56
57
    /**
58
     * Standard SS variable.
59
     */
60
    private static $has_one = array(
0 ignored issues
show
Unused Code introduced by
The property $has_one 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...
61
        'Image' => 'Product_Image',
62
    );
63
64
    /**
65
     * Standard SS variable.
66
     */
67
    private static $many_many = array(
0 ignored issues
show
Unused Code introduced by
The property $many_many 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...
68
        'ProductGroups' => 'ProductGroup',
69
        'AdditionalImages' => 'Image',
70
        'AdditionalFiles' => 'File',
71
    );
72
73
    /**
74
     * Standard SS variable.
75
     */
76
    private static $casting = array(
0 ignored issues
show
Unused Code introduced by
The property $casting 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...
77
        'CalculatedPrice' => 'Currency',
78
        'CalculatedPriceAsMoney' => 'Money',
79
        'AllowPurchaseNice' => 'Varchar',
80
    );
81
82
    /**
83
     * Standard SS variable.
84
     */
85
    private static $indexes = array(
0 ignored issues
show
Unused Code introduced by
The property $indexes 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...
86
        'FullSiteTreeSort' => true,
87
        'FullName' => true,
88
        'InternalItemID' => true,
89
    );
90
91
    /**
92
     * Standard SS variable.
93
     */
94
    private static $defaults = array(
0 ignored issues
show
Unused Code introduced by
The property $defaults 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...
95
        'AllowPurchase' => 1,
96
    );
97
98
    /**
99
     * Standard SS variable.
100
     */
101
    //private static $default_sort = "\"FullSiteTreeSort\" ASC, \"Sort\" ASC, \"InternalItemID\" ASC, \"Price\" ASC";
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
102
    //private static $default_sort = "\"Sort\" ASC, \"InternalItemID\" ASC, \"Price\" ASC";
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
103
104
    /**
105
     * Standard SS variable.
106
     */
107
    private static $summary_fields = array(
0 ignored issues
show
Unused Code introduced by
The property $summary_fields 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...
108
        'Image.CMSThumbnail' => 'Image',
109
        'FullName' => 'Description',
110
        'Price' => 'Price',
111
        'AllowPurchaseNice' => 'For Sale',
112
    );
113
114
    /**
115
     * Standard SS variable.
116
     */
117
    private static $searchable_fields = array(
0 ignored issues
show
Unused Code introduced by
The property $searchable_fields 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...
118
        'FullName' => array(
119
            'title' => 'Keyword',
120
            'field' => 'TextField',
121
        ),
122
        'Price' => array(
123
            'title' => 'Price',
124
            'field' => 'NumericField',
125
        ),
126
        'InternalItemID' => array(
127
            'title' => 'Internal Item ID',
128
            'filter' => 'PartialMatchFilter',
129
        ),
130
        'AllowPurchase',
131
        'ShowInSearch',
132
        'ShowInMenus',
133
        'FeaturedProduct',
134
    );
135
136
    /**
137
     * By default we search for products that are allowed to be purchased only
138
     * standard SS method.
139
     *
140
     * @return FieldList
141
     */
142
    public function scaffoldSearchFields($_params = null)
143
    {
144
        $fields = parent::scaffoldSearchFields($_params);
145
        $fields->fieldByName('AllowPurchase')->setValue(1);
146
147
        return $fields;
148
    }
149
150
    /**
151
     * Standard SS variable.
152
     */
153
    private static $singular_name = 'Product';
0 ignored issues
show
Unused Code introduced by
The property $singular_name 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...
154
    public function i18n_singular_name()
155
    {
156
        return _t('Order.PRODUCT', 'Product');
157
    }
158
159
    /**
160
     * Standard SS variable.
161
     */
162
    private static $plural_name = 'Products';
0 ignored issues
show
Unused Code introduced by
The property $plural_name 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...
163
    public function i18n_plural_name()
164
    {
165
        return _t('Order.PRODUCTS', 'Products');
166
    }
167
168
    /**
169
     * Standard SS variable.
170
     *
171
     * @var string
172
     */
173
    private static $description = 'A product that is for sale in the shop.';
0 ignored issues
show
Unused Code introduced by
The property $description 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...
174
175
    /**
176
     * Standard SS variable.
177
     */
178
    private static $default_parent = 'ProductGroup';
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...
179
180
    /**
181
     * Standard SS variable.
182
     */
183
    private static $icon = 'ecommerce/images/icons/product';
0 ignored issues
show
Unused Code introduced by
The property $icon 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...
184
185
    /**
186
     * Standard SS Method.
187
     */
188
    public function getCMSFields()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
189
    {
190
        //prevent calling updateSettingsFields extend function too early
191
        //$siteTreeFieldExtensions = $this->get_static('SiteTree','runCMSFieldsExtensions');
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% 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...
192
        //$this->disableCMSFieldsExtensions();
0 ignored issues
show
Unused Code Comprehensibility introduced by
84% 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...
193
        $fields = parent::getCMSFields();
194
        if ($this->Config()->get('add_data_to_meta_description_for_search')) {
195
            $fields->removeByName('MetaDescription');
196
        }
197
        //if($siteTreeFieldExtensions) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
84% 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...
198
            //$this->enableCMSFieldsExtensions();
0 ignored issues
show
Unused Code Comprehensibility introduced by
84% 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...
199
        //}
200
        $fields->replaceField('Root.Main', $htmlEditorField = new HTMLEditorField('Content', _t('Product.DESCRIPTION', 'Product Description')));
201
        $htmlEditorField->setRows(3);
202
        $fields->addFieldToTab('Root.Main', new TextField('ShortDescription', _t('Product.SHORT_DESCRIPTION', 'Short Description')), 'Content');
203
        //dirty hack to show images!
204
        $fields->addFieldToTab('Root.Images', $uploadField = new Product_ProductImageUploadField('Image', _t('Product.IMAGE', 'Product Image')));
205
        $uploadField->setCallingClass('Product');
206
        $fields->addFieldToTab('Root.Images', $this->getAdditionalImagesField());
207
        $fields->addFieldToTab('Root.Images', $this->getAdditionalImagesMessage());
208
        $fields->addFieldToTab('Root.Images', $this->getAdditionalFilesField());
209
        $fields->addFieldToTab('Root.Details', new ReadonlyField('FullName', _t('Product.FULLNAME', 'Full Name')));
210
        $fields->addFieldToTab('Root.Details', new ReadOnlyField('FullSiteTreeSort', _t('Product.FULLSITETREESORT', 'Full sort index')));
211
        $fields->addFieldToTab('Root.Details', $allowPurchaseField = new CheckboxField('AllowPurchase', _t('Product.ALLOWPURCHASE', 'Allow product to be purchased')));
212
        $config = $this->EcomConfig();
213
        if ($config && !$config->AllowFreeProductPurchase) {
0 ignored issues
show
Documentation introduced by
The property AllowFreeProductPurchase does not exist on object<EcommerceDBConfig>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
214
            $price = $this->getCalculatedPrice();
215
            if ($price == 0) {
216
                $link = $config->CMSEditLink();
217
                $allowPurchaseField->setDescription(
218
                    _t('Product.DO_NOT_ALLOW_FREE_PRODUCTS_TO_BE_PURCHASED',
219
                        "NB: Allow Purchase + zero price is not allowed.  Change the <a href=\"$link\">Shop Settings</a> to allow a zero price product purchases or set price on this product."
220
                    )
221
                );
222
            }
223
        }
224
225
        $fields->addFieldToTab('Root.Details', new CheckboxField('FeaturedProduct', _t('Product.FEATURED', 'Featured Product')));
226
        $fields->addFieldToTab('Root.Details', new NumericField('Price', _t('Product.PRICE', 'Price'), '', 12));
227
        $fields->addFieldToTab('Root.Details', new TextField('InternalItemID', _t('Product.CODE', 'Product Code'), '', 30));
228
        if ($this->EcomConfig()->ProductsHaveWeight) {
0 ignored issues
show
Documentation introduced by
The property ProductsHaveWeight does not exist on object<EcommerceDBConfig>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
229
            $fields->addFieldToTab('Root.Details', new NumericField('Weight', _t('Product.WEIGHT', 'Weight')));
230
        }
231
        if ($this->EcomConfig()->ProductsHaveModelNames) {
0 ignored issues
show
Documentation introduced by
The property ProductsHaveModelNames does not exist on object<EcommerceDBConfig>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
232
            $fields->addFieldToTab('Root.Details', new TextField('Model', _t('Product.MODEL', 'Model')));
233
        }
234
        if ($this->EcomConfig()->ProductsHaveQuantifiers) {
0 ignored issues
show
Documentation introduced by
The property ProductsHaveQuantifiers does not exist on object<EcommerceDBConfig>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
235
            $fields->addFieldToTab(
236
                'Root.Details',
237
                TextField::create('Quantifier', _t('Product.QUANTIFIER', 'Quantifier'))
238
                    ->setRightTitle(_t('Product.QUANTIFIER_EXPLANATION', 'e.g. per kilo, per month, per dozen, each'))
239
            );
240
        }
241
        if ($this->canPurchase()) {
242
            $fields->addFieldToTab(
243
                'Root.Main',
244
                new LiteralField(
245
                    'AddToCartLink',
246
                    '<p class="message good"><a href="'.$this->AddLink().'">'._t('Product.ADD_TO_CART', 'add to cart').'</a></p>'
247
                )
248
            );
249
        } else {
250
            $fields->addFieldToTab(
251
                'Root.Main',
252
                new LiteralField(
253
                    'AddToCartLink',
254
                    '<p class="message warning">'._t('Product.CAN_NOT_BE_ADDED_TO_CART', 'this product can not be added to cart').'</p>'
255
                )
256
            );
257
        }
258
        if ($this->EcomConfig()->ProductsAlsoInOtherGroups) {
0 ignored issues
show
Documentation introduced by
The property ProductsAlsoInOtherGroups does not exist on object<EcommerceDBConfig>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
259
            $fields->addFieldsToTab(
260
                'Root.AlsoShowHere',
261
                array(
262
                    new HeaderField('ProductGroupsHeader', _t('Product.ALSOSHOWSIN', 'Also shows in ...')),
263
                    $this->getProductGroupsTableField(),
264
                )
265
            );
266
        }
267
        //if($siteTreeFieldExtensions) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
84% 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...
268
            //$this->extend('updateSettingsFields', $fields);
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% 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...
269
        //}
270
        return $fields;
271
    }
272
273
    /**
274
     * Used in getCSMFields.
275
     *
276
     * @return GridField
277
     **/
278
    protected function getProductGroupsTableField()
279
    {
280
        $gridField = new GridField(
281
            'ProductGroups',
282
            _t('Product.THIS_PRODUCT_SHOULD_ALSO_BE_LISTED_UNDER', 'This product is also listed under ...'),
283
            $this->ProductGroups(),
284
            GridFieldBasicPageRelationConfig::create()
285
        );
286
287
        return $gridField;
288
    }
289
290
    /**
291
     * Used in getCSMFields.
292
     *
293
     * @return LiteralField
294
     **/
295
    protected function getAdditionalImagesMessage()
296
    {
297
        $msg = '';
298
        if ($this->InternalItemID) {
299
            $findImagesTask = EcommerceTaskLinkProductWithImages::create();
300
            $findImagesLink = $findImagesTask->Link();
301
            $findImagesLinkOne = $findImagesLink.'?productid='.$this->ID;
302
            $msg .= '
303
                <h3>Batch Upload</h3>
304
                <p>
305
                To batch upload additional images and files, please go to the <a href="/admin/assets">Files section</a>, and upload them there.
306
                Files need to be named in the following way:
307
                An additional image for your product should be named &lt;Product Code&gt;_(00 to 99).(png/jpg/gif). <br />For example, you may name your image:
308
                <strong>'.$this->InternalItemID."_08.jpg</strong>.
309
                <br /><br />You can <a href=\"$findImagesLinkOne\" target='_blank'>find images for <i>".$this->Title."</i></a> or
310
                <a href=\"$findImagesLink\" target='_blank'>images for all products</a> ...
311
            </p>";
312
        } else {
313
            $msg .= '
314
            <h3>Batch Upload</h3>
315
            <p>To batch upload additional images and files, you must first specify a product code.</p>';
316
        }
317
        $field = new LiteralField('ImageFileNote', $msg);
318
319
        return $field;
320
    }
321
322
    /**
323
     * Used in getCSMFields.
324
     *
325
     * @return GridField
0 ignored issues
show
Documentation introduced by
Should the return type not be UploadField?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
326
     **/
327
    protected function getAdditionalImagesField()
328
    {
329
        $uploadField = new UploadFIeld(
330
            'AdditionalImages',
331
            'More images'
332
        );
333
        $uploadField->setAllowedMaxFileNumber(12);
334
        return $uploadField;
335
    }
336
337
    /**
338
     * Used in getCSMFields.
339
     *
340
     * @return GridField
0 ignored issues
show
Documentation introduced by
Should the return type not be UploadField?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
341
     **/
342
    protected function getAdditionalFilesField()
343
    {
344
        $uploadField = new UploadFIeld(
345
            'AdditionalFiles',
346
            'Additional Files'
347
        );
348
        $uploadField->setAllowedMaxFileNumber(12);
349
        return $uploadField;
350
    }
351
352
    /**
353
     * How to view using AJAX
354
     * e.g. if you want to load the produyct in a list - using AJAX
355
     * then use this link
356
     * Opening the link will return a HTML snippet.
357
     *
358
     * @return string
359
     */
360
    public function AjaxLink()
361
    {
362
        return $this->Link('ajaxview');
363
    }
364
365
    /**
366
     * Adds keywords to the MetaKeyword
367
     * Standard SS Method.
368
     */
369
    public function onBeforeWrite()
370
    {
371
        parent::onBeforeWrite();
372
        $config = $this->EcomConfig();
0 ignored issues
show
Unused Code introduced by
$config is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
373
        //set allowpurchase to false IF
374
        //free products are not allowed to be purchased
375
376
        $filter = EcommerceCodeFilter::create();
377
        $filter->checkCode($this, 'InternalItemID');
378
        $this->prepareFullFields();
379
        //we are adding all the fields to the keyword fields here for searching purposes.
380
        //because the MetaKeywords Field is being searched.
381
        if ($this->Config()->get('add_data_to_meta_description_for_search')) {
382
            $this->MetaDescription = '';
383
            $fieldsToExclude = Config::inst()->get('SiteTree', 'db');
384
            foreach ($this->db() as $fieldName => $fieldType) {
385
                if (is_string($this->$fieldName) && strlen($this->$fieldName) > 2) {
386
                    if (!in_array($fieldName, $fieldsToExclude)) {
387
                        $this->MetaDescription .= strip_tags($this->$fieldName);
388
                    }
389
                }
390
            }
391
            if ($this->hasExtension('ProductWithVariationDecorator')) {
392
                $variations = $this->Variations();
393
                if ($variations) {
394
                    $variationCount = $variations->count();
395
                    if ($variationCount > 0 && $variationCount < 8) {
396
                        foreach ($variations as $variation) {
397
                            $this->MetaDescription .= ' - '.$variation->FullName;
398
                        }
399
                    }
400
                }
401
            }
402
        }
403
    }
404
405
    /**
406
     * standard SS Method
407
     * Make sure that the image is a product image.
408
     */
409
    public function onAfterWrite()
410
    {
411
        parent::onAfterWrite();
412
        if ($this->ImageID) {
413
            if ($normalImage = Image::get()->exclude(array('ClassName' => 'Product_Image'))->byID($this->ImageID)) {
414
                $normalImage = $normalImage->newClassInstance('Product_Image');
415
                $normalImage->write();
416
            }
417
        }
418
    }
419
420
421
    /**
422
     * sets the FullName and FullSiteTreeField to the latest values
423
     * This can be useful as you can compare it to the ones saved in the database.
424
     * Returns true if the value is different from the one in the database.
425
     *
426
     * @return bool
427
     */
428
    public function prepareFullFields()
429
    {
430
        //FullName
431
        $fullName = '';
432
        if ($this->InternalItemID) {
433
            $fullName .= $this->InternalItemID.': ';
434
        }
435
        $fullName .= $this->Title;
436
        //FullSiteTreeSort
437
        $parentSortArray = array(sprintf('%03d', $this->Sort));
438
        $obj = $this;
439
        $parentTitleArray = array();
440
        while ($obj && $obj->ParentID) {
441
            $obj = SiteTree::get()->byID(intval($obj->ParentID) - 0);
442
            if ($obj) {
443
                $parentSortArray[] = sprintf('%03d', $obj->Sort);
444
                if (is_a($obj, Object::getCustomClass('ProductGroup'))) {
445
                    $parentTitleArray[] = $obj->Title;
446
                }
447
            }
448
        }
449
        $reverseArray = array_reverse($parentSortArray);
450
        $parentTitle = '';
451
        if (count($parentTitleArray)) {
452
            $parentTitle = ' ('._t('product.IN', 'in').' '.implode(' / ', $parentTitleArray).')';
453
        }
454
        //setting fields with new values!
455
        $this->FullName = $fullName.$parentTitle;
456
        $this->FullSiteTreeSort = implode('', array_map($this->numberPad, $reverseArray));
457
        if (($this->dbObject('FullName') != $this->FullName) || ($this->dbObject('FullSiteTreeSort') != $this->FullSiteTreeSort)) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return $this->dbObject('...this->FullSiteTreeSort;.
Loading history...
458
            return true;
459
        }
460
461
        return false;
462
    }
463
464
    //GROUPS AND SIBLINGS
465
466
    /**
467
     * Returns all the parent groups for the product.
468
     *
469
     *@return DataList (ProductGroups)
470
     **/
471
    public function AllParentGroups()
472
    {
473
        $otherGroupsArray = $this->ProductGroups()->map('ID', 'ID')->toArray();
474
475
        return ProductGroup::get()->filter(
476
            array(
477
                'ID' => array($this->ParentID => $this->ParentID) + $otherGroupsArray,
478
            )
479
        );
480
    }
481
482
    /**
483
     * Returns all the parent groups for the product,
484
     * including the parents and parents and so on.
485
     *
486
     * @return DataList (ProductGroups)
487
     */
488
    public function AllParentGroupsIncludingParents()
489
    {
490
        $directParents = $this->AllParentGroups();
491
        $allParentsArray = array();
492
        foreach ($directParents as $parent) {
493
            $obj = $parent;
494
            $allParentsArray[$obj->ID] = $obj->ID;
495
            while ($obj && $obj->ParentID) {
496
                $obj = SiteTree::get()->byID(intval($obj->ParentID) - 0);
497
                if ($obj) {
498
                    if (is_a($obj, Object::getCustomClass('ProductGroup'))) {
499
                        $allParentsArray[$obj->ID] = $obj->ID;
500
                    }
501
                }
502
            }
503
        }
504
505
        return ProductGroup::get()->filter(array('ID' => $allParentsArray));
506
    }
507
508
    /**
509
     * @return Product ...
510
     * we have this so that Variations can link to products
511
     * and products link to themselves...
512
     */
513
    public function getProduct()
514
    {
515
        return $this;
516
    }
517
518
    /**
519
     * Returns the direct parent group for the product.
520
     *
521
     * @return ProductGroup | NULL
522
     **/
523
    public function MainParentGroup()
524
    {
525
        return ProductGroup::get()->byID($this->ParentID);
526
    }
527
528
    /**
529
     * Returns the top parent group of the product (in the hierarchy).
530
     *
531
     * @return ProductGroup | NULL
532
     **/
533
    public function TopParentGroup()
534
    {
535
        $parent = $this->MainParentGroup();
536
        $x = 0;
537
        while ($parent && $x < 100) {
538
            $returnValue = $parent;
539
            $parent = ProductGroup::get()->filter(array('ID' => $parent->ParentID))->first();
540
            ++$x;
541
        }
542
543
        return $returnValue;
0 ignored issues
show
Bug introduced by
The variable $returnValue does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
544
    }
545
546
    /**
547
     * Returns products in the same group.
548
     *
549
     * @return DataList (Products)
550
     **/
551
    public function Siblings()
552
    {
553
        if ($this->ParentID) {
554
            $extension = '';
0 ignored issues
show
Unused Code introduced by
$extension is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
555
            if (Versioned::current_stage() == 'Live') {
556
                $extension = '_Live';
0 ignored issues
show
Unused Code introduced by
$extension is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
557
            }
558
559
            return Product::get()
560
                ->filter(array(
561
                    'ShowInMenus' => 1,
562
                    'ParentID' => $this->ParentID,
563
                ))
564
                ->exclude(array('ID' => $this->ID));
565
        }
566
    }
567
568
    //IMAGE
569
    /**
570
     * returns a "BestAvailable" image if the current one is not available
571
     * In some cases this is appropriate and in some cases this is not.
572
     * For example, consider the following setup
573
     * - product A with three variations
574
     * - Product A has an image, but the variations have no images
575
     * With this scenario, you want to show ONLY the product image
576
     * on the product page, but if one of the variations is added to the
577
     * cart, then you want to show the product image.
578
     * This can be achieved bu using the BestAvailable image.
579
     *
580
     * @return Image | Null
0 ignored issues
show
Documentation introduced by
Should the return type not be Image|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
581
     */
582
    public function BestAvailableImage()
583
    {
584
        $product = Product::get()->byID($this->ID);
585
        if ($product && $product->ImageID) {
586
            $image = Image::get()->byID($product->ImageID);
587
            if ($image) {
588
                if (file_exists($image->getFullPath())) {
589
                    return $image;
590
                }
591
            }
592
        }
593
        if ($parent = $this->MainParentGroup()) {
594
            return $parent->BestAvailableImage();
595
        }
596
    }
597
598
    /**
599
     * Little hack to show thumbnail in summary fields in modeladmin in CMS.
600
     *
601
     * @return string (HTML = formatted image)
602
     */
603
    public function CMSThumbnail()
604
    {
605
        if ($image = $this->Image()) {
606
            if ($image->exists()) {
607
                return $image->Thumbnail();
608
            }
609
        }
610
611
        return '['._t('product.NOIMAGE', 'no image').']';
612
    }
613
614
    /**
615
     * Returns a link to a default image.
616
     * If a default image is set in the site config then this link is returned
617
     * Otherwise, a standard link is returned.
618
     *
619
     * @return string
620
     */
621
    public function DefaultImageLink()
622
    {
623
        return $this->EcomConfig()->DefaultImageLink();
624
    }
625
626
    /**
627
     * returns the default image of the product.
628
     *
629
     * @return Image | Null
0 ignored issues
show
Documentation introduced by
Should the return type not be string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
630
     */
631
    public function DefaultImage()
632
    {
633
        return $this->EcomConfig()->DefaultImage();
634
    }
635
636
    /**
637
     * returns a product image for use in templates
638
     * e.g. $DummyImage.Width();.
639
     *
640
     * @return Product_Image
641
     */
642
    public function DummyImage()
643
    {
644
        return new Product_Image();
645
    }
646
647
    // VERSIONING
648
649
    /**
650
     * Conditions for whether a product can be purchased.
651
     *
652
     * If it has the checkbox for 'Allow this product to be purchased',
653
     * as well as having a price, it can be purchased. Otherwise a user
654
     * can't buy it.
655
     *
656
     * Other conditions may be added by decorating with the canPurcahse function
657
     *
658
     * @return bool
659
     */
660
661
    /**
662
     * @TODO: complete
663
     *
664
     * @param string $compontent - the has many relationship you are looking at, e.g. OrderAttribute
0 ignored issues
show
Documentation introduced by
There is no parameter named $compontent. Did you maybe mean $component?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
665
     *
666
     * @return DataList (CHECK!)
667
     */
668
    public function getVersionedComponents($component = 'ProductVariations')
669
    {
670
        return;
671
        $baseTable = ClassInfo::baseDataClass(self::$has_many[$component]);
0 ignored issues
show
Unused Code introduced by
$baseTable = \ClassInfo:...$has_many[$component]); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
672
        $query = singleton(self::$has_many[$component])->buildVersionSQL("\"{$baseTable}\".ProductID = {$this->ID} AND \"{$baseTable}\".Version = {$this->Version}");
673
        $result = singleton(self::$has_many[$component])->buildDataObjectSet($query->execute());
674
675
        return $result;
676
    }
677
678
    /**
679
     * Action to return specific version of a specific product.
680
     * This can be any product to enable the retrieval of deleted products.
681
     * This is really useful for sold products where you want to retrieve the actual version that you sold.
682
     * If the version can not be found then we retrieve the current one.
683
     *
684
     * @param int $id
685
     * @param int $version
686
     *
687
     * @return DataObject | Null
688
     */
689
    public function getVersionOfBuyable($id = 0, $version = 0)
690
    {
691
        if (!$id) {
692
            $id = $this->ID;
693
        }
694
        if (!$version) {
695
            $version = $this->Version;
696
        }
697
        //not sure why this is running via OrderItem...
698
        $obj = OrderItem::get_version($this->ClassName, $id, $version);
699
        if (!$obj) {
700
            $className = $this->ClassName;
701
            $obj = $className::get()->byID($id);
702
        }
703
704
        return $obj;
705
    }
706
707
    //ORDER ITEM
708
709
    /**
710
     * returns the order item associated with the buyable.
711
     * ALWAYS returns one, even if there is none in the cart.
712
     * Does not write to database.
713
     *
714
     * @return OrderItem (no kidding)
715
     **/
716
    public function OrderItem()
717
    {
718
        //work out the filter
719
        $filterArray = array();
720
        $extendedFilter = $this->extend('updateItemFilter', $filter);
0 ignored issues
show
Bug introduced by
The variable $filter does not exist. Did you mean $filterArray?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
721
        if ($extendedFilter !== null && is_array($extendedFilter) && count($extendedFilter)) {
722
            $filterArray = $extendedFilter;
723
        }
724
        //make the item and extend
725
        $item = ShoppingCart::singleton()->findOrMakeItem($this, $filterArray);
726
        $this->extend('updateDummyItem', $item);
727
728
        return $item;
729
    }
730
731
    /**
732
     * @var string
733
     */
734
    protected $defaultClassNameForOrderItem = 'Product_OrderItem';
735
736
    /**
737
     * you can overwrite this function in your buyable items (such as Product).
738
     *
739
     * @return string
740
     **/
741
    public function classNameForOrderItem()
742
    {
743
        $className = $this->defaultClassNameForOrderItem;
744
        $updateClassName = $this->extend('updateClassNameForOrderItem', $className);
745
        if ($updateClassName !== null && is_array($updateClassName) && count($updateClassName)) {
746
            $className = $updateClassName[0];
747
        }
748
749
        return $className;
750
    }
751
752
    /**
753
     * You can set an alternative class name for order item using this method.
754
     *
755
     * @param string $ClassName
0 ignored issues
show
Documentation introduced by
There is no parameter named $ClassName. Did you maybe mean $className?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
756
     **/
757
    public function setAlternativeClassNameForOrderItem($className)
758
    {
759
        $this->defaultClassNameForOrderItem = $className;
760
    }
761
762
    /**
763
     * This is used when you add a product to your cart
764
     * if you set it to 1 then you can add 0.1 product to cart.
765
     * If you set it to -1 then you can add 10, 20, 30, etc.. products to cart.
766
     *
767
     * @return int
768
     **/
769
    public function QuantityDecimals()
770
    {
771
        return 0;
772
    }
773
774
    /**
775
     * Number of items sold.
776
     *
777
     * @return int
778
     */
779
    public function HasBeenSold()
780
    {
781
        return $this->getHasBeenSold();
782
    }
783
    public function getHasBeenSold()
784
    {
785
        $dataList = Order::get_datalist_of_orders_with_submit_record($onlySubmittedOrders = true, $includeCancelledOrders = false);
786
        $dataList = $dataList->innerJoin('OrderAttribute', '"OrderAttribute"."OrderID" = "Order"."ID"');
787
        $dataList = $dataList->innerJoin('OrderItem', '"OrderAttribute"."ID" = "OrderItem"."ID"');
788
        $dataList = $dataList->filter(
789
            array(
790
                'BuyableID' => $this->ID,
791
                'buyableClassName' => $this->ClassName
792
            )
793
        );
794
795
        return $dataList->count();
796
    }
797
798
    //LINKS
799
800
    /**
801
     * Tells us the link to select variations
802
     * If ajaxified, this controller method (selectvariation)
803
     * Will return a html snippet for selecting the variation.
804
     * This is useful in the Product Group where you can both
805
     * non-variation and variation products to have the same
806
     * "add to cart" button.  Using this link you can provide a
807
     * pop-up select system for selecting a variation.
808
     *
809
     * @return string
810
     */
811
    public function AddVariationsLink()
812
    {
813
        return $this->Link('selectvariation');
814
    }
815
816
    /**
817
     * passing on shopping cart links ...is this necessary?? ...why not just pass the cart?
818
     *
819
     * @return string
820
     */
821
    public function AddLink()
822
    {
823
        return ShoppingCart_Controller::add_item_link($this->ID, $this->ClassName, $this->linkParameters('add'));
824
    }
825
826
    /**
827
     * link use to add (one) to cart.
828
     *
829
     *@return string
830
     */
831
    public function IncrementLink()
832
    {
833
        //we can do this, because by default add link adds one
834
        return ShoppingCart_Controller::add_item_link($this->ID, $this->ClassName, $this->linkParameters('increment'));
835
    }
836
837
    /**
838
     * Link used to remove one from cart
839
     * we can do this, because by default remove link removes one.
840
     *
841
     * @return string
842
     */
843
    public function DecrementLink()
844
    {
845
        return ShoppingCart_Controller::remove_item_link($this->ID, $this->ClassName, $this->linkParameters('decrement'));
846
    }
847
848
    /**
849
     * remove one buyable's orderitem from cart.
850
     *
851
     * @return string (Link)
852
     */
853
    public function RemoveLink()
854
    {
855
        return ShoppingCart_Controller::remove_item_link($this->ID, $this->ClassName, $this->linkParameters('remove'));
856
    }
857
858
    /**
859
     * remove all of this buyable's orderitem from cart.
860
     *
861
     * @return string (Link)
862
     */
863
    public function RemoveAllLink()
864
    {
865
        return ShoppingCart_Controller::remove_all_item_link($this->ID, $this->ClassName, $this->linkParameters('removeall'));
866
    }
867
868
    /**
869
     * remove all of this buyable's orderitem from cart and go through to this buyble to add alternative selection.
870
     *
871
     * @return string (Link)
872
     */
873
    public function RemoveAllAndEditLink()
874
    {
875
        return ShoppingCart_Controller::remove_all_item_and_edit_link($this->ID, $this->ClassName, $this->linkParameters('removeallandedit'));
876
    }
877
878
    /**
879
     * set new specific new quantity for buyable's orderitem.
880
     *
881
     * @param float
882
     *
883
     * @return string (Link)
884
     */
885
    public function SetSpecificQuantityItemLink($quantity)
886
    {
887
        return ShoppingCart_Controller::set_quantity_item_link($this->ID, $this->ClassName, array_merge($this->linkParameters('setspecificquantityitem'), array('quantity' => $quantity)));
888
    }
889
890
    /**
891
     * @return string
892
     */
893
    public function AddToCartAndGoToCheckoutLink()
894
    {
895
        $array = $this->linkParameters();
896
        $array['BackURL'] = urlencode(CheckoutPage::find_link());
897
898
        return ShoppingCart_Controller::add_item_link($this->ID, $this->ClassName, $array);
899
    }
900
901
    /**
902
     *
903
     *
904
     * @return string
905
     */
906
     public function VersionedLink()
907
     {
908
         return Controller::join_links(
909
             Director::baseURL(),
910
             EcommerceConfig::get('ShoppingCart_Controller', 'url_segment'),
911
             'submittedbuyable',
912
             $this->ClassName,
913
             $this->ID,
914
             $this->Version
915
         );
916
     }
917
918
    public function RemoveFromSaleLink()
919
    {
920
        return ShoppingCart_Controller::remove_from_sale_link($this->ID, $this->ClassName);
921
    }
922
923
    /**
924
     * Here you can add additional information to your product
925
     * links such as the AddLink and the RemoveLink.
926
     * One useful parameter you can add is the BackURL link.
927
     *
928
     * Usage would be by means of
929
     * 1. decorating product
930
     * 2. adding a updateLinkParameters method
931
     * 3. adding items to the array.
932
     *
933
     * You can also extend Product and override this method...
934
     *
935
     * @return array
936
     **/
937
    protected function linkParameters($type = '')
938
    {
939
        $array = array();
940
        $extendedArray = $this->extend('updateLinkParameters', $array, $type);
941
        if ($extendedArray !== null && is_array($extendedArray) && count($extendedArray)) {
942
            foreach ($extendedArray as $extendedArrayUpdate) {
943
                $array = array_merge($array, $extendedArrayUpdate);
944
            }
945
        }
946
947
        return $array;
948
    }
949
950
    //TEMPLATE STUFF
951
952
    /**
953
     * @return bool
954
     */
955
    public function IsInCart()
956
    {
957
        return ($this->OrderItem() && $this->OrderItem()->Quantity > 0) ? true : false;
0 ignored issues
show
Documentation introduced by
The property Quantity does not exist on object<OrderItem>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
958
    }
959
960
    /**
961
     * @return EcomQuantityField
962
     */
963
    public function EcomQuantityField()
964
    {
965
        return EcomQuantityField::create($this);
966
    }
967
968
    /**
969
     * returns the instance of EcommerceConfigAjax for use in templates.
970
     * In templates, it is used like this:
971
     * $EcommerceConfigAjax.TableID.
972
     *
973
     * @return EcommerceConfigAjax
0 ignored issues
show
Documentation introduced by
Should the return type not be EcommerceConfigAjaxDefinitions?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
974
     **/
975
    public function AJAXDefinitions()
976
    {
977
        return EcommerceConfigAjax::get_one($this);
978
    }
979
980
    /**
981
     * @return EcommerceDBConfig
982
     **/
983
    public function EcomConfig()
984
    {
985
        return EcommerceDBConfig::current_ecommerce_db_config();
986
    }
987
988
    /**
989
     * Is it a variation?
990
     *
991
     * @return bool
992
     */
993
    public function IsProductVariation()
994
    {
995
        return false;
996
    }
997
998
    /**
999
     * tells us if the current page is part of e-commerce.
1000
     *
1001
     * @return bool
1002
     */
1003
    public function IsEcommercePage()
1004
    {
1005
        return true;
1006
    }
1007
1008
    public function AllowPurchaseNice()
1009
    {
1010
        return $this->obj('AllowPurchase')->Nice();
1011
    }
1012
1013
    /**
1014
     * Products have a standard price, but for specific situations they have a calculated price.
1015
     * The Price can be changed for specific member discounts, etc...
1016
     *
1017
     * @return float
1018
     */
1019
    public function CalculatedPrice()
1020
    {
1021
        return $this->getCalculatedPrice();
1022
    }
1023
1024
    private static $_calculated_price_cache = array();
1025
1026
    /**
1027
     * Products have a standard price, but for specific situations they have a calculated price.
1028
     * The Price can be changed for specific member discounts, etc...
1029
     *
1030
     * We add three "hooks" / "extensions" here... so that you can update prices
1031
     * in a logical order (e.g. firstly change to forex and then apply discount)
1032
     *
1033
     * @return float
1034
     */
1035
    public function getCalculatedPrice()
1036
    {
1037
        if (! isset(self::$_calculated_price_cache[$this->ID])) {
1038
            $price = $this->Price;
1039
            $updatedPrice = $this->extend('updateBeforeCalculatedPrice', $price);
1040
            if ($updatedPrice !== null && is_array($updatedPrice) && count($updatedPrice)) {
1041
                $price = $updatedPrice[0];
1042
            }
1043
            $updatedPrice = $this->extend('updateCalculatedPrice', $price);
1044
            if ($updatedPrice !== null && is_array($updatedPrice) && count($updatedPrice)) {
1045
                $price = $updatedPrice[0];
1046
            }
1047
            $updatedPrice = $this->extend('updateAfterCalculatedPrice', $price);
1048
            if ($updatedPrice !== null && is_array($updatedPrice) && count($updatedPrice)) {
1049
                $price = $updatedPrice[0];
1050
            }
1051
            self::$_calculated_price_cache[$this->ID] = $price;
1052
        }
1053
        return self::$_calculated_price_cache[$this->ID];
1054
    }
1055
1056
    /**
1057
     * How do we display the price?
1058
     *
1059
     * @return Money
1060
     */
1061
    public function CalculatedPriceAsMoney()
1062
    {
1063
        return $this->getCalculatedPriceAsMoney();
1064
    }
1065
    public function getCalculatedPriceAsMoney()
1066
    {
1067
        return EcommerceCurrency::get_money_object_from_order_currency($this->getCalculatedPrice());
1068
    }
1069
1070
    //CRUD SETTINGS
1071
1072
    /**
1073
     * Is the product for sale?
1074
     *
1075
     * @param Member $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be null|Member?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1076
     * @param bool   $checkPrice
1077
     *
1078
     * @return bool
1079
     */
1080
    public function canPurchase(Member $member = null, $checkPrice = true)
1081
    {
1082
        $config = $this->EcomConfig();
1083
        //shop closed
1084
        if ($config->ShopClosed) {
0 ignored issues
show
Documentation introduced by
The property ShopClosed does not exist on object<EcommerceDBConfig>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1085
            return false;
1086
        }
1087
        //not sold at all
1088
        if (! $this->AllowPurchase) {
1089
            return false;
1090
        }
1091
        //check country
1092
        if (! $member) {
1093
            $member = Member::currentUser();
1094
        }
1095
        $extended = $this->extendedCan('canPurchaseByCountry', $member);
1096
        if ($extended !== null) {
1097
            return $extended;
1098
        }
1099
        if (! EcommerceCountry::allow_sales()) {
1100
            return false;
1101
        }
1102
1103
        if ($checkPrice) {
1104
            $price = $this->getCalculatedPrice();
1105
            if ($price == 0 && !$config->AllowFreeProductPurchase) {
0 ignored issues
show
Documentation introduced by
The property AllowFreeProductPurchase does not exist on object<EcommerceDBConfig>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1106
                return false;
1107
            }
1108
        }
1109
        // Standard mechanism for accepting permission changes from decorators
1110
        $extended = $this->extendedCan(__FUNCTION__, $member);
1111
        if ($extended !== null) {
1112
            return $extended;
1113
        }
1114
        return $this->AllowPurchase;
1115
    }
1116
1117
    public function canCreate($member = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
1118
    {
1119
        if (! $member) {
1120
            $member = Member::currentUser();
1121
        }
1122
        $extended = $this->extendedCan(__FUNCTION__, $member);
1123
        if ($extended !== null) {
1124
            return $extended;
1125
        }
1126
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
1127
            return true;
1128
        }
1129
1130
        return parent::canCreate($member);
1131
    }
1132
1133
    /**
1134
     * Shop Admins can edit.
1135
     *
1136
     * @param Member $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be Member|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1137
     *
1138
     * @return bool
1139
     */
1140
    public function canEdit($member = null)
1141
    {
1142
        if (! $member) {
1143
            $member = Member::currentUser();
1144
        }
1145
        $extended = $this->extendedCan(__FUNCTION__, $member);
1146
        if ($extended !== null) {
1147
            return $extended;
1148
        }
1149
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
1150
            return true;
1151
        }
1152
1153
        return parent::canEdit($member);
1154
    }
1155
1156
    /**
1157
     * Standard SS method.
1158
     *
1159
     * @param Member $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be Member|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1160
     *
1161
     * @return bool
1162
     */
1163
    public function canDelete($member = null)
1164
    {
1165
        if (is_a(Controller::curr(), Object::getCustomClass('ProductsAndGroupsModelAdmin'))) {
1166
            return false;
1167
        }
1168
        if (! $member) {
1169
            $member = Member::currentUser();
1170
        }
1171
        $extended = $this->extendedCan(__FUNCTION__, $member);
1172
        if ($extended !== null) {
1173
            return $extended;
1174
        }
1175
1176
        return $this->canEdit($member);
1177
    }
1178
1179
    /**
1180
     * Standard SS method.
1181
     *
1182
     * @param Member $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be Member|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1183
     *
1184
     * @return bool
1185
     */
1186
    public function canPublish($member = null)
1187
    {
1188
        return $this->canEdit($member);
1189
    }
1190
1191
1192
    public function debug()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
1193
    {
1194
        $html = EcommerceTaskDebugCart::debug_object($this);
1195
        $html .= '<ul>';
1196
        $html .= '<li><hr />Links<hr /></li>';
1197
        $html .= '<li><b>Link:</b> <a href="'.$this->Link().'">'.$this->Link().'</a></li>';
1198
        $html .= '<li><b>Ajax Link:</b> <a href="'.$this->AjaxLink().'">'.$this->AjaxLink().'</a></li>';
1199
        $html .= '<li><b>AddVariations Link:</b> <a href="'.$this->AddVariationsLink().'">'.$this->AddVariationsLink().'</a></li>';
1200
        $html .= '<li><b>Add to Cart Link:</b> <a href="'.$this->AddLink().'">'.$this->AddLink().'</a></li>';
1201
        $html .= '<li><b>Increment Link:</b> <a href="'.$this->IncrementLink().'">'.$this->IncrementLink().'</a></li>';
1202
        $html .= '<li><b>Decrement Link:</b> <a href="'.$this->DecrementLink().'">'.$this->DecrementLink().'</a></li>';
1203
        $html .= '<li><b>Remove Link:</b> <a href="'.$this->RemoveAllLink().'">'.$this->RemoveLink().'</a></li>';
1204
        $html .= '<li><b>Remove All Link:</b> <a href="'.$this->RemoveAllLink().'">'.$this->RemoveAllLink().'</a></li>';
1205
        $html .= '<li><b>Remove All and Edit Link:</b> <a href="'.$this->RemoveAllAndEditLink().'">'.$this->RemoveAllAndEditLink().'</a></li>';
1206
        $html .= '<li><b>Set Specific Quantity Item Link (e.g. 77):</b> <a href="'.$this->SetSpecificQuantityItemLink(77).'">'.$this->SetSpecificQuantityItemLink(77).'</a></li>';
1207
1208
        $html .= '<li><hr />Cart<hr /></li>';
1209
        $html .= '<li><b>Allow Purchase (DB Value):</b> '.$this->AllowPurchaseNice().' </li>';
1210
        $html .= '<li><b>Can Purchase (overal calculation):</b> '.($this->canPurchase() ? 'YES' : 'NO').' </li>';
1211
        $html .= '<li><b>Shop Open:</b> '.($this->EcomConfig() ?  ($this->EcomConfig()->ShopClosed ? 'NO' : 'YES') : 'NO CONFIG').' </li>';
0 ignored issues
show
Documentation introduced by
The property ShopClosed does not exist on object<EcommerceDBConfig>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1212
        $html .= '<li><b>Extended Country Can Purchase:</b> '.($this->extendedCan('canPurchaseByCountry', null) === null ? 'no applicable' : ($this->extendedCan('canPurchaseByCountry', null) ? 'CAN PURCHASE' : 'CAN NOT PURCHASE')).' </li>';
1213
        $html .= '<li><b>Allow sales to this country ('.EcommerceCountry::get_country().'):</b> '.(EcommerceCountry::allow_sales() ? 'YES' : 'NO').' </li>';
1214
        $html .= '<li><b>Class Name for OrderItem:</b> '.$this->classNameForOrderItem().' </li>';
1215
        $html .= '<li><b>Quantity Decimals:</b> '.$this->QuantityDecimals().' </li>';
1216
        $html .= '<li><b>Is In Cart:</b> '.($this->IsInCart() ? 'YES' : 'NO').' </li>';
1217
        $html .= '<li><b>Has Been Sold:</b> '.($this->HasBeenSold() ?  'YES' : 'NO').' </li>';
1218
        $html .= '<li><b>Calculated Price:</b> '.$this->CalculatedPrice().' </li>';
1219
        $html .= '<li><b>Calculated Price as Money:</b> '.$this->getCalculatedPriceAsMoney()->Nice().' </li>';
1220
1221
        $html .= '<li><hr />Location<hr /></li>';
1222
        $html .= '<li><b>Main Parent Group:</b> '.$this->MainParentGroup()->Title.'</li>';
1223
        $html .= '<li><b>All Others Parent Groups:</b> '.($this->AllParentGroups()->count() ? '<pre>'.print_r($this->AllParentGroups()->map()->toArray(), 1).'</pre>' : 'none').'</li>';
1224
1225
        $html .= '<li><hr />Image<hr /></li>';
1226
        $html .= '<li><b>Image:</b> '.($this->BestAvailableImage() ? '<img src='.$this->BestAvailableImage()->Link().' />' : 'no image').' </li>';
1227
        $productGroup = ProductGroup::get()->byID($this->ParentID);
1228
        if ($productGroup) {
1229
            $html .= '<li><hr />Product Example<hr /></li>';
1230
            $html .= '<li><b>Product Group View:</b> <a href="'.$productGroup->Link().'">'.$productGroup->Title.'</a> </li>';
1231
            $html .= '<li><b>Product Group Debug:</b> <a href="'.$productGroup->Link('debug').'">'.$productGroup->Title.'</a> </li>';
1232
            $html .= '<li><b>Product Group Admin:</b> <a href="'.'/admin/pages/edit/show/'.$productGroup->ID.'">'.$productGroup->Title.' Admin</a> </li>';
1233
            $html .= '<li><b>Edit this Product:</b> <a href="'.'/admin/pages/edit/show/'.$this->ID.'">'.$this->Title.' Admin</a> </li>';
1234
        }
1235
        $html .= '</ul>';
1236
1237
        return $html;
1238
        $html .= '</ul>';
0 ignored issues
show
Unused Code introduced by
$html .= '</ul>'; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1239
1240
        return $html;
1241
    }
1242
}
1243
1244
class Product_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...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
1245
{
1246
    private static $allowed_actions = array(
0 ignored issues
show
Unused Code introduced by
The property $allowed_actions 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...
1247
        'viewversion',
1248
        'ajaxview',
1249
        'addproductfromform',
1250
        'debug' => 'ADMIN',
1251
    );
1252
1253
    /**
1254
     * is this the current version?
1255
     *
1256
     * @var bool
1257
     */
1258
    protected $isCurrentVersion = true;
1259
1260
    /**
1261
     * Standard SS method.
1262
     */
1263
    public function init()
1264
    {
1265
        parent::init();
1266
        Requirements::themedCSS('Product', 'ecommerce');
1267
        Requirements::javascript('ecommerce/javascript/EcomProducts.js');
1268
    }
1269
1270
    /**
1271
     * view earlier version of a product
1272
     * returns error or changes datarecord to earlier version
1273
     * if the ID does not match the Page then we look for the variation.
1274
     *
1275
     * @param SS_HTTPRequest
1276
     */
1277
    public function viewversion(SS_HTTPRequest $request)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
1278
    {
1279
        $version = intval($request->param('ID')) - 0;
1280
        $currentVersion = $this->Version;
1281
        if ($currentVersion != $version) {
1282
            if ($record = $this->getVersionOfBuyable($this->ID, $version)) {
1283
                //we check again, because we may actually get the same version back...
1284
                if ($record->Version != $this->Version) {
1285
                    $this->record = $record;
1286
                    $this->dataRecord->AllowPurchase = false;
1287
                    $this->AllowPurchase = false;
1288
                    $this->isCurrentVersion = false;
1289
                    $this->Title .= _t('Product.OLDERVERSION', ' - Older Version');
1290
                    $this->MetaTitle .= _t('Product.OLDERVERSION', ' - Older Version');
1291
                }
1292
            } else {
1293
                return $this->httpError(404);
1294
            }
1295
        }
1296
1297
        return array();
1298
    }
1299
1300
    /**
1301
     * Standard SS method
1302
     * Returns a snippet when requested by ajax.
1303
     */
1304
    public function ajaxview(SS_HTTPRequest $request)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
1305
    {
1306
        Config::nest();
1307
        Config::inst()->update('SSViewer', 'theme_enabled', true);
1308
        $html = $this->renderWith('ProductGroupItemMoreDetail');
1309
        Config::unnest();
1310
1311
        return $html;
1312
    }
1313
1314
    /**
1315
     * returns a form for adding products to cart.
1316
     *
1317
     * @return Form
1318
     */
1319
    public function AddProductForm()
1320
    {
1321
        if ($this->canPurchase()) {
1322
            $farray = array();
1323
            $requiredFields = array();
0 ignored issues
show
Unused Code introduced by
$requiredFields is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1324
            $fields = new FieldList($farray);
1325
            $fields->push(new NumericField('Quantity', 'Quantity', 1)); //TODO: perhaps use a dropdown instead (elimiates need to use keyboard)
1326
            $actions = new FieldList(
1327
                new FormAction('addproductfromform', _t('Product.ADDLINK', 'Add this item to cart'))
1328
            );
1329
            $requiredfields[] = 'Quantity';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$requiredfields was never initialized. Although not strictly required by PHP, it is generally a good practice to add $requiredfields = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1330
            $validator = new RequiredFields($requiredfields);
1331
            $form = new Form($this, 'AddProductForm', $fields, $actions, $validator);
1332
1333
            return $form;
1334
        } else {
1335
            return _t('Product.PRODUCTNOTFORSALE', 'Product not for sale');
1336
        }
1337
    }
1338
1339
    /**
1340
     * executes the AddProductForm.
1341
     *
1342
     * @param array $data
1343
     * @param Form  $form
1344
     */
1345
    public function addproductfromform(array $data, Form $form)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
1346
    {
1347
        if (!$this->IsInCart()) {
1348
            $quantity = round($data['Quantity'], $this->QuantityDecimals());
1349
            if (!$quantity) {
1350
                $quantity = 1;
1351
            }
1352
            $product = Product::get()->byID($this->ID);
1353
            if ($product) {
1354
                ShoppingCart::singleton()->addBuyable($product, $quantity);
1355
            }
1356
            if ($this->IsInCart()) {
1357
                $msg = _t('Order.SUCCESSFULLYADDED', 'Added to cart.');
1358
                $status = 'good';
1359
            } else {
1360
                $msg = _t('Order.NOTADDEDTOCART', 'Not added to cart.');
1361
                $status = 'bad';
1362
            }
1363
            if (Director::is_ajax()) {
1364
                return ShoppingCart::singleton()->setMessageAndReturn($msg, $status);
1365
            } else {
1366
                $form->sessionMessage($msg, $status);
1367
                $this->redirectBack();
1368
            }
1369
        } else {
1370
            return EcomQuantityField::create($this);
1371
        }
1372
    }
1373
1374
    /**
1375
     * Is this an older version?
1376
     *
1377
     * @return bool
1378
     */
1379
    public function IsOlderVersion()
1380
    {
1381
        return $this->isCurrentVersion ? false : true;
1382
    }
1383
1384
    /**
1385
     * This method can be extended to show products in the side bar.
1386
     *
1387
     * @return DataList (Products)
0 ignored issues
show
Documentation introduced by
Should the return type not be DataList|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1388
     */
1389
    public function SidebarProducts()
1390
    {
1391
        return;
1392
    }
1393
1394
    /**
1395
     * This method can be extended to show products in the side bar.
1396
     *
1397
     * @return Product | Null
1398
     */
1399
    public function NextProduct()
1400
    {
1401
        $array = $this->getListOfIDs();
1402
        $next = 0;
0 ignored issues
show
Unused Code introduced by
$next is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1403
        foreach ($array as $key => $id) {
1404
            $id = intval($id);
1405
            if ($id == $this->ID) {
1406
                if (isset($array[$key + 1])) {
1407
                    return Product::get()->byID(intval($array[$key + 1]));
1408
                }
1409
            }
1410
        }
1411
    }
1412
1413
    /**
1414
     * This method can be extended to show products in the side bar.
1415
     *
1416
     * @return Product | Null
1417
     */
1418
    public function PreviousProduct()
1419
    {
1420
        $array = $this->getListOfIDs();
1421
        $previousID = 0;
1422
        foreach ($array as $key => $id) {
1423
            $id = intval($id);
1424
            if ($id == $this->ID) {
1425
                return Product::get()->byID($previousID);
1426
            }
1427
            $previousID = $id;
1428
        }
1429
1430
        return;
1431
    }
1432
1433
    /**
1434
     * This method can be extended to show products in the side bar.
1435
     *
1436
     * @return bool
1437
     */
1438
    public function HasPreviousOrNextProduct()
1439
    {
1440
        return $this->PreviousProduct() || $this->NextProduct() ? true : false;
1441
    }
1442
1443
    /**
1444
     * returns an array of product IDs, as saved in the last
1445
     * ProductGroup view (saved using session).
1446
     *
1447
     * @return array
1448
     */
1449
    protected function getListOfIDs()
1450
    {
1451
        $listOfIDs = Session::get(EcommerceConfig::get('ProductGroup', 'session_name_for_product_array'));
1452
        if ($listOfIDs) {
1453
            $arrayOfIDs = explode(',', $listOfIDs);
1454
            if (is_array($arrayOfIDs)) {
1455
                return $arrayOfIDs;
1456
            }
1457
        }
1458
1459
        return array();
1460
    }
1461
1462
    public function debug()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
1463
    {
1464
        $member = Member::currentUser();
1465
        if (!$member || !$member->IsShopAdmin()) {
1466
            $messages = array(
1467
                'default' => 'You must login as an admin to access debug functions.',
1468
            );
1469
            Security::permissionFailure($this, $messages);
1470
        }
1471
1472
        return $this->dataRecord->debug();
1473
    }
1474
}
1475