Completed
Push — master ( 5f3bd7...3c95e7 )
by Nicolaas
07:28
created

Product::IDForSearchResults()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 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(
219
                        'Product.DO_NOT_ALLOW_FREE_PRODUCTS_TO_BE_PURCHASED',
220
                        "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."
221
                    )
222
                );
223
            }
224
        }
225
226
        $fields->addFieldToTab('Root.Details', new CheckboxField('FeaturedProduct', _t('Product.FEATURED', 'Featured Product')));
227
        $fields->addFieldToTab('Root.Details', new NumericField('Price', _t('Product.PRICE', 'Price'), '', 12));
228
        $fields->addFieldToTab('Root.Details', new TextField('InternalItemID', _t('Product.CODE', 'Product Code'), '', 30));
229
        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...
230
            $fields->addFieldToTab('Root.Details', new NumericField('Weight', _t('Product.WEIGHT', 'Weight')));
231
        }
232
        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...
233
            $fields->addFieldToTab('Root.Details', new TextField('Model', _t('Product.MODEL', 'Model')));
234
        }
235
        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...
236
            $fields->addFieldToTab(
237
                'Root.Details',
238
                TextField::create('Quantifier', _t('Product.QUANTIFIER', 'Quantifier'))
239
                    ->setRightTitle(_t('Product.QUANTIFIER_EXPLANATION', 'e.g. per kilo, per month, per dozen, each'))
240
            );
241
        }
242
        if ($this->canPurchase()) {
243
            $fields->addFieldToTab(
244
                'Root.Main',
245
                new LiteralField(
246
                    'AddToCartLink',
247
                    '<p class="message good"><a href="'.$this->AddLink().'">'._t('Product.ADD_TO_CART', 'add to cart').'</a></p>'
248
                )
249
            );
250
        } else {
251
            $fields->addFieldToTab(
252
                'Root.Main',
253
                new LiteralField(
254
                    'AddToCartLink',
255
                    '<p class="message warning">'._t('Product.CAN_NOT_BE_ADDED_TO_CART', 'this product can not be added to cart').'</p>'
256
                )
257
            );
258
        }
259
        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...
260
            $fields->addFieldsToTab(
261
                'Root.AlsoShowHere',
262
                array(
263
                    new HeaderField('ProductGroupsHeader', _t('Product.ALSOSHOWSIN', 'Also shows in ...')),
264
                    $this->getProductGroupsTableField(),
265
                )
266
            );
267
        }
268
        //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...
269
        //$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...
270
        //}
271
        return $fields;
272
    }
273
274
    /**
275
     * Used in getCSMFields.
276
     *
277
     * @return GridField
278
     **/
279
    protected function getProductGroupsTableField()
280
    {
281
        $gridField = new GridField(
282
            'ProductGroups',
283
            _t('Product.THIS_PRODUCT_SHOULD_ALSO_BE_LISTED_UNDER', 'This product is also listed under ...'),
284
            $this->ProductGroups(),
285
            GridFieldBasicPageRelationConfig::create()
286
        );
287
288
        return $gridField;
289
    }
290
291
    /**
292
     * Used in getCSMFields.
293
     *
294
     * @return LiteralField
295
     **/
296
    protected function getAdditionalImagesMessage()
297
    {
298
        $msg = '';
299
        if ($this->InternalItemID) {
300
            $findImagesTask = EcommerceTaskLinkProductWithImages::create();
301
            $findImagesLink = $findImagesTask->Link();
302
            $findImagesLinkOne = $findImagesLink.'?productid='.$this->ID;
303
            $msg .= '
304
                <h3>Batch Upload</h3>
305
                <p>
306
                To batch upload additional images and files, please go to the <a href="/admin/assets">Files section</a>, and upload them there.
307
                Files need to be named in the following way:
308
                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:
309
                <strong>'.$this->InternalItemID."_08.jpg</strong>.
310
                <br /><br />You can <a href=\"$findImagesLinkOne\" target='_blank'>find images for <i>".$this->Title."</i></a> or
311
                <a href=\"$findImagesLink\" target='_blank'>images for all products</a> ...
312
            </p>";
313
        } else {
314
            $msg .= '
315
            <h3>Batch Upload</h3>
316
            <p>To batch upload additional images and files, you must first specify a product code.</p>';
317
        }
318
        $field = new LiteralField('ImageFileNote', $msg);
319
320
        return $field;
321
    }
322
323
    /**
324
     * Used in getCSMFields.
325
     *
326
     * @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...
327
     **/
328
    protected function getAdditionalImagesField()
329
    {
330
        $uploadField = new UploadFIeld(
331
            'AdditionalImages',
332
            'More images'
333
        );
334
        $uploadField->setAllowedMaxFileNumber(12);
335
        return $uploadField;
336
    }
337
338
    /**
339
     * Used in getCSMFields.
340
     *
341
     * @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...
342
     **/
343
    protected function getAdditionalFilesField()
344
    {
345
        $uploadField = new UploadFIeld(
346
            'AdditionalFiles',
347
            'Additional Files'
348
        );
349
        $uploadField->setAllowedMaxFileNumber(12);
350
        return $uploadField;
351
    }
352
353
    /**
354
     * How to view using AJAX
355
     * e.g. if you want to load the produyct in a list - using AJAX
356
     * then use this link
357
     * Opening the link will return a HTML snippet.
358
     *
359
     * @return string
360
     */
361
    public function AjaxLink()
