Completed
Push — master ( 71a588...6672d2 )
by Nic
09:48
created

Purchasable   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 385
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 176
c 2
b 0
f 0
dl 0
loc 385
rs 10
wmc 25

14 Methods

Rating   Name   Duplication   Size   Complexity  
B updateCMSFields() 0 105 2
A canEdit() 0 7 2
A updateFieldLabels() 0 11 1
A getCMSValidator() 0 6 1
A isProduct() 0 3 1
A canArchive() 0 7 2
A isAvailable() 0 17 5
A canUnpublish() 0 7 2
A onBeforeWrite() 0 5 1
A canCreate() 0 7 2
A setHasVariations() 0 4 1
A canDelete() 0 7 2
A getHasVariations() 0 6 2
A providePermissions() 0 17 1
1
<?php
2
3
namespace Dynamic\Foxy\Extension;
4
5
use Dynamic\Foxy\Model\FoxyCategory;
6
use Dynamic\Foxy\Model\ProductOption;
7
use Dynamic\Foxy\Model\Variation;
8
use Dynamic\Foxy\Model\VariationType;
9
use micschk\GroupableGridfield\GridFieldGroupable;
10
use SilverStripe\Forms\CheckboxField;
11
use SilverStripe\Forms\CurrencyField;
12
use SilverStripe\Forms\DropdownField;
13
use SilverStripe\Forms\FieldList;
14
use SilverStripe\Forms\GridField\GridField;
15
use SilverStripe\Forms\GridField\GridFieldAddExistingAutocompleter;
16
use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor;
17
use SilverStripe\Forms\GridField\GridFieldPageCount;
18
use SilverStripe\Forms\GridField\GridFieldPaginator;
19
use SilverStripe\Forms\GridField\GridFieldSortableHeader;
20
use SilverStripe\Forms\NumericField;
21
use SilverStripe\Forms\RequiredFields;
22
use SilverStripe\Forms\TextField;
23
use SilverStripe\ORM\DataExtension;
24
use SilverStripe\ORM\DataObject;
25
use SilverStripe\ORM\HasManyList;
26
use SilverStripe\ORM\ManyManyList;
27
use SilverStripe\ORM\ValidationResult;
28
use SilverStripe\Security\Permission;
29
use SilverStripe\Security\PermissionProvider;
30
use SilverStripe\Security\Security;
31
use Symbiote\GridFieldExtensions\GridFieldAddExistingSearchButton;
32
use Symbiote\GridFieldExtensions\GridFieldOrderableRows;
33
use Symbiote\GridFieldExtensions\GridFieldTitleHeader;
34
35
/**
36
 * Class Purchasable
37
 * @package Dynamic\Foxy\Extension
38
 *
39
 * @property double Price
40
 * @property string Code
41
 * @property string ReceiptTitle
42
 * @property bool Available
43
 * @property int $QuantityMax
44
 *
45
 * @property int FoxyCategoryID
46
 * @method FoxyCategory FoxyCategory()
47
 *
48
 * @method HasManyList Variations()
49
 * @method ManyManyList Options()
50
 *
51
 * @property-read DataObject|Purchasable $owner
52
 */
53
class Purchasable extends DataExtension implements PermissionProvider
54
{
55
    /**
56
     * @var
57
     */
58
    private $has_variations;
59
60
    /**
61
     * @var array
62
     */
63
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
64
        'Price' => 'Currency',
65
        'Code' => 'Varchar(100)',
66
        'ReceiptTitle' => 'HTMLVarchar(255)',
67
        'Available' => 'Boolean',
68
        'QuantityMax' => 'Int',
69
    ];
70
71
    /**
72
     * @var array
73
     */
74
    private static $has_one = [
0 ignored issues
show
introduced by
The private property $has_one is not used, and could be removed.
Loading history...
75
        'FoxyCategory' => FoxyCategory::class,
76
    ];
77
78
    /**
79
     * @var string[]
80
     */
