Product::canUnpublish()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
namespace Dynamic\Foxy\Page;
4
5
use Dynamic\Foxy\Controller\ProductController;
6
use Dynamic\Foxy\Model\FoxyCategory;
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\GridFieldDeleteAction;
18
use SilverStripe\Forms\GridField\GridFieldPageCount;
19
use SilverStripe\Forms\GridField\GridFieldPaginator;
20
use SilverStripe\Forms\GridField\GridFieldSortableHeader;
21
use SilverStripe\Forms\NumericField;
22
use SilverStripe\Forms\RequiredFields;
23
use SilverStripe\Forms\TextField;
24
use SilverStripe\ORM\DataObject;
25
use SilverStripe\ORM\HasManyList;
26
use SilverStripe\ORM\ValidationException;
27
use SilverStripe\Security\Permission;
28
use SilverStripe\Security\Security;
29
use Symbiote\GridFieldExtensions\GridFieldOrderableRows;
30
use Symbiote\GridFieldExtensions\GridFieldTitleHeader;
31
32
/**
33
 * @property double Price
34
 * @property string Code
35
 * @property string ReceiptTitle
36
 * @property bool Available
37
 * @property int $QuantityMax
38
 *
39
 * @property int FoxyCategoryID
40
 * @method FoxyCategory FoxyCategory()
41
 *
42
 * @method HasManyList Variations()
43
 */