362
    {
363
        return $this->Link('ajaxview');
364
    }
365
366
    /**
367
     * Adds keywords to the MetaKeyword
368
     * Standard SS Method.
369
     */
370
    public function onBeforeWrite()
371
    {
372
        parent::onBeforeWrite();
373
        $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...
374
        //set allowpurchase to false IF
375
        //free products are not allowed to be purchased
376
377
        $filter = EcommerceCodeFilter::create();
378
        $filter->checkCode($this, 'InternalItemID');
379
        $this->prepareFullFields();
380
        //we are adding all the fields to the keyword fields here for searching purposes.
381
        //because the MetaKeywords Field is being searched.
382
        if ($this->Config()->get('add_data_to_meta_description_for_search')) {
383
            $this->MetaDescription = '';
384
            $fieldsToExclude = Config::inst()->get('SiteTree', 'db');
385
            foreach ($this->db() as $fieldName => $fieldType) {
386
                if (is_string($this->$fieldName) && strlen($this->$fieldName) > 2) {
387
                    if (!in_array($fieldName, $fieldsToExclude)) {
388
                        $this->MetaDescription .= strip_tags($this->$fieldName);
389
                    }
390
                }
391
            }
392
            if ($this->hasExtension('ProductWithVariationDecorator')) {
393
                $variations = $this->Variations();
394
                if ($variations) {
395
                    $variationCount = $variations->count();
396
                    if ($variationCount > 0 && $variationCount < 8) {
397
                        foreach ($variations as $variation) {
398
                            $this->MetaDescription .= ' - '.$variation->FullName;
399
                        }
400
                    }
401
                }
402
            }
403
        }
404
    }
405
406
    /**
407
     * standard SS Method
408
     * Make sure that the image is a product image.
409
     */
410
    public function onAfterWrite()
411
    {
412
        parent::onAfterWrite();
413
        if ($this->ImageID) {
414
            if ($normalImage = Image::get()->exclude(array('ClassName' => 'Product_Image'))->byID($this->ImageID)) {
415
                $normalImage = $normalImage->newClassInstance('Product_Image');
416
                $normalImage->write();
417
            }
418
        }
419
    }
420
421
422
    /**
423
     * sets the FullName and FullSiteTreeField to the latest values
424
     * This can be useful as you can compare it to the ones saved in the database.
425
     * Returns true if the value is different from the one in the database.
426
     *
427
     * @return bool
428
     */
429
    public function prepareFullFields()
430
    {
431
        //FullName
432
        $fullName = '';
433
        if ($this->InternalItemID) {
434
            $fullName .= $this->InternalItemID.': ';
435
        }
436
        $fullName .= $this->Title;
437
        //FullSiteTreeSort
438
        $parentSortArray = array(sprintf('%03d', $this->Sort));
439
        $obj = $this;
440
        $parentTitleArray = array();
441
        while ($obj && $obj->ParentID) {
442
            $obj = SiteTree::get()->byID(intval($obj->ParentID) - 0);
443
            if ($obj) {
444
                $parentSortArray[] = sprintf('%03d', $obj->Sort);
445
                if (is_a($obj, Object::getCustomClass('ProductGroup'))) {
446
                    $parentTitleArray[] = $obj->Title;
447
                }
448
            }
449
        }
450
        $reverseArray = array_reverse($parentSortArray);
451
        $parentTitle = '';
452
        if (count($parentTitleArray)) {
453
            $parentTitle = ' ('._t('product.IN', 'in').' '.implode(' / ', $parentTitleArray).')';
454
        }
455
        //setting fields with new values!
456
        $this->FullName = $fullName.$parentTitle;
457
        $this->FullSiteTreeSort = implode('', array_map($this->numberPad, $reverseArray));
458
        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...
459
            return true;
460
        }
461
462
        return false;
463
    }
464
465
    //GROUPS AND SIBLINGS
466
467
    /**
468
     * Returns all the parent groups for the product.
469
     *
470
     *@return DataList (ProductGroups)
471
     **/
472
    public function AllParentGroups()
473
    {
474
        $otherGroupsArray = $this->ProductGroups()->map('ID', 'ID')->toArray();
475
476
        return ProductGroup::get()->filter(
477
            array(
478
                'ID' => array($this->ParentID => $this->ParentID) + $otherGroupsArray,
479
            )
480
        );
481
    }
482
483
    /**
484
     * Returns all the parent groups for the product,
485
     * including the parents and parents and so on.
486
     *
487
     * @return DataList (ProductGroups)
488
     */
489
    public function AllParentGroupsIncludingParents()
490
    {
491
        $directParents = $this->AllParentGroups();
492
        $allParentsArray = array();
493
        foreach ($directParents as $parent) {
494
            $obj = $parent;
495
            $allParentsArray[$obj->ID] = $obj->ID;
496
            while ($obj && $obj->ParentID) {
497
                $obj = SiteTree::get()->byID(intval($obj->ParentID) - 0);
498
                if ($obj) {
499
                    if (is_a($obj, Object::getCustomClass('ProductGroup'))) {
500
                        $allParentsArray[$obj->ID] = $obj->ID;
501
                    }
502
                }
503
            }
504
        }
505
506
        return ProductGroup::get()->filter(array('ID' => $allParentsArray));
507
    }
508
509
    /**
510
     * @return Product ...
511
     * we have this so that Variations can link to products
512
     * and products link to themselves...
513
     */
514
    public function getProduct()
515
    {
516
        return $this;
517
    }
