Variation::getCMSFields()   B
last analyzed

Complexity

Conditions 5
Paths 2

Size

Total Lines 101
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 64
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 101
rs 8.4743

How to fix   Long Method   

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
namespace SilverShop\Model\Variation;
4
5
use SilverShop\Cart\ShoppingCart;
6
use SilverShop\Model\Buyable;
7
use SilverShop\Model\Order;
8
use SilverShop\Page\Product;
9
use SilverStripe\AssetAdmin\Forms\UploadField;
10
use SilverStripe\Assets\Image;
11
use SilverStripe\Forms\FieldList;
12
use SilverStripe\Forms\LiteralField;
13
use SilverStripe\Forms\TextField;
14
use SilverStripe\ORM\ArrayList;
15
use SilverStripe\ORM\DataObject;
16
use SilverStripe\ORM\FieldType\DBCurrency;
17
use SilverStripe\ORM\FieldType\DBDecimal;
18
use SilverStripe\ORM\ManyManyList;
19
use SilverStripe\Versioned\Versioned;
20
21
/**
22
 * Product Variation
23
 *
24
 * Provides a means for specifying many variations on a product.
25
 * Used in combination with ProductAttributes, such as color, size.
26
 * A variation will specify one particular combination, such as red, and large.
27
 *
28
 * @property string $InternalItemID
29
 * @property DBCurrency $Price
30
 * @property DBDecimal $Weight
31
 * @property DBDecimal $Height
32
 * @property DBDecimal $Width
33
 * @property DBDecimal $Depth
34
 * @property int $ProductID
35
 * @property int $ImageID
36
 * @method   Product Product()
37
 * @method   Image Image()
38
 * @method   AttributeValue[]|ManyManyList AttributeValues()
39
 */
40
class Variation extends DataObject implements Buyable
41
{
42
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
43
        'Sort' => 'Int',
44
        'InternalItemID' => 'Varchar(30)',
45
        'Price' => 'Currency(19,4)',
46
47
        //physical properties
48
        //TODO: Move to an extension
49
        'Weight' => 'Decimal(12,5)',
50
        'Height' => 'Decimal(12,5)',
51
        'Width' => 'Decimal(12,5)',
52
        'Depth' => 'Decimal(12,5)'
53
    ];
54
55
    private static $has_one = [
0 ignored issues
show
introduced by
The private property $has_one is not used, and could be removed.
Loading history...
56
        'Product' => Product::class,
57
        'Image' => Image::class
58
    ];
59
60
    private static $owns = [
0 ignored issues
show
introduced by
The private property $owns is not used, and could be removed.
Loading history...
61
        'Image'
62
    ];
63
64
    private static $many_many = [
0 ignored issues
show
introduced by
The private property $many_many is not used, and could be removed.
Loading history...
65
        'AttributeValues' => AttributeValue::class
66
    ];
67
68
    private static $casting = [
0 ignored issues
show
introduced by
The private property $casting is not used, and could be removed.
Loading history...
69
        'Title' => 'Text',
70
        'Price' => 'Currency'
71
    ];
72
73
    private static $versioning = [
0 ignored issues
show
introduced by
The private property $versioning is not used, and could be removed.
Loading history...
74
        'Live'
75
    ];
76
77
    private static $extensions = [
0 ignored issues
show
introduced by
The private property $extensions is not used, and could be removed.
Loading history...
78
        Versioned::class . '.versioned'
79
    ];
80
81
    private static $summary_fields = [
0 ignored issues
show
introduced by
The private property $summary_fields is not used, and could be removed.
Loading history...
82
        'InternalItemID' => 'Product Code',
83
        //'Product.Title' => 'Product',
84
        'Title' => 'Variation',
85
        'Price' => 'Price'
86
    ];
87
88
    private static $searchable_fields = [
0 ignored issues
show
introduced by
The private property $searchable_fields is not used, and could be removed.
Loading history...
89
        'Product.Title',
90
        'InternalItemID'
91
    ];
92
93
    private static $indexes = [
0 ignored issues
show
introduced by
The private property $indexes is not used, and could be removed.
Loading history...
94
        'InternalItemID' => true,
95
        'LastEdited' => true
96
    ];
97
98
    private static $singular_name = 'Variation';
0 ignored issues
show
introduced by
The private property $singular_name is not used, and could be removed.
Loading history...
99
100
    private static $plural_name = 'Variations';
0 ignored issues
show
introduced by
The private property $plural_name is not used, and could be removed.
Loading history...
101
102
    private static $default_sort = 'InternalItemID';
