ProductVariation::getCMSFields()   F
last analyzed

Complexity

Conditions 18
Paths 73

Size

Total Lines 120

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 120
rs 3.8933
c 0
b 0
f 0
cc 18
nc 73
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 *
5
 */
6
class ProductVariation extends DataObject implements BuyableModel, EditableEcommerceObject
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...
7
{
8
    /**
9
     * Standard SS variable.
10
     */
11
    private static $api_access = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
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...
12
        'view' => array(
13
            'Title',
14
            'Description',
15
            'FullName',
16
            'AllowPurchase',
17
            'InternalItemID',
18
            'NumberSold',
19
            'Price',
20
            'Weight',
21
            'Model',
22
            'Quantifier',
23
            'Version',
24
        ),
25
    );
26
27
    /**
28
     * Standard SS variable.
29
     */
30
    private static $db = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
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...
31
        'InternalItemID' => 'Varchar(30)',
32
        'Price' => 'Currency',
33
        'Weight' => 'Float',
34
        'Model' => 'Varchar(30)',
35
        'Quantifier' => 'Varchar(30)',
36
        'AllowPurchase' => 'Boolean',
37
        'Sort' => 'Int',
38
        'NumberSold' => 'Int',
39
        'Description' => 'Varchar(255)',
40
        'FullName' => 'Varchar(255)',
41
        'FullSiteTreeSort' => 'Varchar(110)',
42
    );
43
44
    /**
45
     * Standard SS variable.
46
     */
47
    private static $has_one = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
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...
48
        'Product' => 'Product',
49
        'Image' => 'Product_Image',
50
    );
51
52
    /**
53
     * Standard SS variable.
54
     */
55
    private static $many_many = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
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...
56
        'AttributeValues' => 'ProductAttributeValue',
57
    );
58
59
    /**
60
     * Standard SS variable.
61
     */
62
    private static $casting = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
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...
63
        'Parent' => 'Product',
64
        'Title' => 'HTMLText',
65
        'Link' => 'Text',
66
        'AllowPurchaseNice' => 'Varchar',
67
        'CalculatedPrice' => 'Currency',
68
        'CalculatedPriceAsMoney' => 'Money',
69
    );
70
71
    /**
72
     * Standard SS variable.
73
     */
74
    private static $defaults = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
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...
75
        'AllowPurchase' => 1,
76
    );
77
78
    /**
79
     * Standard SS variable.
80
     */
81
    private static $versioning = array(
0 ignored issues
show
Unused Code introduced by
The property $versioning 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...
82
        'Stage',
83
    );
84
85
    /**
86
     * Standard SS variable.
87
     */
88
    private static $extensions = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $extensions 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...
89
        "Versioned('Stage')",
90
    );
91
92
    /**
93
     * Standard SS variable.
94
     */
95
    private static $indexes = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
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...
96
        'Sort' => true,
97
        'FullName' => true,
98
        'FullSiteTreeSort' => true,
99
    );
100
101
    /**
102
     * Standard SS variable.
103
     */
104
    private static $field_labels = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $field_labels 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...
105
        'Description' => 'Title (optional)',
106
    );
107
108
    /**
109
     * Standard SS variable.
110
     */
111
    private static $summary_fields = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
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...
112
        'CMSThumbnail' => 'Image',
113
        'Title' => 'Title',
114
        'Price' => 'Price',
115
        'AllowPurchaseNice' => 'For Sale',
116
    );
117
118
    /**
119
     * Standard SS variable.
120
     */
121
    private static $searchable_fields = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
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...
122
        'FullName' => array(
123
            'title' => 'Keyword',
124
            'field' => 'TextField',
125
            'filter' => 'PartialMatchFilter',
126
        ),
127
        'Price' => array(
128
            'title' => 'Price',
129
            'field' => 'NumericField',
130
        ),
131
        'InternalItemID' => array(
132
            'title' => 'Internal Item ID',
133
            'filter' => 'PartialMatchFilter',
134
        ),
135
        'AllowPurchase',
136
    );
137
138
    /**
139
     * Standard SS variable.
140
     */
141
    private static $default_sort = '"AllowPurchase" DESC, "FullSiteTreeSort" ASC, "Sort" ASC, "InternalItemID" ASC, "Price" ASC';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
Unused Code introduced by
The property $default_sort 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...
142
143
    /**
144
     * Standard SS variable.
145
     */
146
    private static $singular_name = 'Product Variation';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
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...
147
    public function i18n_singular_name()
148
    {
149
        return _t('ProductVariation.PRODUCTVARIATION', 'Product Variation');
150
    }
151
152
    /**
153
     * Standard SS variable.
154
     */
155
    private static $plural_name = 'Product Variations';
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
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...
156
    public function i18n_plural_name()
157
    {
158
        return _t('ProductVariation.PRODUCTVARIATIONS', 'Product Variations');
159
    }
160
    public static function get_plural_name()
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...
161
    {
162
        $obj = Injector::inst()->get('ProductVariation');
163
164
        return $obj->i18n_plural_name();
165
    }
166
167
    /**
168
     * How is the title build up?
169
     *
170
     * @var array
171
     **/
172
    private static $title_style_option = array(
173
        'default' => array(
174
            'ShowType' => true,
175
            'BetweenTypeAndValue' => ': ',
176
            'BetweenVariations' => ', ',
177
        ),
178
    );
179
180
    /**
181
     * change the way the title of the variation is displayed
182
     * @param string $code                key
183
     * @param string $showType            do we show the type (e.g. colour, size)?
184
     * @param string $betweenTypeAndValue e.g. a semi-colon (:)
185
     * @param string $betweenVariations   e.g. a comma (,)
186
     */