518
519
    /**
520
     * Returns the direct parent group for the product.
521
     *
522
     * @return ProductGroup | NULL
523
     **/
524
    public function MainParentGroup()
525
    {
526
        return ProductGroup::get()->byID($this->ParentID);
527
    }
528
529
    /**
530
     * Returns the top parent group of the product (in the hierarchy).
531
     *
532
     * @return ProductGroup | NULL
533
     **/
534
    public function TopParentGroup()
535
    {
536
        $parent = $this->MainParentGroup();
537
        $x = 0;
538
        while ($parent && $x < 100) {
539
            $returnValue = $parent;
540
            $parent = DataObject::get_one(
541
                'ProductGroup',
542
                array('ID' => $parent->ParentID)
543
            );
544
            ++$x;
545
        }
546
547
        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...
548
    }
549
550
    /**
551
     * Returns products in the same group.
552
     *
553
     * @return DataList (Products)
554
     **/
555
    public function Siblings()
556
    {
557
        if ($this->ParentID) {
558
            $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...
559
            if (Versioned::current_stage() == 'Live') {
560
                $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...
561
            }
562
563
            return Product::get()
564
                ->filter(array(
565
                    'ShowInMenus' => 1,
566
                    'ParentID' => $this->ParentID,
567
                ))
568
                ->exclude(array('ID' => $this->ID));
569
        }
570
    }
571
572
    //IMAGE
573
    /**
574
     * returns a "BestAvailable" image if the current one is not available
575
     * In some cases this is appropriate and in some cases this is not.
576
     * For example, consider the following setup
577
     * - product A with three variations
578
     * - Product A has an image, but the variations have no images
579
     * With this scenario, you want to show ONLY the product image
580
     * on the product page, but if one of the variations is added to the
581
     * cart, then you want to show the product image.
582
     * This can be achieved bu using the BestAvailable image.
583
     *
584
     * @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...
585
     */
586
    public function BestAvailableImage()
587
    {
588
        $product = Product::get()->byID($this->ID);
589
        if ($product && $product->ImageID) {
590
            $image = Image::get()->byID($product->ImageID);
591
            if ($image) {
592
                if (file_exists($image->getFullPath())) {
593
                    return $image;
594
                }
595
            }
596
        }
597
        if ($parent = $this->MainParentGroup()) {
598
            return $parent->BestAvailableImage();
599
        }
600
    }
601
602
    /**
603
     * Little hack to show thumbnail in summary fields in modeladmin in CMS.
604
     *
605
     * @return string (HTML = formatted image)
606
     */
607
    public function CMSThumbnail()
608
    {
609
        if ($image = $this->Image()) {
610
            if ($image->exists()) {
611
                return $image->Thumbnail();
612
            }
613
        }
614
615
        return '['._t('product.NOIMAGE', 'no image').']';
616
    }
617
618
    /**
619
     * Returns a link to a default image.
620
     * If a default image is set in the site config then this link is returned
621
     * Otherwise, a standard link is returned.
622
     *
623
     * @return string
624
     */
625
    public function DefaultImageLink()
626
    {
627
        return $this->EcomConfig()->DefaultImageLink();
628
    }
629
630
    /**
631
     * returns the default image of the product.
632
     *
633
     * @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...
634
     */
635
    public function DefaultImage()
636
    {
637
        return $this->EcomConfig()->DefaultImage();
638
    }
639
640
    /**
641
     * returns a product image for use in templates
642
     * e.g. $DummyImage.Width();.
643
     *
644
     * @return Product_Image
645
     */
646
    public function DummyImage()
647
    {
648
        return new Product_Image();
649
    }
650
651
    // VERSIONING
652
653
    /**
654
     * Conditions for whether a product can be purchased.
655
     *
656
     * If it has the checkbox for 'Allow this product to be purchased',
657
     * as well as having a price, it can be purchased. Otherwise a user
658
     * can't buy it.
659
     *
660
     * Other conditions may be added by decorating with the canPurcahse function
661
     *
662
     * @return bool
663
     */
664
665
    /**
666
     * @TODO: complete
667
     *
668
     * @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...
669
     *
670
     * @return DataList (CHECK!)
671
     */
672
    public function getVersionedComponents($component = 'ProductVariations')
673
    {
674
        return;
675
        $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...
676
        $query = singleton(self::$has_many[$component])->buildVersionSQL("\"{$baseTable}\".ProductID = {$this->ID} AND \"{$baseTable}\".Version = {$this->Version}");
677
        $result = singleton(self::$has_many[$component])->buildDataObjectSet($query->execute());
678
679
        return $result;
680
    }
681
682
    /**
683
     * Action to return specific version of a specific product.
684
     * This can be any product to enable the retrieval of deleted products.
685
     * This is really useful for sold products where you want to retrieve the actual version that you sold.
686
     * If the version can not be found then we retrieve the current one.
687
     *
688
     * @param int $id
689
     * @param int $version
690
     *
691
     * @return DataObject | Null
692
     */
693
    public function getVersionOfBuyable($id = 0, $version = 0)
694
    {
695
        if (!$id) {
696
            $id = $this->ID;
697
        }
698
        if (!$version) {
699
            $version = $this->Version;
700
        }
701
        //not sure why this is running via OrderItem...
702
        $obj = OrderItem::get_version($this->ClassName, $id, $version);
703
        if (!$obj) {
704
            $className = $this->ClassName;
705
            $obj = $className::get()->byID($id);
706
        }
707
708
        return $obj;
709
    }