0 ignored issues
show
introduced by
The private property $default_sort is not used, and could be removed.
Loading history...
103
104
    private static $order_item = OrderItem::class;
105
106
    private static $table_name = 'SilverShop_Variation';
0 ignored issues
show
introduced by
The private property $table_name is not used, and could be removed.
Loading history...
107
108
    /**
109
     * @config
110
     * @var bool
111
     */
112
    private static $title_has_label = true;
113
114
    /**
115
     * @config
116
     * @var string
117
     */
118
    private static $title_separator = ':';
119
120
    /**
121
     * @config
122
     * @var string
123
     */
124
    private static $title_glue = ', ';
125
126
    public function getCMSFields()
127
    {
128
        $fields = FieldList::create(
129
            TextField::create('InternalItemID', _t('SilverShop\Page\Product.Code', 'Product Code')),
130
            TextField::create('Price', _t('SilverShop\Page\Product.db_BasePrice', 'Price'))
131
        );
132
        //add attributes dropdowns
133
        $attributes = $this->Product()->VariationAttributeTypes();
134
        if ($attributes->exists()) {
135
            foreach ($attributes as $attribute) {
136
                if ($field = $attribute->getDropDownField()) {
137
                    if ($value = $this->AttributeValues()->find('TypeID', $attribute->ID)) {
138
                        $field->setValue($value->ID);
139
                    }
140
                    $fields->push($field);
141
                } else {
142
                    $fields->push(
143
                        LiteralField::create(
144
                            'novalues' . $attribute->Name,
145
                            '<p class="message warning">' .
146
                            _t(
147
                                __CLASS__ . '.NoAttributeValuesMessage',
148
                                '{attribute} has no values to choose from. You can create them in the "Products" &#62; "Product Attribute Type" section of the CMS.',
149
                                'Warning that will be shown if an attribute doesn\'t have any values',
150
                                ['attribute' => $attribute->Name]
151
                            ) .
152
                            '</p>'
153
                        )
154
                    );
155
                }
156
                //TODO: allow setting custom values here, rather than visiting the products section
157
            }
158
        } else {
159
            $fields->push(
160
                LiteralField::create(
161
                    'savefirst',
162
                    '<p class="message warning">' .
163
                    _t(
164
                        __CLASS__ . '.MustSaveFirstMessage',
165
                        'You can choose variation attributes after saving for the first time, if they exist.'
166
                    ) .
167
                    '</p>'
168
                )
169
            );
170
        }
171
        $fields->push(
172
            UploadField::create('Image', _t('SilverShop\Page\Product.Image', 'Product Image'))
173
        );
174
175
        //physical measurement units
176
        $fieldSubstitutes = [
177
            'LengthUnit' => Product::config()->length_unit
178
        ];
179
180
        //physical measurements
181
        $fields->push(
182
            TextField::create(
183
                'Weight',
184
                _t(
185
                    'SilverShop\Page\Product.WeightWithUnit',
186
                    'Weight ({WeightUnit})',
187
                    '',
188
                    [
189
                        'WeightUnit' => Product::config()->weight_unit
190
                    ]
191
                ),
192
                '',
193
                12
194
            )
195
        );
196
197
        $fields->push(
198
            TextField::create(
199
                'Height',
200
                _t('SilverShop\Page\Product.HeightWithUnit', 'Height ({LengthUnit})', '', $fieldSubstitutes),
201
                '',
202
                12
203
            )
204
        );
205
206
        $fields->push(
207
            TextField::create(
208
                'Width',
209
                _t('SilverShop\Page\Product.WidthWithUnit', 'Width ({LengthUnit})', '', $fieldSubstitutes),
210
                '',
211
                12
212
            )
213
        );
214
215
        $fields->push(
216
            TextField::create(
217
                'Depth',
218
                _t('SilverShop\Page\Product.DepthWithUnit', 'Depth ({LengthUnit})', '', $fieldSubstitutes),
219
                '',
220
                12
221
            )
222
        );
223
224
        $this->extend('updateCMSFields', $fields);
225
226
        return $fields;
227
    }
228
229
    /**
230
     * Save selected attributes - somewhat of a hack.
231
     */
232
    public function onBeforeWrite()
233
    {
234
        parent::onBeforeWrite();
235
236
        if (isset($_POST['ProductAttributes']) && is_array($_POST['ProductAttributes'])) {
237
            $this->AttributeValues()->setByIDList(array_values($_POST['ProductAttributes']));
238
        }
239
240
        $img = $this->Image();
241
242
        if ($img && $img->exists()) {
243
            $img->doPublish();
244
        }
245
    }