187
    public static function add_title_style_option($code, $showType, $betweenTypeAndValue, $betweenVariations)
188
    {
189
        self::$title_style_option[$code] = array(
190
                'ShowType' => $showType,
191
                'BetweenTypeAndValue' => $betweenTypeAndValue,
192
                'BetweenVariations' => $betweenVariations,
193
            );
194
        Config::inst()->update('ProductVariation', 'current_style_option_code', $code);
195
    }
196
197
    /**
198
     * remove style option by key
199
     * @param  string $code               key
200
     */
201
    public static function remove_title_style_option($code)
202
    {
203
        unset(self::$title_style_option[$code]);
204
    }
205
206
    private static $current_style_option_code = 'default';
0 ignored issues
show
Unused Code introduced by
The property $current_style_option_code 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...
207
208
    public static function get_current_style_option_array()
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...
209
    {
210
        return self::$title_style_option[Config::inst()->get('ProductVariation', 'current_style_option_code')];
211
    }
212
213
    /**
214
     * Standard SS method.
215
     *
216
     * @return FieldSet
217
     */
218
    public function getCMSFields()
219
    {
220
        //backup in case there are no products.
221
        if (Product::get()->count() == 0) {
222
            return parent::getCMSFields();
223
        }
224
        $product = $this->Product();
0 ignored issues
show
Bug introduced by
The method Product() does not exist on ProductVariation. Did you maybe mean setSaveParentProduct()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
225
        if (class_exists('CMSEditLinkField')) {
226
            $productField = CMSEditLinkField::create(
227
                'ProductID',
228
                $this->Product()->i18n_singular_name(),
0 ignored issues
show
Bug introduced by
The method Product() does not exist on ProductVariation. Did you maybe mean setSaveParentProduct()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
229
                $this->Product()
0 ignored issues
show
Bug introduced by
The method Product() does not exist on ProductVariation. Did you maybe mean setSaveParentProduct()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
230
            );
231
        } else {
232
            $productCount = Product::get()->count();
233
            if ($productCount > 500) {
234
                user_error('We recommend you install https://github.com/briceburg/silverstripe-pickerfield');
235
                $productField = ReadonlyField::create(
236
                    'ProductIDTitle',
237
                    _t('ProductVariation.PRODUCT', 'Product'),
238
                    $this->Product() ? $this->Product()->Title : _t('ProductVariation.NO_PRODUCT', 'none')
0 ignored issues
show
Bug introduced by
The method Product() does not exist on ProductVariation. Did you maybe mean setSaveParentProduct()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
239
                );
240
            } else {
241
                $productField = new DropdownField('ProductID', _t('ProductVariation.PRODUCT', 'Product'), Product::get()->map('ID', 'Title')->toArray());
242
                $productField->setEmptyString('(Select one)');
243
            }
244
        }
245
        $fields = new FieldList(
246
            new TabSet(
247
                'Root',
248
                new Tab(
249
                    'Main',
250
                    $productField,
251
                    $fullNameLinkField = ReadOnlyField::create('FullNameLink', _t('ProductVariation.FULLNAME', 'Full Name'), '<a href="'.$this->Link().'">'.$this->FullName.'</a>'),
0 ignored issues
show
Documentation introduced by
The property FullName does not exist on object<ProductVariation>. 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...
252
                    new NumericField('Price', _t('ProductVariation.PRICE', 'Price')),
253
                    new CheckboxField('AllowPurchase', _t('ProductVariation.ALLOWPURCHASE', 'Allow Purchase ?'))
254
                ),
255
                new Tab(
256
                    'Details',
257
                    new TextField('InternalItemID', _t('ProductVariation.INTERNALITEMID', 'Internal Item ID')),
258
                    new TextField('Description', _t('ProductVariation.DESCRIPTION', 'Description (optional)'))
259
                ),
260
                new Tab(
261
                    'Image',
262
                    new Product_ProductImageUploadField('Image')
263
                )
264
            )
265
        );
266
        $fullNameLinkField->dontEscape = true;
267
        if ($this->EcomConfig()->ProductsHaveWeight) {
268
            $fields->addFieldToTab('Root.Details', new NumericField('Weight', _t('ProductVariation.WEIGHT', 'Weight')));
269
        }
270
        if ($this->EcomConfig()->ProductsHaveModelNames) {
271
            $fields->addFieldToTab('Root.Details', new TextField('Model', _t('ProductVariation.MODEL', 'Model')));
272
        }
273
        if ($this->EcomConfig()->ProductsHaveQuantifiers) {
274
            $fields->addFieldToTab('Root.Details', new TextField('Quantifier', _t('ProductVariation.QUANTIFIER', 'Quantifier (e.g. per kilo, per month, per dozen, each)')));
275
        }
276
        $fields->addFieldToTab('Root.Details', new ReadOnlyField('FullSiteTreeSort', _t('Product.FULLSITETREESORT', 'Full sort index')));
277
        if ($product) {
278
            $types = $product->VariationAttributes();
279
            if ($this->ID) {
280
                $values = $this->AttributeValues();
0 ignored issues
show
Bug introduced by
The method AttributeValues() does not exist on ProductVariation. Did you maybe mean AttributeValuesSorted()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
281
                foreach ($types as $type) {
282
                    $isReadonlyField = false;
283
                    $rightTitle = '';
284
                    $field = $type->getDropDownField();
285
                    if ($field) {
286
                        $value = $values->find('TypeID', $type->ID);
287
                        if ($value) {
288
                            $isReadonlyField = true;
289
                        } else {
290
                            if ($this->HasBeenSold()) {
291
                                $isReadonlyField = true;
292
                                $rightTitle = _t(
293
                                    'ProductVariation.ALREADYPURCHASED',
294
                                    'NOT SET (you can not select a value now because it has already been purchased).'
295
                                );
296
                            } else {
297
                                $field = $type->getDropDownField();
298
                                if ($field instanceof DropdownField) {
299
                                    $field->setEmptyString('');
300
                                }
301
                            }
302
                        }
303
                    } else {
304
                        $isReadonlyField = true;
305
                        $rightTitle = _t('ProductVariation.NOVALUESTOSELECT', 'No values to select');
306
                    }
307
                    if ($isReadonlyField) {
308
                        if (class_exists('CMSEditLinkField')) {
309
                            $field = CMSEditLinkField::create("Type{$type->ID}", $type->Name, $value)
0 ignored issues
show
Bug introduced by
The variable $value 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...
310
                                ->setDescription($rightTitle);
311
                        } else {
312
                            $field->setValue($value->ID);
313
                            $field = $field->performReadonlyTransformation();
314
                            $field->setName("Type{$type->ID}");
315
                            $field->setDescription($rightTitle);
316
                        }
317
                    }
318
                    $fields->addFieldToTab('Root.Attributes', $field);
319
                }
320
            } else {
321
                foreach ($types as $type) {
322
                    $field = $type->getDropDownField();
323
                    $fields->addFieldToTab('Root.Attributes', $field);
324
                }
325
            }
326
        }
327
        $fields->addFieldToTab(
328
            'Root.Main',
329
            new LiteralField(
330
                'AddToCartLink',
331
                '<p class="message good"><a href="'.$this->AddLink().'">'._t('Product.ADD_TO_CART', 'add to cart').'</a></p>'
332
            )
333
        );
334
        $this->extend('updateCMSFields', $fields);
335
336
        return $fields;
337
    }