710
711
    //ORDER ITEM
712
713
    /**
714
     * returns the order item associated with the buyable.
715
     * ALWAYS returns one, even if there is none in the cart.
716
     * Does not write to database.
717
     *
718
     * @return OrderItem (no kidding)
719
     **/
720
    public function OrderItem()
721
    {
722
        //work out the filter
723
        $filterArray = array();
724
        $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...
725
        if ($extendedFilter !== null && is_array($extendedFilter) && count($extendedFilter)) {
726
            $filterArray = $extendedFilter;
727
        }
728
        //make the item and extend
729
        $item = ShoppingCart::singleton()->findOrMakeItem($this, $filterArray);
730
        $this->extend('updateDummyItem', $item);
731
732
        return $item;
733
    }
734
735
    /**
736
     * @var string
737
     */
738
    protected $defaultClassNameForOrderItem = 'Product_OrderItem';
739
740
    /**
741
     * you can overwrite this function in your buyable items (such as Product).
742
     *
743
     * @return string
744
     **/
745
    public function classNameForOrderItem()
746
    {
747
        $className = $this->defaultClassNameForOrderItem;
748
        $updateClassName = $this->extend('updateClassNameForOrderItem', $className);
749
        if ($updateClassName !== null && is_array($updateClassName) && count($updateClassName)) {
750
            $className = $updateClassName[0];
751
        }
752
753
        return $className;
754
    }
755
756
    /**
757
     * You can set an alternative class name for order item using this method.
758
     *
759
     * @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...
760
     **/
761
    public function setAlternativeClassNameForOrderItem($className)
762
    {
763
        $this->defaultClassNameForOrderItem = $className;
764
    }
765
766
    /**
767
     * This is used when you add a product to your cart
768
     * if you set it to 1 then you can add 0.1 product to cart.
769
     * If you set it to -1 then you can add 10, 20, 30, etc.. products to cart.
770
     *
771
     * @return int
772
     **/
773
    public function QuantityDecimals()
774
    {
775
        return 0;
776
    }
777
778
    /**
779
     * Number of items sold.
780
     *
781
     * @return int
782
     */
783
    public function HasBeenSold()
784
    {
785
        return $this->getHasBeenSold();
786
    }
787
    public function getHasBeenSold()
788
    {
789
        $dataList = Order::get_datalist_of_orders_with_submit_record($onlySubmittedOrders = true, $includeCancelledOrders = false);
790
        $dataList = $dataList->innerJoin('OrderAttribute', '"OrderAttribute"."OrderID" = "Order"."ID"');
791
        $dataList = $dataList->innerJoin('OrderItem', '"OrderAttribute"."ID" = "OrderItem"."ID"');
792
        $dataList = $dataList->filter(
793
            array(
794
                'BuyableID' => $this->ID,
795
                'buyableClassName' => $this->ClassName
796
            )
797
        );
798
799
        return $dataList->count();
800
    }
801
802
    //LINKS
803
804
    /**
805
     * Tells us the link to select variations
806
     * If ajaxified, this controller method (selectvariation)
807
     * Will return a html snippet for selecting the variation.
808
     * This is useful in the Product Group where you can both
809
     * non-variation and variation products to have the same
810
     * "add to cart" button.  Using this link you can provide a
811
     * pop-up select system for selecting a variation.
812
     *
813
     * @return string
814
     */
815
    public function AddVariationsLink()
816
    {
817
        return $this->Link('selectvariation');
818
    }
819
820
    /**
821
     * passing on shopping cart links ...is this necessary?? ...why not just pass the cart?
822
     *
823
     * @return string
824
     */
825
    public function AddLink()
826
    {
827
        return ShoppingCart_Controller::add_item_link($this->ID, $this->ClassName, $this->linkParameters('add'));
828
    }
829
830
    /**
831
     * link use to add (one) to cart.
832
     *
833
     *@return string
834
     */
835
    public function IncrementLink()
836
    {
837
        //we can do this, because by default add link adds one
838
        return ShoppingCart_Controller::add_item_link($this->ID, $this->ClassName, $this->linkParameters('increment'));
839
    }
840
841
    /**
842
     * Link used to remove one from cart
843
     * we can do this, because by default remove link removes one.
844
     *
845
     * @return string
846
     */
847
    public function DecrementLink()
848
    {
849
        return ShoppingCart_Controller::remove_item_link($this->ID, $this->ClassName, $this->linkParameters('decrement'));
850
    }
851
852
    /**
853
     * remove one buyable's orderitem from cart.
854
     *
855
     * @return string (Link)
856
     */
857
    public function RemoveLink()
858
    {
859
        return ShoppingCart_Controller::remove_item_link($this->ID, $this->ClassName, $this->linkParameters('remove'));
860
    }
861
862
    /**
863
     * remove all of this buyable's orderitem from cart.
864
     *
865
     * @return string (Link)
866
     */
867
    public function RemoveAllLink()
868
    {
869
        return ShoppingCart_Controller::remove_all_item_link($this->ID, $this->ClassName, $this->linkParameters('removeall'));
870
    }
871
872
    /**
873
     * remove all of this buyable's orderitem from cart and go through to this buyble to add alternative selection.
874
     *
875
     * @return string (Link)
876
     */
877
    public function RemoveAllAndEditLink()
878
    {
879
        return ShoppingCart_Controller::remove_all_item_and_edit_link($this->ID, $this->ClassName, $this->linkParameters('removeallandedit'));
880
    }