81
    private static $has_many = [
0 ignored issues
show
introduced by
The private property $has_many is not used, and could be removed.
Loading history...
82
        'Variations' => Variation::class,
83
    ];
84
85
    /**
86
     * @var array
87
     */
88
    private static $many_many = [
0 ignored issues
show
introduced by
The private property $many_many is not used, and could be removed.
Loading history...
89
        'Options' => ProductOption::class,
90
    ];
91
92
    /**
93
     * @var array
94
     */
95
    private static $many_many_extraFields = [
0 ignored issues
show
introduced by
The private property $many_many_extraFields is not used, and could be removed.
Loading history...
96
        'Options' => [
97
            'WeightModifier' => 'Decimal(9,3)',
98
            'CodeModifier' => 'Text',
99
            'PriceModifier' => 'Currency',
100
            'WeightModifierAction' => "Enum('Add,Subtract,Set', null)",
101
            'CodeModifierAction' => "Enum('Add,Subtract,Set', null)",
102
            'PriceModifierAction' => "Enum('Add,Subtract,Set', null)",
103
            'Available' => 'Boolean',
104
            'Type' => 'Int',
105
            'OptionModifierKey' => 'Varchar(255)',
106
            'SortOrder' => 'Int',
107
        ],
108
        'OptionTypes' => [
109
            'SortOrder' => 'Int',
110
        ],
111
    ];
112
113
    /**
114
     * @var array
115
     */
116
    private static $indexes = [
0 ignored issues
show
introduced by
The private property $indexes is not used, and could be removed.
Loading history...
117
        'Code' => [
118
            'type' => 'unique',
119
            'columns' => ['Code'],
120
        ],
121
    ];
122
123
    /**
124
     * @var array
125
     */
126
    private static $defaults = [
0 ignored issues
show
introduced by
The private property $defaults is not used, and could be removed.
Loading history...
127
        'ShowInMenus' => false,
128
        'Available' => true,
129
    ];
130
131
    /**
132
     * @var array
133
     */
134
    private static $summary_fields = [
0 ignored issues
show
introduced by
The private property $summary_fields is not used, and could be removed.
Loading history...
135
        'Image.CMSThumbnail',
136
        'Title',
137
        'Code',
138
        'Price.Nice',
139
    ];
140
141
    /**
142
     * @var array
143
     */
144
    private static $searchable_fields = [
0 ignored issues
show
introduced by
The private property $searchable_fields is not used, and could be removed.
Loading history...
145
        'Title',
146
        'Code',
147
        'Available',
148
    ];
149
150
    /**
151
     * @param array $labels
152
     */
153
    public function updateFieldLabels(&$labels)
154
    {
155
        $labels['Title'] = _t(__CLASS__ . '.TitleLabel', 'Product Name');
156
        $labels['Code'] = _t(__CLASS__ . '.CodeLabel', 'Code');
157
        $labels['Price'] = _t(__CLASS__ . '.PriceLabel', 'Price');
158
        $labels['Price.Nice'] = _t(__CLASS__ . '.PriceLabel', 'Price');
159
        $labels['Available'] = _t(__CLASS__ . '.AvailableLabel', 'Available for purchase');
160
        $labels['Available.Nice'] = _t(__CLASS__ . '.AvailableLabelNice', 'Available');
161
        $labels['Image.CMSThumbnail'] = _t(__CLASS__ . '.ImageLabel', 'Image');
162
        $labels['ReceiptTitle'] = _t(__CLASS__ . '.ReceiptTitleLabel', 'Product title for receipt');
163
        $labels['FoxyCategoryID'] = _t(__CLASS__ . '.FoxyCategoryLabel', 'Foxy Category');
164
    }
165
166
    /**
167
     * @param FieldList $fields
168
     */
169
    public function updateCMSFields(FieldList $fields)