338
339
    /**
340
     * link to edit the record.
341
     *
342
     * @param string | Null $action - e.g. edit
343
     *
344
     * @return string
345
     */
346
    public function CMSEditLink($action = null)
347
    {
348
        return Controller::join_links(
349
            Director::baseURL(),
350
            '/admin/product-config/ProductVariation/EditForm/field/ProductVariation/item/'.$this->ID.'/',
351
            $action
352
        );
353
    }
354
355
    /**
356
     * Use the sort order of the variation attributes to order the attribute values.
357
     * This ensures that when VariationAttributes is used for a table header
358
     * and AttributeValues are used for the table rows then the columns will be
359
     * in the same order.
360
     *
361
     * @return DataObjectSet
0 ignored issues
show
Documentation introduced by
Should the return type not be ArrayList?

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...
362
     */
363
    public function AttributeValuesSorted()
364
    {
365
        $values = parent::AttributeValues();
0 ignored issues
show
Bug introduced by
The method AttributeValues() does not seem to exist on object<DataObject>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Comprehensibility Bug introduced by
It seems like you call parent on a different method (AttributeValues() instead of AttributeValuesSorted()). Are you sure this is correct? If so, you might want to change this to $this->AttributeValues().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
366
        $types = $this->Product()->VariationAttributes();
0 ignored issues
show
Bug introduced by
The method Product() does not exist on ProductVariation. Did you maybe mean setSaveParentProduct()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
367
        $result = ArrayList::create();
368
        foreach ($types as $type) {
369
            $result->push($values->find('TypeID', $type->ID));
370
        }
371
372
        return $result;
373
    }
374
375
    /**
376
     * standard SS method.
377
     */
378
    public function populateDefaults()
379
    {
380
        parent::populateDefaults();
381
        $this->AllowPurchase = 1;
0 ignored issues
show
Documentation introduced by
The property AllowPurchase does not exist on object<ProductVariation>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
382
    }
383
384
    /**
385
     * Puts together a title for the Product Variation.
386
     *
387
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be HTMLText|array|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...
388
     */
389
    public function Title()
390
    {
391
        return $this->getTitle();
392
    }
393
    public function TitleWithHTML($noProductTitle = false)
394
    {
395
        return $this->getTitle(true, $noProductTitle);
396
    }
397
    public function getTitle($withHTML = false, $noProductTitle = false)
398
    {
399
        $array = array(
400
            'Values' => $this->AttributeValues(),
0 ignored issues
show
Bug introduced by
The method AttributeValues() does not exist on ProductVariation. Did you maybe mean AttributeValuesSorted()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
401
            'Product' => $this->Product(),
0 ignored issues
show
Bug introduced by
The method Product() does not exist on ProductVariation. Did you maybe mean setSaveParentProduct()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
402
            'Description' => $this->Description,
0 ignored issues
show
Documentation introduced by
The property Description does not exist on object<ProductVariation>. 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...
403
            'InternalItemID' => $this->InternalItemID,
0 ignored issues
show
Documentation introduced by
The property InternalItemID does not exist on object<ProductVariation>. 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...
404
            'Price' => $this->Price,
0 ignored issues
show
Documentation introduced by
The property Price does not exist on object<ProductVariation>. 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...
405
            'WithProductTitle' => $noProductTitle ? false : true,
406
        );
407
        $html = $this->customise($array)->renderWith('ProductVariationItem');
408
        if ($withHTML) {
409
            return $html;
410
        } else {
411
            //@todo: reverse the ampersands, etc...
412
            return Convert::raw2att(trim(preg_replace('/\s+/', ' ', strip_tags($html))));
413
        }
414
    }
415
416
    /**
417
     * shorthand.
418
     *
419
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be HTMLText|array|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...
420
     */
421
    public function FullDescription()