881
882
    /**
883
     * set new specific new quantity for buyable's orderitem.
884
     *
885
     * @param float
886
     *
887
     * @return string (Link)
888
     */
889
    public function SetSpecificQuantityItemLink($quantity)
890
    {
891
        return ShoppingCart_Controller::set_quantity_item_link($this->ID, $this->ClassName, array_merge($this->linkParameters('setspecificquantityitem'), array('quantity' => $quantity)));
892
    }
893
894
    /**
895
     * @return string
896
     */
897
    public function AddToCartAndGoToCheckoutLink()
898
    {
899
        $array = $this->linkParameters();
900
        $array['BackURL'] = urlencode(CheckoutPage::find_link());
901
902
        return ShoppingCart_Controller::add_item_link($this->ID, $this->ClassName, $array);
903
    }
904
905
    /**
906
     *
907
     *
908
     * @return string
909
     */
910
    public function VersionedLink()
911
    {
912
        return Controller::join_links(
913
             Director::baseURL(),
914
             EcommerceConfig::get('ShoppingCart_Controller', 'url_segment'),
915
             'submittedbuyable',
916
             $this->ClassName,
917
             $this->ID,
918
             $this->Version
919
         );
920
    }
921
922
    public function RemoveFromSaleLink()
923
    {
924
        return ShoppingCart_Controller::remove_from_sale_link($this->ID, $this->ClassName);
925
    }
926
927
    /**
928
     * Here you can add additional information to your product
929
     * links such as the AddLink and the RemoveLink.
930
     * One useful parameter you can add is the BackURL link.
931
     *
932
     * Usage would be by means of
933
     * 1. decorating product
934
     * 2. adding a updateLinkParameters method
935
     * 3. adding items to the array.
936
     *
937
     * You can also extend Product and override this method...
938
     *
939
     * @return array
940
     **/
941
    protected function linkParameters($type = '')
942
    {
943
        $array = array();
944
        $extendedArray = $this->extend('updateLinkParameters', $array, $type);
945
        if ($extendedArray !== null && is_array($extendedArray) && count($extendedArray)) {
946
            foreach ($extendedArray as $extendedArrayUpdate) {
947
                $array = array_merge($array, $extendedArrayUpdate);
948
            }
949
        }
950
951
        return $array;
952
    }
953
954
    //TEMPLATE STUFF
955
956
    /**
957
     * @return bool
958
     */
959
    public function IsInCart()
960
    {
961
        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...
962
    }
963
964
    /**
965
     * @return EcomQuantityField
966
     */
967
    public function EcomQuantityField()
968
    {
969
        return EcomQuantityField::create($this);
970
    }
971
972
    /**
973
     * returns the instance of EcommerceConfigAjax for use in templates.
974
     * In templates, it is used like this:
975
     * $EcommerceConfigAjax.TableID.
976
     *
977
     * @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...
978
     **/
979
    public function AJAXDefinitions()
980
    {
981
        return EcommerceConfigAjax::get_one($this);
982
    }
983
984
    /**
985
     * @return EcommerceDBConfig
986
     **/
987
    public function EcomConfig()
988
    {
989
        return EcommerceDBConfig::current_ecommerce_db_config();
990
    }
991
992
    /**
993
     * Is it a variation?
994
     *
995
     * @return bool
996
     */
997
    public function IsProductVariation()
998
    {
999
        return false;
1000
    }
1001
1002
    /**
1003
     * tells us if the current page is part of e-commerce.
1004
     *
1005
     * @return bool
1006
     */
1007
    public function IsEcommercePage()
1008
    {
1009
        return true;
1010
    }
1011
1012
    public function AllowPurchaseNice()
1013
    {
1014
        return $this->obj('AllowPurchase')->Nice();
1015
    }
1016
1017
    /**
1018
     * Products have a standard price, but for specific situations they have a calculated price.
1019
     * The Price can be changed for specific member discounts, etc...
1020
     *
1021
     * @return float
1022
     */
1023
    public function CalculatedPrice()
1024
    {
1025
        return $this->getCalculatedPrice();
1026
    }
1027
1028
    private static $_calculated_price_cache = array();
1029
1030
    /**
1031
     * Products have a standard price, but for specific situations they have a calculated price.
1032
     * The Price can be changed for specific member discounts, etc...
1033
     *
1034
     * We add three "hooks" / "extensions" here... so that you can update prices
1035
     * in a logical order (e.g. firstly change to forex and then apply discount)
1036
     *
1037
     * @return float
1038
     */
1039
    public function getCalculatedPrice($forceRecalculation = false)
1040
    {
1041
        if (! isset(self::$_calculated_price_cache[$this->ID]) || $forceRecalculation) {
1042
            $price = $this->Price;
1043
            $updatedPrice = $this->extend('updateBeforeCalculatedPrice', $price);
1044
            if ($updatedPrice !== null && is_array($updatedPrice) && count($updatedPrice)) {
1045
                $price = $updatedPrice[0];
1046
            }
1047
            $updatedPrice = $this->extend('updateCalculatedPrice', $price);
1048
            if ($updatedPrice !== null && is_array($updatedPrice) && count($updatedPrice)) {
1049
                $price = $updatedPrice[0];
1050
            }
1051
            $updatedPrice = $this->extend('updateAfterCalculatedPrice', $price);
1052
            if ($updatedPrice !== null && is_array($updatedPrice) && count($updatedPrice)) {
1053
                $price = $updatedPrice[0];
1054
            }
1055
            self::$_calculated_price_cache[$this->ID] = $price;
1056
        }
1057
        return self::$_calculated_price_cache[$this->ID];
1058
    }
