Passed
Push — master ( 6c2061...b6dc4c )
by Will
05:08
created

src/Model/Variation/Variation.php (1 issue)

Labels
Severity
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;
0 ignored issues
show
The type SilverStripe\AssetAdmin\Forms\UploadField was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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 = [
43
        'InternalItemID' => 'Varchar(30)',
44
        'Price' => 'Currency(19,4)',
45
46
        //physical properties
47
        //TODO: Move to an extension
48
        'Weight' => 'Decimal(12,5)',
49
        'Height' => 'Decimal(12,5)',
50
        'Width' => 'Decimal(12,5)',
51
        'Depth' => 'Decimal(12,5)'
52
    ];
53
54
    private static $has_one = [
55
        'Product' => Product::class,
56
        'Image' => Image::class
57
    ];
58
59
    private static $owns = [
60
        'Image'
61
    ];
62
63
    private static $many_many = [
64
        'AttributeValues' => AttributeValue::class
65
    ];
66
67
    private static $casting = [
68
        'Title' => 'Text',
69
        'Price' => 'Currency'
70
    ];
71
72
    private static $versioning = [
73
        'Live'
74
    ];
75
76
    private static $extensions = [
77
        Versioned::class . '.versioned'
78
    ];
79
80
    private static $summary_fields = [
81
        'InternalItemID' => 'Product Code',
82
        //'Product.Title' => 'Product',
83
        'Title' => 'Variation',
84
        'Price' => 'Price'
85
    ];
86
87
    private static $searchable_fields = [
88
        'Product.Title',
89
        'InternalItemID'
90
    ];
91
92
    private static $indexes = [
93
        'InternalItemID' => true,
94
        'LastEdited' => true
95
    ];
96
97
    private static $singular_name = 'Variation';
98
99
    private static $plural_name = 'Variations';
100
101
    private static $default_sort = 'InternalItemID';
102
103
    private static $order_item = OrderItem::class;
104
105
    private static $table_name = 'SilverShop_Variation';
106
107
    /**
108
     * @config
109
     * @var bool
110
     */
111
    private static $title_has_label = true;
112
113
    /**
114
     * @config
115
     * @var string
116
     */
117
    private static $title_separator = ':';
118
119
    /**
120
     * @config
121
     * @var string
122
     */
123
    private static $title_glue = ', ';
124
125
    public function getCMSFields()
126
    {
127
        $fields = FieldList::create(
128
            TextField::create('InternalItemID', _t('SilverShop\Page\Product.Code', 'Product Code')),
129
            TextField::create('Price', _t('SilverShop\Page\Product.db_BasePrice', 'Price'))
130
        );
131
        //add attributes dropdowns
132
        $attributes = $this->Product()->VariationAttributeTypes();
133
        if ($attributes->exists()) {
134
            foreach ($attributes as $attribute) {
135
                if ($field = $attribute->getDropDownField()) {
136
                    if ($value = $this->AttributeValues()->find('TypeID', $attribute->ID)) {
137
                        $field->setValue($value->ID);
138
                    }
139
                    $fields->push($field);
140
                } else {
141
                    $fields->push(
142
                        LiteralField::create(
143
                            'novalues' . $attribute->Name,
144
                            '<p class="message warning">' .
145
                            _t(
146
                                __CLASS__ . '.NoAttributeValuesMessage',
147
                                '{attribute} has no values to choose from. You can create them in the "Products" &#62; "Product Attribute Type" section of the CMS.',
148
                                'Warning that will be shown if an attribute doesn\'t have any values',
149
                                ['attribute' => $attribute->Name]
150
                            ) .
151
                            '</p>'
152
                        )
153
                    );
154
                }
155
                //TODO: allow setting custom values here, rather than visiting the products section
156
            }
157
        } else {
158
            $fields->push(
159
                LiteralField::create(
160
                    'savefirst',
161
                    '<p class="message warning">' .
162
                    _t(
163
                        __CLASS__ . '.MustSaveFirstMessage',
164
                        'You can choose variation attributes after saving for the first time, if they exist.'
165
                    ) .
166
                    '</p>'
167
                )
168
            );
169
        }
170
        $fields->push(
171
            UploadField::create('Image', _t('SilverShop\Page\Product.Image', 'Product Image'))
172
        );
173
174
        //physical measurement units
175
        $fieldSubstitutes = [
176
            'LengthUnit' => Product::config()->length_unit
177
        ];
178
179
        //physical measurements
180
        $fields->push(
181
            TextField::create(
182
                'Weight',
183
                _t(
184
                    'SilverShop\Page\Product.WeightWithUnit',
185
                    'Weight ({WeightUnit})',
186
                    '',
187
                    [
188
                        'WeightUnit' => Product::config()->weight_unit
189
                    ]
190
                ),
191
                '',
192
                12
193
            )
194
        );
195
196
        $fields->push(
197
            TextField::create(
198
                'Height',
199
                _t('SilverShop\Page\Product.HeightWithUnit', 'Height ({LengthUnit})', '', $fieldSubstitutes),
200
                '',
201
                12
202
            )
203
        );
204
205
        $fields->push(
206
            TextField::create(
207
                'Width',
208
                _t('SilverShop\Page\Product.WidthWithUnit', 'Width ({LengthUnit})', '', $fieldSubstitutes),
209
                '',
210
                12
211
            )
212
        );
213
214
        $fields->push(
215
            TextField::create(
216
                'Depth',
217
                _t('SilverShop\Page\Product.DepthWithUnit', 'Depth ({LengthUnit})', '', $fieldSubstitutes),
218
                '',
219
                12
220
            )
221
        );
222
223
        $this->extend('updateCMSFields', $fields);
224
225
        return $fields;
226
    }