44
class Product extends \Page
45
{
46
    /**
47
     * @var string
48
     */
49
    private static $table_name = 'FoxyProduct';
50
51
    /**
52
     * Force default variant's title to match the Product page's title if the variant reset is called.
53
     *
54
     * @var bool
55
     */
56
    private static $force_default_variant_title_update = false;
57
58
    /**
59
     * @var array
60
     */
61
    private static $db = [
62
        'Price' => 'Currency(9,3)',
63
        'Code' => 'Varchar(100)',
64
        'ReceiptTitle' => 'HTMLVarchar(255)',
65
        'Available' => 'Boolean',
66
        'QuantityMax' => 'Int',
67
    ];
68
69
    /**
70
     * @var array
71
     */
72
    private static $has_one = [
73
        'FoxyCategory' => FoxyCategory::class,
74
    ];
75
76
    /**
77
     * @var string[]
78
     */
79
    private static $has_many = [
80
        'Variations' => Variation::class,
81
    ];
82
83
    /**
84
     * @var array
85
     */
86
    private static $indexes = [
87
        'Code' => [
88
            'type' => 'unique',
89
            'columns' => ['Code'],
90
        ],
91
    ];
92
93
    /**
94
     * @var array
95
     */
96
    private static $defaults = [
97
        'ShowInMenus' => false,
98
        'Available' => true,
99
    ];
100
101
    /**
102
     * @var array
103
     */
104
    private static $summary_fields = [
105
        //'Image.CMSThumbnail',
106
        'Title',
107
        'Code',
108
        'Price.Nice',
109
    ];
110
111
    /**
112
     * @var array
113
     */
114
    private static $searchable_fields = [
115
        'Title',
116
        'Code',
117
        'Available',
118
    ];
119
120
    /**
121
     * @param $includeRelations
122
     * @return array
123
     */
124
    public function fieldLabels($includeRelations = true)
125
    {
126
        $labels = parent::fieldLabels($includeRelations);
127
128
        $labels['Title'] = _t(__CLASS__ . '.TitleLabel', 'Product Name');
129
        $labels['Code'] = _t(__CLASS__ . '.CodeLabel', 'Code');
130
        $labels['Price'] = _t(__CLASS__ . '.PriceLabel', 'Price');
131
        $labels['Price.Nice'] = _t(__CLASS__ . '.PriceLabel', 'Price');
132
        $labels['Available'] = _t(__CLASS__ . '.AvailableLabel', 'Available for purchase');
133
        $labels['Available.Nice'] = _t(__CLASS__ . '.AvailableLabelNice', 'Available');
134
        //$labels['Image.CMSThumbnail'] = _t(__CLASS__ . '.ImageLabel', 'Image');
135
        $labels['ReceiptTitle'] = _t(__CLASS__ . '.ReceiptTitleLabel', 'Product title for receipt');
136
        $labels['FoxyCategoryID'] = _t(__CLASS__ . '.FoxyCategoryLabel', 'Foxy Category');
137
138
        return $labels;
139
    }
140
141
    /**
142
     * @return FieldList
143
     */
144
    public function getCMSFields()
145
    {
146
        $this->beforeUpdateCMSFields(function (FieldList $fields) {
147
            $fields->removeByName([
148
                'SKU',
149
            ]);
150
151
            $fields->addFieldsToTab(
152
                'Root.Ecommerce',
153
                [
154
                    TextField::create('Price')
155
                        ->setDescription(_t(
156
                            __CLASS__ . '.PriceDescription',
157
                            'Base price for this product. Can be modified using Product variations'
158
                        )),
159
                    TextField::create('Code')
160
                        ->setDescription(_t(
161
                            __CLASS__ . '.CodeDescription',
162
                            'Required, must be unique. Product identifier used by FoxyCart in transactions. All leading and trailing spaces are removed on save.'
163
                        )),
164
                    DropdownField::create('FoxyCategoryID')
165
                        ->setTitle($this->fieldLabel('FoxyCategoryID'))
166
                        ->setSource(FoxyCategory::get()->map())
167
                        ->setDescription(_t(
168
                            __CLASS__ . '.FoxyCategoryDescription',
169
                            'Required. Must also exist in
170
                        <a href="https://admin.foxycart.com/admin.php?ThisAction=ManageProductCategories"
171
                            target="_blank">
172
                            Foxy Categories
173
                        </a>.
174
                        Used to set category specific options like shipping and taxes. Managed in Foxy > Categories'
175
                        ))
176
                        ->setEmptyString(''),
177
                    TextField::create('ReceiptTitle')
178
                        ->setDescription(_t(
179
                            __CLASS__ . '.ReceiptTitleDescription',
180
                            'Optional. Alternate title to display on order receipt'
181
                        )),
182
                ],
183
                'Content'
184
            );
185
186
            if ($this->exists()) {
187
                $variationsConfig = GridFieldConfig_RelationEditor::create()
188
                    ->removeComponentsByType([
189
                        GridFieldAddExistingAutocompleter::class,
190
                        GridFieldPaginator::class,
191
                        GridFieldPageCount::class,
192
                        GridFieldSortableHeader::class,
193
                        GridFieldDeleteAction::class,
194
                    ])
195
                    ->addComponents([
196
                        new GridFieldOrderableRows('SortOrder'),
197
                        new GridFieldTitleHeader(),
198
                        new GridFieldGroupable(
199
                            'VariationTypeID',    // The fieldname to set the Group
200
                            'Variation Type',   // A description of the function of the group
201
                            'none',         // A title/header for items without a group/unassigned
202
                            VariationType::get()->sort('SortOrder')->map()->toArray()
203
                        ),
204
                        new GridFieldDeleteAction(),
205
                    ]);
206
207
                $fields->addFieldToTab(
208
                    'Root.Variations',
209
                    GridField::create(
210
                        'Variations',
211
                        'Variations',
212
                        $this->Variations(),
213
                        $variationsConfig
214
                    )
215
                );
216
            }
217
218
            $fields->addFieldsToTab(
219
                'Root.Inventory',
220
                [
221
                    NumericField::create('QuantityMax')
222
                        ->setTitle('Maximum quantity allowed in the cart')
223
                        ->setDescription('For unlimited enter 0')
224
                        ->addExtraClass('stacked'),
225
                    CheckboxField::create('Available')
226
                        ->setDescription(_t(
227
                            __CLASS__ . '.AvailableDescription',
228
                            'If unchecked, will remove "Add to Cart" form and instead display "Currently unavailable"'
229
                        )),
230
                ]
231
            );
232
        });
233
234
        return parent::getCMSFields();
235
    }
236
237
    /**
238
     * @return RequiredFields
239
     */
240
    public function getCMSValidator()
241
    {
242
        return new RequiredFields([
243
            "Price",
244
            "Code",
245
            "FoxyCategoryID",
246
        ]);
247
    }
248
249
    /**
250
     * @return bool
251
     */
252
    public function getIsAvailable()
253
    {
254
        $available = true;
255
256
        if (!$this->Available) {
257
            $available = false;
258
        }
259
260
        if ($available && $this->Variations()->count()) {
261
            $available = false;
262
            foreach ($this->Variations() as $variation) {
263
                if ($variation->Available) {
264
                    $available = true;
265
                }
266
            }
267
        }
268
269
        $this->extend('updateGetIsAvailable', $available);
270
271
        return $available;
272
    }
273
274
    /**
275
     * @return array
276
     */
277
    public function providePermissions()
278
    {
279
        return [
280
            'MANAGE_FOXY_PRODUCTS' => [
281
                'name' => _t(
282
                    __CLASS__ . '.PERMISSION_MANAGE_PRODUCTS_DESCRIPTION',
283
                    'Manage products'
284
                ),
285
                'category' => _t(
286
                    __CLASS__ . '.PERMISSIONS_CATEGORY',
287
                    'Foxy'
288
                ),
289
                'help' => _t(
290
                    __CLASS__ . '.PERMISSION_MANAGE_PRODUCTS_HELP',
291
                    'Manage products and related settings'
292
                ),
293
                'sort' => 400,
294
            ],
295
        ];
296
    }
297
298
    /**
299
     * @param $member
300
     * @param $context
301
     * @return bool|int
302
     */
303
    public function canCreate($member = null, $context = [])
304
    {
305
        if (!$member) {
306
            $member = Security::getCurrentUser();
307
        }
308
309
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
310
    }
311
312
    /**
313
     * @param $member
314
     * @return bool|int
315
     */
316
    public function canEdit($member = null)
317
    {
318
        if (!$member) {
319
            $member = Security::getCurrentUser();
320
        }
321
322
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
323
    }
324
325
    /**
326
     * @param $member
327
     * @return bool|int
328
     */
329
    public function canDelete($member = null)
330
    {
331
        if (!$member) {
332
            $member = Security::getCurrentUser();
333
        }
334
335
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
336
    }
337
338
    /**
339
     * @param $member
340
     * @return bool|int|mixed|null
341
     */
342
    public function canUnpublish($member = null)
343
    {
344
        if (!$member) {
345
            $member = Security::getCurrentUser();
346
        }
347
348
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
349
    }
350
351
    /**
352
     * @param $member
353
     * @return bool|int
354
     */
355
    public function canArchive($member = null)
356
    {
357
        if (!$member) {
358
            $member = Security::getCurrentUser();
359
        }
360
361
        return Permission::checkMember($member, 'MANAGE_FOXY_PRODUCTS');
362
    }
363
364
    /**
365
     * @return void
366
     */
367
    public function onBeforeWrite()
368
    {
369
        parent::onBeforeWrite();
370
371
        // trim spaces and replace duplicate spaces
372
        $trimmed = trim($this->Code);
373
        $this->Code = preg_replace('/\s+/', ' ', $trimmed);
374
    }
375
376
    /**
377
     * @return void
378
     * @throws ValidationException
379
     */
380
    protected function onAfterWrite()
381
    {
382
        parent::onAfterWrite();
383
384
        if (!$this->getDefaultVariation() || $this->baseProductChange()) {
385
            $this->resetDefaultVariant();
386
        }
387
    }
388
389
    /**
390
     * @return string
391
     */
392
    public function getControllerName()
393
    {
394
        return ProductController::class;
395
    }
396
397
    /**
398
     * @return bool
399
     */
400
    protected function baseProductChange()
401
    {
402
        return $this->isChanged('Code');
403
    }
404
405
    /**
406
     * @return DataObject|null
407
     */
408
    public function getDefaultVariation()
409
    {
410
        return $this->Variations()->filter('IsDefault', true)->first();
411
    }
412
413
    /**
414
     * @return void
415
     * @throws ValidationException
416
     */
417
    protected function resetDefaultVariant()
418
    {
419
        if (!$variant = $this->getDefaultVariation()) {
420
            $variant = Variation::create();
421
422
            foreach (Variation::singleton()->config()->get('default_variation_mapping') as $productField => $variationField) {
423
                $variant->$variationField = $this->$productField;
424
            }
425
426
            $variant->ProductID = $this->ID;
427
            $variant->IsDefault = true;
428
429
            $variant->write();
430
        }
431
432
        if (!$variant->Title || $this->config()->get('force_default_variant_title_update')) {
433
            $variant->Title = $this->Title;
434
        }
435
436
        $variant->CodeModifier = $this->Code;
437
        $variant->CodeModifierAction = 'Set';
438
439
        $variant->write();
440
    }
441
}
442