1059
1060
    /**
1061
     * How do we display the price?
1062
     *
1063
     * @return Money
1064
     */
1065
    public function CalculatedPriceAsMoney()
1066
    {
1067
        return $this->getCalculatedPriceAsMoney();
1068
    }
1069
    public function getCalculatedPriceAsMoney()
1070
    {
1071
        return EcommerceCurrency::get_money_object_from_order_currency($this->getCalculatedPrice());
1072
    }
1073
1074
    //CRUD SETTINGS
1075
1076
    /**
1077
     * Is the product for sale?
1078
     *
1079
     * @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...
1080
     * @param bool   $checkPrice
1081
     *
1082
     * @return bool
1083
     */
1084
    public function canPurchase(Member $member = null, $checkPrice = true)
1085
    {
1086
        $config = $this->EcomConfig();
1087
        //shop closed
1088
        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...
1089
            return false;
1090
        }
1091
        //not sold at all
1092
        if (! $this->AllowPurchase) {
1093
            return false;
1094
        }
1095
        //check country
1096
        if (! $member) {
1097
            $member = Member::currentUser();
1098
        }
1099
        $extended = $this->extendedCan('canPurchaseByCountry', $member);
1100
        if ($extended !== null) {
1101
            return $extended;
1102
        }
1103
        if (! EcommerceCountry::allow_sales()) {
1104
            return false;
1105
        }
1106
1107
        if ($checkPrice) {
1108
            $price = $this->getCalculatedPrice();
1109
            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...
1110
                return false;
1111
            }
1112
        }
1113
        // Standard mechanism for accepting permission changes from decorators
1114
        $extended = $this->extendedCan(__FUNCTION__, $member);
1115
        if ($extended !== null) {
1116
            return $extended;
1117
        }
1118
        return $this->AllowPurchase;
1119
    }
1120
1121
    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...
1122
    {
1123
        if (! $member) {
1124
            $member = Member::currentUser();
1125
        }
1126
        $extended = $this->extendedCan(__FUNCTION__, $member);
1127
        if ($extended !== null) {
1128
            return $extended;
1129
        }
1130
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
1131
            return true;
1132
        }
1133
1134
        return parent::canCreate($member);
1135
    }
1136
1137
    /**
1138
     * Shop Admins can edit.
1139
     *
1140
     * @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...
1141
     *
1142
     * @return bool
1143
     */
1144
    public function canEdit($member = null)
1145
    {
1146
        if (! $member) {
1147
            $member = Member::currentUser();
1148
        }
1149
        $extended = $this->extendedCan(__FUNCTION__, $member);
1150
        if ($extended !== null) {
1151
            return $extended;
1152
        }
1153
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
1154
            return true;
1155
        }
1156
1157
        return parent::canEdit($member);
1158
    }
1159
1160
    /**
1161
     * Standard SS method.
1162
     *
1163
     * @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...
1164
     *
1165
     * @return bool
1166
     */
1167
    public function canDelete($member = null)
1168
    {
1169
        if (is_a(Controller::curr(), Object::getCustomClass('ProductsAndGroupsModelAdmin'))) {
1170
            return false;
1171
        }
1172
        if (! $member) {
1173
            $member = Member::currentUser();
1174
        }
1175
        $extended = $this->extendedCan(__FUNCTION__, $member);
1176
        if ($extended !== null) {
1177
            return $extended;
1178
        }
1179
1180
        return $this->canEdit($member);
1181
    }
1182
1183
    /**
1184
     * Standard SS method.
1185
     *
1186
     * @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...
1187
     *
1188
     * @return bool
1189
     */
1190
    public function canPublish($member = null)
1191
    {
1192
        return $this->canEdit($member);
1193
    }
1194
1195
1196
    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...