422
    {
423
        return $this->Title(true, false);
0 ignored issues
show
Unused Code introduced by
The call to ProductVariation::Title() has too many arguments starting with true.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
424
    }
425
426
    /**
427
     * shorthand.
428
     *
429
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be HTMLText|array|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...
430
     */
431
    public function ImgAltTag()
432
    {
433
        return $this->Title(false, false);
0 ignored issues
show
Unused Code introduced by
The call to ProductVariation::Title() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
434
    }
435
436
    /**
437
     * returns YES or NO for the CMS Fields.
438
     *
439
     * @return string
440
     */
441
    public function AllowPurchaseNice()
442
    {
443
        return $this->obj('AllowPurchase')->Nice();
444
    }
445
446
    protected $currentStageOfRequest = '';
447
448
    /**
449
     * when we save this object, should we save the parent
450
     * as well?
451
     *
452
     * @var bool
453
     */
454
    protected $saveParentProduct = false;
455
456
    /**
457
     * By setting this to TRUE
458
     * the parent (product) will be save when this object will be saved.
459
     *
460
     * @param bool $b
461
     */
462
    public function setSaveParentProduct($b)
463
    {
464
        $this->saveParentProduct = $b;
465
    }
466
467
    /**
468
     * standard SS method
469
     * sets the FullName + FullSiteTreeSort of the variation.
470
     */
471
    public function onBeforeWrite()
472
    {
473
        //$this->currentStageOfRequest = Versioned::current_stage();
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...
474
        //Versioned::set_reading_mode("");
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% 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...
475
        $this->prepareFullFields();
476
        parent::onBeforeWrite();
477
    }
478
479
    /**
480
     * sets the FullName and FullSiteTreeField to the latest values
481
     * This can be useful as you can compare it to the ones saved in the database.
482
     * Returns true if the value is different from the one in the database.
483
     *
484
     * @return bool
485
     */
486
    public function prepareFullFields()