170
    {
171
        $fields->removeByName([
172
            'SKU',
173
        ]);
174
175
        $fields->addFieldsToTab(
176
            'Root.Ecommerce',
177
            [
178
                CurrencyField::create('Price')
179
                    ->setDescription(_t(
180
                        __CLASS__ . '.PriceDescription',
181
                        'Base price for this product. Can be modified using Product Options'
182
                    )),
183
                TextField::create('Code')
184
                    ->setDescription(_t(
185
                        __CLASS__ . '.CodeDescription',
186
                        'Required, must be unique. Product identifier used by FoxyCart in transactions. All leading and trailing spaces are removed on save.'
187
                    )),
188
                DropdownField::create('FoxyCategoryID')
189
                    ->setTitle($this->owner->fieldLabel('FoxyCategoryID'))
0 ignored issues
show
Bug introduced by
The method fieldLabel() does not exist on Dynamic\Foxy\Extension\Purchasable. ( Ignorable by Annotation )

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

189
                    ->setTitle($this->owner->/** @scrutinizer ignore-call */ fieldLabel('FoxyCategoryID'))

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...
190
                    ->setSource(FoxyCategory::get()->map())
191
                    ->setDescription(_t(
192
                        __CLASS__ . '.FoxyCategoryDescription',
193
                        'Required. Must also exist in
194
                        <a href="https://admin.foxycart.com/admin.php?ThisAction=ManageProductCategories"
195
                            target="_blank">
196
                            Foxy Categories
197
                        </a>.
198
                        Used to set category specific options like shipping and taxes. Managed in Foxy > Categories'
199
                    ))
200
                    ->setEmptyString(''),
201
                TextField::create('ReceiptTitle')
202
                    ->setDescription(_t(
203
                        __CLASS__ . '.ReceiptTitleDescription',
204
                        'Optional. Alternate title to display on order receipt'
205
                    )),
206
            ],
207
            'Content'
208
        );
209
210
        if ($this->owner->exists()) {
0 ignored issues
show
Bug introduced by
The method exists() does not exist on Dynamic\Foxy\Extension\Purchasable. ( Ignorable by Annotation )

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

210
        if ($this->owner->/** @scrutinizer ignore-call */ exists()) {

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...
211
            $config = GridFieldConfig_RelationEditor::create();
212
            $config
213
                ->addComponents([
214
                    new GridFieldOrderableRows('SortOrder'),
215
                    new GridFieldAddExistingSearchButton(),
216
                ])
217
                ->removeComponentsByType([
218
                    GridFieldAddExistingAutocompleter::class,
219
                ]);
220
            $options = GridField::create(
221
                'Options',
222
                'Options',
223
                $this->owner->Options()->sort('SortOrder'),
224
                $config
225
            );
226
227
            $fields->addFieldsToTab(
228
                'Root.Options',
229
                [
230
                    $options,
231
                ]
232
            );
233
234
            $variationsConfig = GridFieldConfig_RelationEditor::create()
235
                ->removeComponentsByType([
236
                    GridFieldAddExistingAutocompleter::class,
237
                    GridFieldPaginator::class,
238
                    GridFieldPageCount::class,
239
                    GridFieldSortableHeader::class,
240
                ])
241
                ->addComponents([
242
                    new GridFieldOrderableRows('SortOrder'),
243
                    new GridFieldTitleHeader(),
244
                    new GridFieldGroupable(
245
                        'VariationTypeID',    // The fieldname to set the Group
246
                        'Variation Type',   // A description of the function of the group
247
                        'none',         // A title/header for items without a group/unassigned
248
                        VariationType::get()->sort('SortOrder')->map()->toArray()
249
                    )
250
                ]);
251
252
            $fields->addFieldToTab(
253
                'Root.Variations',
254
                GridField::create(
255
                    'Variations',
256
                    'Variations',
257
                    $this->owner->Variations(),
258
                    $variationsConfig
259
                )
260
            );
261
        }
262
263
        $fields->addFieldsToTab(
264
            'Root.Inventory',
265
            [
266
                NumericField::create('QuantityMax')
267
                    ->setTitle('Maximum quantity allowed in the cart')
268
                    ->setDescription('For unlimited enter 0')
269
                    ->addExtraClass('stacked'),
270
                CheckboxField::create('Available')
271
                    ->setDescription(_t(
272
                        __CLASS__ . '.AvailableDescription',
273
                        'If unchecked, will remove "Add to Cart" form and instead display "Currently unavailable"'
274
                    )),
275
            ]
276
        );
277
    }
278
279
    /**
280
     * @return RequiredFields
281
     */
282
    public function getCMSValidator()
283
    {
284
        return new RequiredFields([
285
            "Price",
286
            "Code",
287
            "FoxyCategoryID",
288
        ]);
289
    }
290
291
    /**
292
     * @return $this
293
     */
294
    public function setHasVariations()
295
    {
296
        $this->has_variations = $this->owner->Variations()->exists();
297
        return $this;
298
    }
299
300
    /**
301
     * @return mixed
302
     */
303
    public function getHasVariations()
304
    {
305
        if (!$this->has_variations) {
306
            $this->setHasVariations();
307
        }
308
        return $this->has_variations;
309
    }
310
311
    /**
312
     * @return mixed
313
     */
314
    public function isAvailable()
315
    {
316
        if (!$this->owner->Available) {
317
            return false;
318
        }
319
320
        if (!$this->getHasVariations()) {
321
            return true;
322
        }
323
324
        foreach ($this->owner->Variations() as $variation) {
325
            if ($variation->Available) {
326
                return true;
327
            }
328
        }
329
330
        return false;
331
    }
332
333
    /**
334
     * @return bool
335
     */
336
    public function isProduct()
337
    {
338
        return true;
339
    }
340
341
    /**
342
     * @return array
343
     */
344
    public function providePermissions()
345
    {
346
        return [
347
            'MANAGE_FOXY_PRODUCTS' => [
348
                'name' => _t(
349
                    __CLASS__ . '.PERMISSION_MANAGE_PRODUCTS_DESCRIPTION',
350
                    'Manage products'
351
                ),
352
                'category' => _t(
353
                    __CLASS__ . '.PERMISSIONS_CATEGORY',
354
                    'Foxy'
355
                ),
356
                'help' => _t(
357
                    __CLASS__ . '.PERMISSION_MANAGE_PRODUCTS_HELP',
358
                    'Manage products and related settings'
359
                ),
360
                'sort' => 400,
361
            ],
362
        ];
363
    }
364
365
    /**
366
     * @param $member
367
     * @return bool|int|void
368
     */
369
    public function canCreate($member)
370
    {
371
        if (!$member) {
372
            $member = Security::getCurrentUser();
373
        }
374
375
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
376
    }
377
378
    /**
379
     * @param $member
380
     * @return bool|int|void|null
381
     */
382
    public function canEdit($member)
383
    {
384
        if (!$member) {
385
            $member = Security::getCurrentUser();
386
        }
387
388
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
389
    }
390
391
    /**
392
     * @param $member
393
     * @return bool|int|void
394
     */
395
    public function canDelete($member)
396
    {
397
        if (!$member) {
398
            $member = Security::getCurrentUser();
399
        }
400
401
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
402
    }
403
404
    /**
405
     * @param null $member
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $member is correct as it would always require null to be passed?
Loading history...
406
     * @return bool|int
407
     */
408
    public function canUnpublish($member = null)
409
    {
410
        if (!$member) {
0 ignored issues
show
introduced by
$member is of type null, thus it always evaluated to false.
Loading history...
411
            $member = Security::getCurrentUser();
412
        }
413
414
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
415
    }
416
417
    /**
418
     * @param $member
419
     * @return bool|int
420
     */
421
    public function canArchive($member = null)
422
    {
423
        if (!$member) {
424
            $member = Security::getCurrentUser();
425
        }
426
427
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
428
    }
429
430
    /**
431
     *
432
     */
433
    public function onBeforeWrite()
434
    {
435
        // trim spaces and replace duplicate spaces
436
        $trimmed = trim($this->owner->Code);
437
        $this->owner->Code = preg_replace('/\s+/', ' ', $trimmed);
438
    }
439
}
440