246
247
    public function getTitle()
248
    {
249
        $values = $this->AttributeValues();
250
        if ($values->exists()) {
251
            $labelvalues = [];
252
            foreach ($values as $value) {
253
                if (self::config()->title_has_label) {
254
                    $labelvalues[] = $value->Type()->Label . self::config()->title_separator . $value->Value;
255
                } else {
256
                    $labelvalues[] = $value->Value;
257
                }
258
            }
259
260
            $title = implode(self::config()->title_glue, $labelvalues);
261
        }
262
        $this->extend('updateTitle', $title);
263
264
        return $title;
265
    }
266
267
    public function getCategoryIDs()
268
    {
269
        return $this->Product() ? $this->Product()->getCategoryIDs() : [];
270
    }
271
272
    public function getCategories()
273
    {
274
        return $this->Product() ? $this->Product()->getCategories() : ArrayList::create();
275
    }
276
277
    public function canPurchase($member = null, $quantity = 1)
278
    {
279
        $allowpurchase = false;
280
        if ($product = $this->Product()) {
281
            $allowpurchase =
282
                ($this->sellingPrice() > 0 || Product::config()->allow_zero_price) && $product->AllowPurchase;
283
        }
284
285
        $permissions = $this->extend('canPurchase', $member, $quantity);
286
        $permissions[] = $allowpurchase;
287
        return min($permissions);
288
    }
289
290
    /*
291
     * Returns if the product variation is already in the shopping cart.
292
     * @return boolean
293
     */
294
    public function IsInCart()
295
    {
296
        return $this->Item() && $this->Item()->Quantity > 0;
297
    }
298
299
    /*
300
     * Returns the order item which contains the product variation
301
     * @return  OrderItem
302
     */
303
    public function Item()
304
    {
305
        $filter = [];
306
        $this->extend('updateItemFilter', $filter);
307
        $item = ShoppingCart::singleton()->get($this, $filter);
308
        if (!$item) {
309
            //return dummy item so that we can still make use of Item
310
            $item = $this->createItem(0);
311
        }
312
        $this->extend('updateDummyItem', $item);
313
        return $item;
314
    }
315
316
    public function addLink()
317
    {
318
        return $this->Item()->addLink($this->ProductID, $this->ID);
0 ignored issues
show
Unused Code introduced by
The call to SilverShop\Model\OrderItem::addLink() has too many arguments starting with $this->ProductID. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

318
        return $this->Item()->/** @scrutinizer ignore-call */ addLink($this->ProductID, $this->ID);

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. Please note the @ignore annotation hint above.

Loading history...
319
    }
320
321
    /**
322
     * Returns a link to the parent product of this variation (variations don't have their own pages)
323
     *
324
     * @param $action string
325
     *
326
     * @return string
327
     */
328
    public function Link($action = null)
329
    {
330
        return ($this->ProductID) ? $this->Product()->Link($action) : false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->ProductID ...->Link($action) : false could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
331
    }
332
333
    public function createItem($quantity = 1, $filter = [])
334
    {
335
        $orderitem = self::config()->order_item;
336
        $item = new $orderitem();
337
        $item->ProductID = $this->ProductID;
338
        $item->ProductVariationID = $this->ID;
339
        //$item->ProductVariationVersion = $this->Version;
340
        if ($filter) {
341
            //TODO: make this a bit safer, perhaps intersect with allowed fields
342
            $item->update($filter);
343
        }
344
        $item->Quantity = $quantity;
345
        return $item;
346
    }
347
348
    public function sellingPrice()
349
    {
350
        $price = $this->Price;
351
        $this->extend('updateSellingPrice', $price);
352
353
        //prevent negative values
354
        $price = $price < 0 ? 0 : $price;
355
356
        // NOTE: Ideally, this would be dependent on the locale but as of
357
        // now the Silverstripe Currency field type has 2 hardcoded all over
358
        // the place. In the mean time there is an issue where the displayed
359
        // unit price can not exactly equal the multiplied price on an order
360
        // (i.e. if the calculated price is 3.145 it will display as 3.15.
361
        // so if I put 10 of them in my cart I will expect the price to be
362
        // 31.50 not 31.45).
363
        return round($price, Order::config()->rounding_precision);
0 ignored issues
show
Bug Best Practice introduced by
The expression return round($price, Sil...()->rounding_precision) returns the type double which is incompatible with the return type mandated by SilverShop\Model\Buyable::sellingPrice() of SilverShop\ORM\FieldType\ShopCurrency.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
364
    }
365
}
366