487
    {
488
        $fullName = '';
489
        if ($this->InternalItemID) {
0 ignored issues
show
Documentation introduced by
The property InternalItemID does not exist on object<ProductVariation>. 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...
490
            $fullName .= $this->InternalItemID.': ';
0 ignored issues
show
Documentation introduced by
The property InternalItemID does not exist on object<ProductVariation>. 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...
491
        }
492
        $fullName .= $this->getTitle(false, true);
493
        if ($product = $this->MainParentGroup()) {
494
            $product->prepareFullFields();
495
            $fullName .= ' ('.$product->FullName.')';
496
            $this->FullSiteTreeSort = $product->FullSiteTreeSort.','.$this->Sort;
0 ignored issues
show
Documentation introduced by
The property FullSiteTreeSort does not exist on object<ProductVariation>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
Documentation introduced by
The property Sort does not exist on object<ProductVariation>. 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...
497
        }
498
        $this->FullName = strip_tags($fullName);
0 ignored issues
show
Documentation introduced by
The property FullName does not exist on object<ProductVariation>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
499
        if ($this->EcomConfig()->ProductsHaveWeight) {
500
            if (!$this->Weight) {
0 ignored issues
show
Documentation introduced by
The property Weight does not exist on object<ProductVariation>. 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...
501
                if ($product && $product->Weight) {
502
                    $this->Weight = $product->Weight;
0 ignored issues
show
Documentation introduced by
The property Weight does not exist on object<ProductVariation>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
503
                }
504
            }
505
        }
506
        if (($this->dbObject('FullName') != $this->FullName) || ($this->dbObject('FullSiteTreeSort') != $this->FullSiteTreeSort)) {
0 ignored issues
show
Documentation introduced by
The property FullName does not exist on object<ProductVariation>. 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...
Documentation introduced by
The property FullSiteTreeSort does not exist on object<ProductVariation>. 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...
Unused Code introduced by
This if statement, and the following return statement can be replaced with return $this->dbObject('...this->FullSiteTreeSort;.
Loading history...
507
            return true;
508
        }
509
510
        return false;
511
    }
512
513
    /**
514
     * Standard SS Method.
515
     */
516
    public function onAfterWrite()
0 ignored issues
show
Coding Style introduced by
onAfterWrite uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
517
    {
518
        parent::onAfterWrite();
519
        //clean up data???
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% 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...
520
        //todo: what is this for?
521
        if (isset($_POST['ProductAttributes']) && is_array($_POST['ProductAttributes']) && $this->canEdit()) {
522
            $productAttributesArray = array();
523
            foreach ($_POST['ProductAttributes'] as $key => $value) {
524
                $productAttributesArray[$key] = intval($value);
525
            }
526
            $this->AttributeValues()->setByIDList(array_values($productAttributesArray));
0 ignored issues
show
Bug introduced by
The method AttributeValues() does not exist on ProductVariation. Did you maybe mean AttributeValuesSorted()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
527
        }
528
        unset($_POST['ProductAttributes']);
529
        if ($this->saveParentProduct) {
530
            if ($product = $this->Product()) {
0 ignored issues
show
Bug introduced by
The method Product() does not exist on ProductVariation. Did you maybe mean setSaveParentProduct()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
531
                $product->write();
532
                $product->publish('Stage', 'Live');
533
            }
534
        }
535
    }
536
537
    /**
538
     * Standard SS Method
539
     * Remove links to Attribute Values.
540
     */
541
    public function onBeforeDelete()
542
    {
543
        parent::onBeforeDelete();
544
        $this->AttributeValues()->removeAll();
0 ignored issues
show
Bug introduced by
The method AttributeValues() does not exist on ProductVariation. Did you maybe mean AttributeValuesSorted()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
545
    }
546
547
    /**
548
     * this is used by TableListField to access attribute values.
549
     *
550
     * @return DataObject
551
     */
552
    public function AttributeProxy()
553
    {
554
        $do = new DataObject();
555
        if ($this->AttributeValues()->exists()) {
0 ignored issues
show
Bug introduced by
The method AttributeValues() does not exist on ProductVariation. Did you maybe mean AttributeValuesSorted()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
556
            foreach ($this->AttributeValues() as $value) {
0 ignored issues
show
Bug introduced by
The method AttributeValues() does not exist on ProductVariation. Did you maybe mean AttributeValuesSorted()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
557
                $do->{'Val'.$value->Type()->ID} = $value->Value;
558
            }
559
        }
560
561
        return $do;
562
    }
563
564
    //GROUPS AND SIBLINGS
565
566
    /**
567
     * We use this function to make it more universal.
568
     * For a buyable, a parent could refer to a ProductGroup OR a Product.
569
     *
570
     * @return DataObject | Null
571
     **/
572
    public function Parent()
573
    {
574
        return $this->getParent();
575
    }
576
    public function getParent()
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...
577
    {
578
        return $this->Product();
0 ignored issues
show
Bug introduced by
The method Product() does not exist on ProductVariation. Did you maybe mean setSaveParentProduct()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
579
    }
580
581
    /**
582
     * Returns the direct parent (group) for the product.
583
     **/
584
    public function MainParentGroup()
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...
585
    {
586
        return $this->Product();
0 ignored issues
show
Bug introduced by
The method Product() does not exist on ProductVariation. Did you maybe mean setSaveParentProduct()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
587
    }
588
589
    /**
590
     * Returns Buybales in the same group.
591
     **/
592
    public function Siblings()
593
    {
594
        return self::get()->Filter(array('ProductID' => $this->ProductID));
0 ignored issues
show
Documentation introduced by
The property ProductID does not exist on object<ProductVariation>. 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...
595
    }
596
597
    //IMAGES
598
    /**
599
     * returns a "BestAvailable" image if the current one is not available
600
     * In some cases this is appropriate and in some cases this is not.
601
     * For example, consider the following setup
602
     * - product A with three variations
603
     * - Product A has an image, but the variations have no images
604
     * With this scenario, you want to show ONLY the product image
605
     * on the product page, but if one of the variations is added to the
606
     * cart, then you want to show the product image.
607
     * This can be achieved bu using the BestAvailable image.
608
     *
609
     * @return Image | Null
610
     */
611
    public function BestAvailableImage()
612
    {
613
        $image = $this->Image();
0 ignored issues
show
Documentation Bug introduced by
The method Image does not exist on object<ProductVariation>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
614
        if ($image && $image->exists()) {
615
            return $image;
616
        }
617
        if ($product = $this->Product()) {
0 ignored issues
show
Bug introduced by
The method Product() does not exist on ProductVariation. Did you maybe mean setSaveParentProduct()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
618
            return $product->BestAvailableImage();
619
        }
620
    }
621
622
    /**
623
     * Little hack to show thumbnail in summary fields in modeladmin in CMS.
624
     *
625
     * @return string (HTML = formatted image)
626
     */
627
    public function CMSThumbnail()
628
    {
629
        if ($image = $this->Image()) {
0 ignored issues
show
Documentation Bug introduced by
The method Image does not exist on object<ProductVariation>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
630
            if ($image->exists()) {
631
                return $image->Thumbnail();
632
            }
633
        }
634
635
        return '['._t('product.NOIMAGE', 'no image').']';
636
    }
637
638
    /**
639
     * Returns a link to a default image.
640
     * If a default image is set in the site config then this link is returned
641
     * Otherwise, a standard link is returned.
642
     *
643
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|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...
644
     */
645
    public function DefaultImageLink()
646
    {
647
        $this->EcomConfig()->DefaultImageLink();
648
    }
649
650
    /**
651
     * returns the default image of the product.
652
     *
653
     * @return Image | Null
654
     */
655
    public function DefaultImage()
656
    {
657
        return $this->Product()->DefaultImage();
0 ignored issues
show
Bug introduced by
The method Product() does not exist on ProductVariation. Did you maybe mean setSaveParentProduct()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
658
    }
659
660
    /**
661
     * returns a product image for use in templates
662
     * e.g. $DummyImage.Width();.
663
     *
664
     * @return Product_Image
665
     */
666
    public function DummyImage()
667
    {
668
        return new Product_Image();
669
    }
670
671
    // VERSIONING
672
673
    /**
674
     * Action to return specific version of a product.
675
     * This is really useful for sold products where you want to retrieve the actual version that you sold.
676
     *
677
     * @TODO: this is not correct yet, as the versions of product and productvariation are muddled up!
678
     *
679
     * @param HTTPRequest $request
680
     */
681
    public function viewversion($request)
682
    {
683
        $version = intval($request->param('ID'));
684
        $product = $this->Product();
0 ignored issues
show
Bug introduced by
The method Product() does not exist on ProductVariation. Did you maybe mean setSaveParentProduct()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
685
        if ($product) {
686
            $this->redirect($product->Link('viewversion/'.$product->ID.'/'.$version.'/'));
0 ignored issues
show
Documentation Bug introduced by
The method redirect does not exist on object<ProductVariation>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
687
        } else {
688
            $page = DataObject::get_one(
689
                'ErrorPage',
690
                array('ErrorCode' => '404')
691
            );
692
            if ($page) {
693
                $this->redirect($page->Link());
0 ignored issues
show
Documentation Bug introduced by
The method redirect does not exist on object<ProductVariation>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
694
695
                return;
696
            }
697
        }
698
699
        return array();
700
    }
701
702
    /**
703
     * Action to return specific version of a product variation.
704
     * This can be any product to enable the retrieval of deleted products.
705
     * This is really useful for sold products where you want to retrieve the actual version that you sold.
706
     *
707
     * @param int $id
708
     * @param int $version
709
     *
710
     * @return DataObject | Null
0 ignored issues
show
Documentation introduced by
Should the return type not be DataObject|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...
711
     */
712
    public function getVersionOfBuyable($id = 0, $version = 0)
713
    {
714
        if (!$id) {
715
            $id = $this->ID;
716
        }
717
        if (!$version) {
718
            $version = $this->Version;
0 ignored issues
show
Bug introduced by
The property Version does not seem to exist. Did you mean versioning?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
719
        }
720
721
        return OrderItem::get_version($this->ClassName, $id, $version);
722
    }
723
724
    //ORDER ITEM
725
726
    /**
727
     * returns the order item associated with the buyable.
728
     * ALWAYS returns one, even if there is none in the cart.
729
     * Does not write to database.
730
     *
731
     * @return OrderItem (no kidding)
732
     **/
733
    public function OrderItem()
734
    {
735
        //work out the filter
736
        $filter = array();
737
        $updatedFilters = $this->extend('updateItemFilter', $filter);
738
        if ($updatedFilters !== null && is_array($updatedFilters) && count($updatedFilters)) {
739
            foreach ($updatedFilters as $updatedFilter) {
740
                if (is_array($updatedFilter)) {
741
                    $filter = array_merge($filter, $updatedFilter);
742
                } else {
743
                    $filter[] = $updatedFilter;
744
                }
745
            }
746
        }
747
        //make the item and extend
748
        $item = ShoppingCart::singleton()->findOrMakeItem($this, $filter);
749
        $this->extend('updateDummyItem', $item);
750
751
        return $item;
752
    }
753
754
    /**
755
     * @var string
756
     */
757
    protected $defaultClassNameForOrderItem = 'ProductVariation_OrderItem';
758
759
    /**
760
     * you can overwrite this function in your buyable items (such as Product).
761
     *
762
     * @return string
763
     **/
764
    public function classNameForOrderItem()
765
    {
766
        $className = $this->defaultClassNameForOrderItem;
767
        $updatedClassName = $this->extend('updateClassNameForOrderItem', $className);
768
        if ($updatedClassName != null && is_array($updatedClassName) && count($updatedClassName)) {
769
            $className = $updatedClassName[0];
770
        }
771
772
        return $className;
773
    }
774
775
    /**
776
     * You can set an alternative class name for order item using this method.
777
     *
778
     * @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...
779
     **/
780
    public function setAlternativeClassNameForOrderItem($className)
781
    {
782
        $this->defaultClassNameForOrderItem = $className;
783
    }
784
785
    /**
786
     * When purchasing this buyable, how many decimals can it have?
787
     *
788
     * @return int
789
     */
790
    public function QuantityDecimals()
791
    {
792
        return 0;
793
    }
794
795
    /**
796
     * Number of variations sold.
797
     *
798
     * @TODO: check if we need to use other class names
799
     *
800
     *
801
     * @return int
802
     */
803
    public function HasBeenSold()
804
    {
805
        return $this->getHasBeenSold();
806
    }
807
    public function getHasBeenSold()
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...
808
    {
809
        $dataList = Order::get_datalist_of_orders_with_submit_record($onlySubmittedOrders = true, $includeCancelledOrders = false);
810
        $dataList = $dataList->innerJoin('OrderAttribute', '"OrderAttribute"."OrderID" = "Order"."ID"');
811
        $dataList = $dataList->innerJoin('OrderItem', '"OrderAttribute"."ID" = "OrderItem"."ID"');
812
        $dataList = $dataList->filter(
813
            array(
814
                'BuyableID' => $this->ID,
815
                'buyableClassName' => $this->ClassName
816
            )
817
        );
818
819
        return $dataList->count();
820
    }
821
822
    //LINKS
823
824
    /**
825
     * Takes you to the Product and filters
826
     * for the provided variation.
827
     *
828
     * @param string $action - OPTIONAL
0 ignored issues
show
Documentation introduced by
Should the type for parameter $action not be string|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...
829
     *
830
     * @return string
831
     */
832
    public function Link($action = null)
833
    {
834
        if (!$action) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $action of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
835
            $action = 'filterforvariations/'.$this->ID.'/';
836
        }
837
838
        return $this->Product()->Link($action);
0 ignored issues
show
Bug introduced by
The method Product() does not exist on ProductVariation. Did you maybe mean setSaveParentProduct()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
839
    }