1197
    {
1198
        $html = EcommerceTaskDebugCart::debug_object($this);
1199
        $html .= '<ul>';
1200
        $html .= '<li><hr />Links<hr /></li>';
1201
        $html .= '<li><b>Link:</b> <a href="'.$this->Link().'">'.$this->Link().'</a></li>';
1202
        $html .= '<li><b>Ajax Link:</b> <a href="'.$this->AjaxLink().'">'.$this->AjaxLink().'</a></li>';
1203
        $html .= '<li><b>AddVariations Link:</b> <a href="'.$this->AddVariationsLink().'">'.$this->AddVariationsLink().'</a></li>';
1204
        $html .= '<li><b>Add to Cart Link:</b> <a href="'.$this->AddLink().'">'.$this->AddLink().'</a></li>';
1205
        $html .= '<li><b>Increment Link:</b> <a href="'.$this->IncrementLink().'">'.$this->IncrementLink().'</a></li>';
1206
        $html .= '<li><b>Decrement Link:</b> <a href="'.$this->DecrementLink().'">'.$this->DecrementLink().'</a></li>';
1207
        $html .= '<li><b>Remove Link:</b> <a href="'.$this->RemoveAllLink().'">'.$this->RemoveLink().'</a></li>';
1208
        $html .= '<li><b>Remove All Link:</b> <a href="'.$this->RemoveAllLink().'">'.$this->RemoveAllLink().'</a></li>';
1209
        $html .= '<li><b>Remove All and Edit Link:</b> <a href="'.$this->RemoveAllAndEditLink().'">'.$this->RemoveAllAndEditLink().'</a></li>';
1210
        $html .= '<li><b>Set Specific Quantity Item Link (e.g. 77):</b> <a href="'.$this->SetSpecificQuantityItemLink(77).'">'.$this->SetSpecificQuantityItemLink(77).'</a></li>';
1211
1212
        $html .= '<li><hr />Cart<hr /></li>';
1213
        $html .= '<li><b>Allow Purchase (DB Value):</b> '.$this->AllowPurchaseNice().' </li>';
1214
        $html .= '<li><b>Can Purchase (overal calculation):</b> '.($this->canPurchase() ? 'YES' : 'NO').' </li>';
1215
        $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...
1216
        $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>';
1217
        $html .= '<li><b>Allow sales to this country ('.EcommerceCountry::get_country().'):</b> '.(EcommerceCountry::allow_sales() ? 'YES' : 'NO').' </li>';
1218
        $html .= '<li><b>Class Name for OrderItem:</b> '.$this->classNameForOrderItem().' </li>';
1219
        $html .= '<li><b>Quantity Decimals:</b> '.$this->QuantityDecimals().' </li>';
1220
        $html .= '<li><b>Is In Cart:</b> '.($this->IsInCart() ? 'YES' : 'NO').' </li>';
1221
        $html .= '<li><b>Has Been Sold:</b> '.($this->HasBeenSold() ?  'YES' : 'NO').' </li>';
1222
        $html .= '<li><b>Calculated Price:</b> '.$this->CalculatedPrice().' </li>';
1223
        $html .= '<li><b>Calculated Price as Money:</b> '.$this->getCalculatedPriceAsMoney()->Nice().' </li>';
1224
1225
        $html .= '<li><hr />Location<hr /></li>';
1226
        $html .= '<li><b>Main Parent Group:</b> '.$this->MainParentGroup()->Title.'</li>';
1227
        $html .= '<li><b>All Others Parent Groups:</b> '.($this->AllParentGroups()->count() ? '<pre>'.print_r($this->AllParentGroups()->map()->toArray(), 1).'</pre>' : 'none').'</li>';
1228
1229
        $html .= '<li><hr />Image<hr /></li>';
1230
        $html .= '<li><b>Image:</b> '.($this->BestAvailableImage() ? '<img src='.$this->BestAvailableImage()->Link().' />' : 'no image').' </li>';
1231
        $productGroup = ProductGroup::get()->byID($this->ParentID);
1232
        if ($productGroup) {
1233
            $html .= '<li><hr />Product Example<hr /></li>';
1234
            $html .= '<li><b>Product Group View:</b> <a href="'.$productGroup->Link().'">'.$productGroup->Title.'</a> </li>';
1235
            $html .= '<li><b>Product Group Debug:</b> <a href="'.$productGroup->Link('debug').'">'.$productGroup->Title.'</a> </li>';
1236
            $html .= '<li><b>Product Group Admin:</b> <a href="'.'/admin/pages/edit/show/'.$productGroup->ID.'">'.$productGroup->Title.' Admin</a> </li>';
1237
            $html .= '<li><b>Edit this Product:</b> <a href="'.'/admin/pages/edit/show/'.$this->ID.'">'.$this->Title.' Admin</a> </li>';
1238
        }
1239
        $html .= '</ul>';
1240
1241
        return $html;
1242
        $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...
1243
1244
        return $html;
1245
    }
1246
1247
    /**
1248
     *
1249
     * @int
1250
     */
1251
    public function IDForSearchResults()
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...
1252
    {
1253
        return $this->ID;
1254
    }
1255
}
1256
1257
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...
1258
{
1259
    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...
1260
        'viewversion',
1261
        'ajaxview',
1262
        'addproductfromform',
1263
        'debug' => 'ADMIN',
1264
    );
1265
1266
    /**
1267
     * is this the current version?
1268
     *
1269
     * @var bool
1270
     */
1271
    protected $isCurrentVersion = true;
1272
1273
    /**
1274
     * Standard SS method.
1275
     */
1276
    public function init()
1277
    {
1278
        parent::init();
1279
        Requirements::themedCSS('Product', 'ecommerce');
1280
        Requirements::javascript('ecommerce/javascript/EcomProducts.js');
1281
    }
1282
1283
    /**
1284
     * view earlier version of a product
1285
     * returns error or changes datarecord to earlier version
1286
     * if the ID does not match the Page then we look for the variation.
1287
     *
1288
     * @param SS_HTTPRequest
1289
     */
1290
    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...
1291
    {
1292
        $version = intval($request->param('ID')) - 0;
1293
        $currentVersion = $this->Version;
1294
        if ($currentVersion != $version) {
1295
            if ($record = $this->getVersionOfBuyable($this->ID, $version)) {
1296
                //we check again, because we may actually get the same version back...
1297
                if ($record->Version != $this->Version) {
1298
                    $this->record = $record;
1299
                    $this->dataRecord->AllowPurchase = false;
1300
                    $this->AllowPurchase = false;
1301
                    $this->isCurrentVersion = false;
1302
                    $this->Title .= _t('Product.OLDERVERSION', ' - Older Version');
1303
                    $this->MetaTitle .= _t('Product.OLDERVERSION', ' - Older Version');
1304
                }
1305
            } else {
1306
                return $this->httpError(404);
1307
            }
1308
        }
1309
1310
        return array();
1311
    }
1312
1313
    /**
1314
     * Standard SS method
1315
     * Returns a snippet when requested by ajax.
1316
     */