227
228
    /**
229
     * Save selected attributes - somewhat of a hack.
230
     */
231
    public function onBeforeWrite()
232
    {
233
        parent::onBeforeWrite();
234
235
        if (isset($_POST['ProductAttributes']) && is_array($_POST['ProductAttributes'])) {
236
            $this->AttributeValues()->setByIDList(array_values($_POST['ProductAttributes']));
237
        }
238
239
        $img = $this->Image();
240
241
        if ($img && $img->exists()) {
242
            $img->doPublish();
243
        }
244
    }
245
246
    public function getTitle()
247
    {
248
        $values = $this->AttributeValues();
249
        if ($values->exists()) {
250
            $labelvalues = array();
251
            foreach ($values as $value) {
252
                if (self::config()->title_has_label) {
253
                    $labelvalues[] = $value->Type()->Label . self::config()->title_separator . $value->Value;
254
                } else {
255
                    $labelvalues[] = $value->Value;
256
                }
257
            }
258
259
            $title = implode(self::config()->title_glue, $labelvalues);
260
        }
261
        $this->extend('updateTitle', $title);
262
263
        return $title;
264
    }
265
266
    public function getCategoryIDs()
267
    {
268
        return $this->Product() ? $this->Product()->getCategoryIDs() : array();
269
    }
270
271
    public function getCategories()
272
    {
273
        return $this->Product() ? $this->Product()->getCategories() : ArrayList::create();
274
    }
275
276
    public function canPurchase($member = null, $quantity = 1)
277
    {
278
        $allowpurchase = false;
279
        if ($product = $this->Product()) {
280
            $allowpurchase =
281
                ($this->sellingPrice() > 0 || Product::config()->allow_zero_price) && $product->AllowPurchase;
282
        }
283
284
        $permissions = $this->extend('canPurchase', $member, $quantity);
285
        $permissions[] = $allowpurchase;
286
        return min($permissions);
287
    }
288
289
    /*
290
     * Returns if the product variation is already in the shopping cart.
291
     * @return boolean
292
     */
293
    public function IsInCart()
294
    {
295
        return $this->Item() && $this->Item()->Quantity > 0;
296
    }
297
298
    /*
299
     * Returns the order item which contains the product variation
300
     * @return  OrderItem
301
     */
302
    public function Item()
303
    {
304
        $filter = array();
305
        $this->extend('updateItemFilter', $filter);
306
        $item = ShoppingCart::singleton()->get($this, $filter);
307
        if (!$item) {
308
            //return dummy item so that we can still make use of Item
309
            $item = $this->createItem(0);
310
        }
311
        $this->extend('updateDummyItem', $item);
312
        return $item;
313
    }
314
315
    public function addLink()
316
    {
317
        return $this->Item()->addLink($this->ProductID, $this->ID);
318
    }
319
320
    public function createItem($quantity = 1, $filter = array())
321
    {
322
        $orderitem = self::config()->order_item;
323
        $item = new $orderitem();
324
        $item->ProductID = $this->ProductID;
325
        $item->ProductVariationID = $this->ID;
326
        //$item->ProductVariationVersion = $this->Version;
327
        if ($filter) {
328
            //TODO: make this a bit safer, perhaps intersect with allowed fields
329
            $item->update($filter);
330
        }
331
        $item->Quantity = $quantity;
332
        return $item;
333
    }
334
335
    public function sellingPrice()
336
    {
337
        $price = $this->Price;
338
        $this->extend('updateSellingPrice', $price);
339
340
        //prevent negative values
341
        $price = $price < 0 ? 0 : $price;
342
343
        // NOTE: Ideally, this would be dependent on the locale but as of
344
        // now the Silverstripe Currency field type has 2 hardcoded all over
345
        // the place. In the mean time there is an issue where the displayed
346
        // unit price can not exactly equal the multiplied price on an order
347
        // (i.e. if the calculated price is 3.145 it will display as 3.15.
348
        // so if I put 10 of them in my cart I will expect the price to be
349
        // 31.50 not 31.45).
350
        return round($price, Order::config()->rounding_precision);
351
    }
352
}
353