840
841
842
    /**
843
     *
844
     * @todo TEST!!!!
845
     * @return string
846
     */
847
    public function VersionedLink()
848
    {
849
        return Controller::join_links(
850
             Director::baseURL(),
851
             EcommerceConfig::get('ShoppingCart_Controller', 'url_segment'),
852
             'submittedbuyable',
853
             $this->ClassName,
854
             $this->ID,
855
             $this->Version
0 ignored issues
show
Bug introduced by
The property Version does not seem to exist. Did you mean versioning?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
856
         );
857
    }
858
859
    /**
860
     * passing on shopping cart links ...is this necessary?? ...why not just pass the cart?
861
     *
862
     * @return string
863
     */
864
    public function AddLink()
865
    {
866
        return ShoppingCart_Controller::add_item_link($this->ID, $this->ClassName, $this->linkParameters());
867
    }
868
869
    /**
870
     * link use to add (one) to cart.
871
     *
872
     *@return string
873
     */
874
    public function IncrementLink()
875
    {
876
        //we can do this, because by default add link adds one
877
        return ShoppingCart_Controller::add_item_link($this->ID, $this->ClassName, $this->linkParameters());
878
    }
879
880
    /**
881
     * Link used to remove one from cart
882
     * we can do this, because by default remove link removes one.
883
     *
884
     * @return string
885
     */