1317
    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...
1318
    {
1319
        Config::nest();
1320
        Config::inst()->update('SSViewer', 'theme_enabled', true);
1321
        $html = $this->renderWith('ProductGroupItemMoreDetail');
1322
        Config::unnest();
1323
1324
        return $html;
1325
    }
1326
1327
    /**
1328
     * returns a form for adding products to cart.
1329
     *
1330
     * @return Form
1331
     */
1332
    public function AddProductForm()
1333
    {
1334
        if ($this->canPurchase()) {
1335
            $farray = array();
1336
            $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...
1337
            $fields = new FieldList($farray);
1338
            $fields->push(new NumericField('Quantity', 'Quantity', 1)); //TODO: perhaps use a dropdown instead (elimiates need to use keyboard)
1339
            $actions = new FieldList(
1340
                new FormAction('addproductfromform', _t('Product.ADDLINK', 'Add this item to cart'))
1341
            );
1342
            $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...
1343
            $validator = new RequiredFields($requiredfields);
1344
            $form = new Form($this, 'AddProductForm', $fields, $actions, $validator);
1345
1346
            return $form;
1347
        } else {
1348
            return _t('Product.PRODUCTNOTFORSALE', 'Product not for sale');
1349
        }
1350
    }
1351
1352
    /**
1353
     * executes the AddProductForm.
1354
     *
1355
     * @param array $data
1356
     * @param Form  $form
1357
     */
1358
    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...
1359
    {
1360
        if (!$this->IsInCart()) {
1361
            $quantity = round($data['Quantity'], $this->QuantityDecimals());
1362
            if (!$quantity) {
1363
                $quantity = 1;
1364
            }
1365
            $product = Product::get()->byID($this->ID);
1366
            if ($product) {
1367
                ShoppingCart::singleton()->addBuyable($product, $quantity);
1368
            }
1369
            if ($this->IsInCart()) {
1370
                $msg = _t('Order.SUCCESSFULLYADDED', 'Added to cart.');
1371
                $status = 'good';
1372
            } else {
1373
                $msg = _t('Order.NOTADDEDTOCART', 'Not added to cart.');
1374
                $status = 'bad';
1375
            }
1376
            if (Director::is_ajax()) {
1377
                return ShoppingCart::singleton()->setMessageAndReturn($msg, $status);
1378
            } else {
1379
                $form->sessionMessage($msg, $status);
1380
                $this->redirectBack();
1381
            }
1382
        } else {
1383
            return EcomQuantityField::create($this);
1384
        }
1385
    }
1386
1387
    /**
1388
     * Is this an older version?
1389
     *
1390
     * @return bool
1391
     */
1392
    public function IsOlderVersion()
1393
    {
1394
        return $this->isCurrentVersion ? false : true;
1395
    }
1396
1397
    /**
1398
     * This method can be extended to show products in the side bar.
1399
     *
1400
     * @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...
1401
     */
1402
    public function SidebarProducts()
1403
    {
1404
        return;
1405
    }
1406
1407
    /**
1408
     * This method can be extended to show products in the side bar.
1409
     *
1410
     * @return Product | Null
1411
     */
1412
    public function NextProduct()
1413
    {
1414
        $array = $this->getListOfIDs();
1415
        $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...
1416
        foreach ($array as $key => $id) {
1417
            $id = intval($id);
1418
            if ($id == $this->ID) {
1419
                if (isset($array[$key + 1])) {
1420
                    return Product::get()->byID(intval($array[$key + 1]));
1421
                }
1422
            }
1423
        }
1424
    }
1425
1426
    /**
1427
     * This method can be extended to show products in the side bar.
1428
     *
1429
     * @return Product | Null
1430
     */
1431
    public function PreviousProduct()
1432
    {
1433
        $array = $this->getListOfIDs();
1434
        $previousID = 0;
1435
        foreach ($array as $key => $id) {
1436
            $id = intval($id);
1437
            if ($id == $this->ID) {
1438
                return Product::get()->byID($previousID);
1439
            }
1440
            $previousID = $id;
1441
        }
1442
1443
        return;
1444
    }
1445
1446
    /**
1447
     * This method can be extended to show products in the side bar.
1448
     *
1449
     * @return bool
1450
     */
1451
    public function HasPreviousOrNextProduct()
1452
    {
1453
        return $this->PreviousProduct() || $this->NextProduct() ? true : false;
1454
    }
1455
1456
    /**
1457
     * returns an array of product IDs, as saved in the last
1458
     * ProductGroup view (saved using session).
1459
     *
1460
     * @return array
1461
     */
1462
    protected function getListOfIDs()
1463
    {
1464
        $listOfIDs = Session::get(EcommerceConfig::get('ProductGroup', 'session_name_for_product_array'));
1465
        if ($listOfIDs) {
1466
            $arrayOfIDs = explode(',', $listOfIDs);
1467
            if (is_array($arrayOfIDs)) {
1468
                return $arrayOfIDs;
1469
            }
1470
        }
1471
1472
        return array();
1473
    }
1474
1475
    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...
1476
    {
1477
        $member = Member::currentUser();
1478
        if (!$member || !$member->IsShopAdmin()) {
1479
            $messages = array(
1480
                'default' => 'You must login as an admin to access debug functions.',
1481
            );
1482
            Security::permissionFailure($this, $messages);
1483
        }
1484
1485
        return $this->dataRecord->debug();
1486
    }
1487
}
1488