886
    public function DecrementLink()
887
    {
888
        return ShoppingCart_Controller::remove_item_link($this->ID, $this->ClassName, $this->linkParameters());
889
    }
890
891
    /**
892
     * remove one buyable's orderitem from cart.
893
     *
894
     * @return string (Link)
895
     */
896
    public function RemoveLink()
897
    {
898
        return ShoppingCart_Controller::remove_item_link($this->ID, $this->ClassName, $this->linkParameters());
899
    }
900
901
    /**
902
     * remove all of this buyable's orderitem from cart.
903
     *
904
     * @return string (Link)
905
     */
906
    public function RemoveAllLink()
907
    {
908
        return ShoppingCart_Controller::remove_all_item_link($this->ID, $this->ClassName, $this->linkParameters());
909
    }
910
911
    /**
912
     * remove all of this buyable's orderitem from cart and go through to this buyble to add alternative selection.
913
     *
914
     * @return string (Link)
915
     */
916
    public function RemoveAllAndEditLink()
917
    {
918
        return ShoppingCart_Controller::remove_all_item_and_edit_link($this->ID, $this->ClassName, $this->linkParameters());
919
    }
920
921
    /**
922
     * set new specific new quantity for buyable's orderitem.
923
     *
924
     * @param float
925
     *
926
     * @return string (Link)
927
     */
928
    public function SetSpecificQuantityItemLink($quantity)
929
    {
930
        return ShoppingCart_Controller::set_quantity_item_link($this->ID, $this->ClassName, array_merge($this->linkParameters(), array('quantity' => $quantity)));
931
    }
932
933
    /**
934
     * @return string
935
     */
936
    public function AddToCartAndGoToCheckoutLink()
937
    {
938
        $array = $this->linkParameters();
939
        $array['BackURL'] = urlencode(CheckoutPage::find_link());
940
941
        return ShoppingCart_Controller::add_item_link($this->ID, $this->ClassName, $array);
942
    }
943
944
    /**
945
     * Here you can add additional information to your product
946
     * links such as the AddLink and the RemoveLink.
947
     * One useful parameter you can add is the BackURL link.
948
     *
949
     * Usage would be by means of
950
     * 1. decorating product
951
     * 2. adding a updateLinkParameters method
952
     * 3. adding items to the array.
953
     *
954
     * You can also extend Product and override this method...
955
     *
956
     * @return array
957
     **/
958
    protected function linkParameters()
959
    {
960
        $array = array();
961
        $extendedArray = $this->extend('updateLinkParameters', $array, $type);
962
        if ($extendedArray !== null && is_array($extendedArray) && count($extendedArray)) {
963
            foreach ($extendedArray as $extendedArrayUpdate) {
964
                $array = array_merge($array, $extendedArrayUpdate);
965
            }
966
        }
967
968
        return $array;
969
    }
970
971
    //TEMPLATE STUFF
972
973
    /**
974
     * @return bool
975
     */
976
    public function IsInCart()
977
    {
978
        return ($this->OrderItem() && $this->OrderItem()->Quantity > 0) ? true : false;
979
    }
980
981
    /**
982
     * @return EcomQuantityField
983
     */
984
    public function EcomQuantityField()
985
    {
986
        $obj = new EcomQuantityField($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<ProductVariation>, but the function expects a object<buyable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
987
988
        return $obj;
989
    }
990
991
    /**
992
     * returns the instance of EcommerceConfigAjax for use in templates.
993
     * In templates, it is used like this:
994
     * $EcommerceConfigAjax.TableID.
995
     *
996
     * @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...
997
     **/
998
    public function AJAXDefinitions()
999
    {
1000
        return EcommerceConfigAjax::get_one($this);
1001
    }
1002
1003
    /**
1004
     * @return EcommerceDBConfig
1005
     **/
1006
    public function EcomConfig()
1007
    {
1008
        return EcommerceDBConfig::current_ecommerce_db_config();
1009
    }
1010
1011
    /**
1012
     * Is it a variation?
1013
     *
1014
     * @return bool
1015
     */
1016
    public function IsProductVariation()
1017
    {
1018
        return true;
1019
    }
1020
1021
    /**
1022
     * returns the actual price worked out after discounts, currency conversions, etc...
1023
     *
1024
     * @casted
1025
     *
1026
     * @return float
1027
     */
1028
    public function CalculatedPrice()
1029
    {
1030
        return $this->getCalculatedPrice();
1031
    }
1032
    public function getCalculatedPrice()
1033
    {
1034
        $price = $this->Price;
0 ignored issues
show
Documentation introduced by
The property Price does not exist on object<ProductVariation>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
1035
        $updatedPrice = $this->extend('updateCalculatedPrice', $price);
1036
        if ($updatedPrice !== null && is_array($updatedPrice) && count($updatedPrice)) {
1037
            $price = $updatedPrice[0];
1038
        }
1039
1040
        return $price;
1041
    }
1042
1043
    /**
1044
     * How do we display the price?
1045
     *
1046
     * @return Money
1047
     */
1048
    public function CalculatedPriceAsMoney()
1049
    {
1050
        return $this->getCalculatedPriceAsMoney();
1051
    }
1052
    public function getCalculatedPriceAsMoney()
1053
    {
1054
        return EcommerceCurrency::get_money_object_from_order_currency($this->CalculatedPrice());
1055
    }
1056
1057
    //CRUD SETTINGS
1058
1059
    /**
1060
     * Is the product for sale?
1061
     *
1062
     * @return bool
1063
     */
1064
    public function canPurchase(Member $member = null, $checkPrice = true)
1065
    {
1066
        $config = $this->EcomConfig();
1067
        if ($config->ShopClosed) {
1068
            return false;
1069
        }
1070
        $allowpurchase = $this->AllowPurchase;
0 ignored issues
show
Documentation introduced by
The property AllowPurchase does not exist on object<ProductVariation>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
1071
        if (!$allowpurchase) {
1072
            return false;
1073
        }
1074
        if ($product = $this->Product()) {
0 ignored issues
show
Bug introduced by
The method Product() does not exist on ProductVariation. Did you maybe mean setSaveParentProduct()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1075
            $allowpurchase = $product->canPurchase($member);
1076
            if (!$allowpurchase) {
1077
                return false;
1078
            }
1079
        }
1080
        $price = $this->getCalculatedPrice();
1081
        if ($price == 0 && !$config->AllowFreeProductPurchase) {
1082
            return false;
1083
        }
1084
        $extended = $this->extendedCan('canPurchase', $member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by parameter $member on line 1064 can be null; however, DataObject::extendedCan() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
1085
        if ($extended !== null) {
1086
            $allowpurchase = $extended;
1087
        }
1088
1089
        return $allowpurchase;
1090
    }
1091
1092
    /**
1093
     * standard SS Method
1094
     * we explicitely set this to give access in the API.
1095
     *
1096
     * @return bool
1097
     */
1098
    public function canView($member = null)
1099
    {
1100
        if ($this->ProductID && $this->Product()->exists()) {
0 ignored issues
show
Documentation introduced by
The property ProductID does not exist on object<ProductVariation>. 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...
Bug introduced by
The method Product() does not exist on ProductVariation. Did you maybe mean setSaveParentProduct()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1101
            return $this->Product()->canEdit($member);
0 ignored issues
show
Bug introduced by
The method Product() does not exist on ProductVariation. Did you maybe mean setSaveParentProduct()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1102
        }
1103
1104
        return $this->canEdit($member);
1105
    }
1106
1107
    /**
1108
     * Shop Admins can edit.
1109
     *
1110
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|string|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...
1111
     */
1112
    public function canEdit($member = null)
1113
    {
1114
        if (!$member) {
1115
            $member = Member::currentUser();
1116
        }
1117
        if ($member && $member->IsShopAdmin()) {
1118
            return true;
1119
        }
1120
1121
        return parent::canEdit($member);
1122
    }
1123
1124
    /**
1125
     * Standard SS method.
1126
     *
1127
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|string|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...
1128
     */
1129 View Code Duplication
    public function canDelete($member = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1130
    {
1131
        $extended = $this->extendedCan(__FUNCTION__, $member);
1132
        if ($extended !== null) {
1133
            return $extended;
1134
        }
1135
1136
        return $this->canEdit($member);
1137
    }
1138
1139
    /**
1140
     * Standard SS method
1141
     * //check if it is in a current cart?
1142
     *
1143
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|string|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...
1144
     */
1145 View Code Duplication
    public function canDeleteFromLive($member = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1146
    {
1147
        $extended = $this->extendedCan(__FUNCTION__, $member);
1148
        if ($extended !== null) {
1149
            return $extended;
1150
        }
1151
1152
        return $this->canEdit($member);
1153
    }
1154
1155
    /**
1156
     * Standard SS method.
1157
     *
1158
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|string|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...
1159
     */
1160 View Code Duplication
    public function canCreate($member = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1161
    {
1162
        $extended = $this->extendedCan(__FUNCTION__, $member);
1163
        if ($extended !== null) {
1164
            return $extended;
1165
        }
1166
1167
        return $this->canEdit($member);
1168
    }
1169
1170
    /**
1171
     * finds similar ("siblings") variations where one
1172
     * attribute value is NOT the same.
1173
     *
1174
     * @return DataList
1175
     */
1176
    public function MostLikeMe()
1177
    {
1178
        $idArray = array();
1179
        foreach ($this->AttributeValues() as $excludeValue) {
0 ignored issues
show
Bug introduced by
The method AttributeValues() does not exist on ProductVariation. Did you maybe mean AttributeValuesSorted()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1180
            unset($getAnyArray);
1181
            $getAnyArray = array();
1182
            foreach ($this->AttributeValues() as $innerValue) {
0 ignored issues
show
Bug introduced by
The method AttributeValues() does not exist on ProductVariation. Did you maybe mean AttributeValuesSorted()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1183
                if ($excludeValue->ID != $innerValue->ID) {
1184
                    $getAnyArray[$innerValue->ID] = $innerValue->ID;
1185
                }
1186
                //find a product variation that has the getAnyArray Values
1187
                $items = ProductVariation::get()
1188
                    ->innerJoin(
1189
                        'ProductVariation_AttributeValues',
1190
                        '"ProductVariation"."ID" = "ProductVariationID"'
1191
                    )
1192
                    ->filter(
1193
                        array(
1194
                            'ProductAttributeValueID' => $getAnyArray,
1195
                            'ProductID' => $this->ProductID
0 ignored issues
show
Documentation introduced by
The property ProductID does not exist on object<ProductVariation>. 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...
1196
                        )
1197
                    )
1198
                    ->exclude(array('ID' => $this->ID));
1199
                if ($items->count()) {
1200
                    $idArray = array_merge($idArray, $items->column('ID'));
1201
                }
1202
            }
1203
        }
1204
1205
        return ProductVariation::get()->filter(array('ID' => $idArray));
1206
    }
1207
}
1208