Completed
Push — master ( dc665d...111f9f )
by Nicolaas
03:48
created

ProductGroup_Controller::debug()   F

Complexity

Conditions 12
Paths 1024

Size

Total Lines 84
Code Lines 67

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
dl 0
loc 84
rs 2
c 0
b 0
f 0
eloc 67
nc 1024
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
 * Product Group is a 'holder' for Products within the CMS
5
 * It contains functions for versioning child products.
6
 *
7
 * The way the products are selected:
8
 *
9
 * Controller calls:
10
 * ProductGroup::ProductsShowable($extraFilter = "")
11
 *
12
 * ProductsShowable runs currentInitialProducts.  This selects ALL the applicable products
13
 * but it does NOT PAGINATE (limit) or SORT them.
14
 * After that, it calls currentFinalProducts, this sorts the products and notes the total
15
 * count of products (removing ones that can not be shown for one reason or another)
16
 *
17
 * Pagination is done in the controller.
18
 *
19
 * For each product page, there is a default:
20
 *  - filter
21
 *  - sort
22
 *  - number of levels to show (e.g. children, grand-children, etc...)
23
 * and these settings can be changed in the CMS, depending on what the
24
 * developer makes available to the content editor.
25
 *
26
 * In extending the ProductGroup class, it is recommended
27
 * that you override the following methods (as required ONLY!):
28
 * - getBuyableClassName
29
 * - getGroupFilter
30
 * - getStandardFilter
31
 * - getGroupJoin
32
 * - currentSortSQL
33
 * - limitCurrentFinalProducts
34
 * - removeExcludedProductsAndSaveIncludedProducts
35
 *
36
 * To filter products, you have three options:
37
 *
38
 * (1) getGroupFilter
39
 * - the standard product groups from which the products are selected
40
 * - if you extend Product Group this is the one you most likely want to change
41
 * - for example, rather than children, you set it to "yellow" products
42
 * - goes hand in hand with changes to showProductLevels / LevelOfProductsToShow
43
 * - works out the group filter based on the LevelOfProductsToShow value
44
 * - it also considers the other group many-many relationship
45
 * - this filter ALWAYS returns something: 1 = 1 if nothing else.
46
 *
47
 * (2) getStandardFilter
48
 * - these are the standard (user selectable) filters
49
 * - available options set via config
50
 * - the standard filter is updated by controller
51
 * - options can show above / below product lists to let user select alternative filter.
52
 *
53
 * (3) the extraWhere in ProductsShowable
54
 * - provided by the controller for specific ('on the fly') sub-sets
55
 * - this is for example for search results
56
 * - set in ProductShowable($extraWhere)
57
 *
58
 *
59
 * Caching
60
 * ==================
61
 *
62
 * There are two type of caching available:
63
 *
64
 * (1) caching of Product SQL queries
65
 *     - turned on and off by variable: ProductGroup->allowCaching
66
 *     - this is not a static so that you can create different settings for ProductGroup extensions.
67
 * (2) caching of product lists
68
 *     - see Product_Controller::ProductGroupListAreCacheable
69
 *
70
 * You can also ajaxify the product list, although this has nothing to do with
71
 * caching, it is related to it.
72
 *
73
 *
74
 * @authors: Nicolaas [at] Sunny Side Up .co.nz
75
 * @package: ecommerce
76
 * @sub-package: Pages
77
 * @inspiration: Silverstripe Ltd, Jeremy
78
 **/
79
class ProductGroup extends Page
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...
80
{
81
    /**
82
     * standard SS variable.
83
     *
84
     * @static Array
85
     */
86
    private static $db = array(
87
        'NumberOfProductsPerPage' => 'Int',
88
        'LevelOfProductsToShow' => 'Int',
89
        'DefaultSortOrder' => 'Varchar(20)',
90
        'DefaultFilter' => 'Varchar(20)',
91
        'DisplayStyle' => 'Varchar(20)',
92
    );
93
94
    /**
95
     * standard SS variable.
96
     *
97
     * @static Array
98
     */
99
    private static $has_one = array(
100
        'Image' => 'Product_Image',
101
    );
102
103
    /**
104
     * standard SS variable.
105
     *
106
     * @static Array
107
     */
108
    private static $belongs_many_many = array(
109
        'AlsoShowProducts' => 'Product',
110
    );
111
112
    /**
113
     * standard SS variable.
114
     *
115
     * @static Array
116
     */
117
    private static $defaults = array(
118
        'DefaultSortOrder' => 'default',
119
        'DefaultFilter' => 'default',
120
        'DisplayStyle' => 'default',
121
        'LevelOfProductsToShow' => 99,
122
    );
123
124
    /**
125
     * standard SS variable.
126
     *
127
     * @static Array
128
     */
129
    private static $indexes = array(
130
        'LevelOfProductsToShow' => true,
131
        'DefaultSortOrder' => true,
132
        'DefaultFilter' => true,
133
        'DisplayStyle' => true,
134
    );
135
136
    private static $summary_fields = array(
137
        'Image.CMSThumbnail' => 'Image',
138
        'Title' => 'Category',
139
        'NumberOfProducts' => 'Direct Product Count'
140
    );
141
142
    private static $casting = array(
143
        'NumberOfProducts' => 'Int'
144
    );
145
146
    /**
147
     * standard SS variable.
148
     *
149
     * @static String
150
     */
151
    private static $default_child = 'Product';
152
153
    /**
154
     * standard SS variable.
155
     *
156
     * @static String | Array
157
     */
158
    private static $icon = 'ecommerce/images/icons/productgroup';
159
160
    /**
161
     * Standard SS variable.
162
     */
163
    private static $singular_name = 'Product Category';
164
    public function i18n_singular_name()
165
    {
166
        return _t('ProductGroup.SINGULARNAME', 'Product Category');
167
    }
168
169
    /**
170
     * Standard SS variable.
171
     */
172
    private static $plural_name = 'Product Categories';
173
    public function i18n_plural_name()
174
    {
175
        return _t('ProductGroup.PLURALNAME', 'Product Categories');
176
    }
177
178
    /**
179
     * Standard SS variable.
180
     *
181
     * @var string
182
     */
183
    private static $description = 'A page the shows a bunch of products, based on your selection. By default it shows products linked to it (children)';
184
185
    public function canCreate($member = null)
186
    {
187
        if (! $member) {
188
            $member = Member::currentUser();
189
        }
190
        $extended = $this->extendedCan(__FUNCTION__, $member);
191
        if ($extended !== null) {
192
            return $extended;
193
        }
194
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
195
            return true;
196
        }
197
198
        return parent::canEdit($member);
199
    }
200
201
    /**
202
     * Shop Admins can edit.
203
     *
204
     * @param Member $member
205
     *
206
     * @return bool
207
     */
208
    public function canEdit($member = null)
209
    {
210
        if (! $member) {
211
            $member = Member::currentUser();
212
        }
213
        $extended = $this->extendedCan(__FUNCTION__, $member);
214
        if ($extended !== null) {
215
            return $extended;
216
        }
217
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
218
            return true;
219
        }
220
221
        return parent::canEdit($member);
222
    }
223
224
    /**
225
     * Standard SS method.
226
     *
227
     * @param Member $member
228
     *
229
     * @return bool
230
     */
231
    public function canDelete($member = null)
232
    {
233
        if (is_a(Controller::curr(), Object::getCustomClass('ProductsAndGroupsModelAdmin'))) {
234
            return false;
235
        }
236
        if (! $member) {
237
            $member = Member::currentUser();
238
        }
239
        $extended = $this->extendedCan(__FUNCTION__, $member);
240
        if ($extended !== null) {
241
            return $extended;
242
        }
243
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
244
            return true;
245
        }
246
247
        return parent::canEdit($member);
248
    }
249
250
    /**
251
     * Standard SS method.
252
     *
253
     * @param Member $member
254
     *
255
     * @return bool
256
     */
257
    public function canPublish($member = null)
258
    {
259
        if (Permission::checkMember($member, Config::inst()->get('EcommerceRole', 'admin_permission_code'))) {
260
            return true;
261
        }
262
263
        return parent::canEdit($member);
264
    }
265
266
    /**
267
     * list of sort / filter / display variables.
268
     *
269
     * @var array
270
     */
271
    protected $sortFilterDisplayNames = array(
272
        'SORT' => array(
273
            'value' => 'default',
274
            'configName' => 'sort_options',
275
            'sessionName' => 'session_name_for_sort_preference',
276
            'getVariable' => 'sort',
277
            'dbFieldName' => 'DefaultSortOrder',
278
            'translationCode' => 'SORT_BY',
279
        ),
280
        'FILTER' => array(
281
            'value' => 'default',
282
            'configName' => 'filter_options',
283
            'sessionName' => 'session_name_for_filter_preference',
284
            'getVariable' => 'filter',
285
            'dbFieldName' => 'DefaultFilter',
286
            'translationCode' => 'FILTER_FOR',
287
        ),
288
        'DISPLAY' => array(
289
            'value' => 'default',
290
            'configName' => 'display_styles',
291
            'sessionName' => 'session_name_for_display_style_preference',
292
            'getVariable' => 'display',
293
            'dbFieldName' => 'DisplayStyle',
294
            'translationCode' => 'DISPLAY_STYLE',
295
        ),
296
    );
297
298
    /**
299
     * @var array
300
     *            List of options to show products.
301
     *            With it, we provide a bunch of methods to access and edit the options.
302
     *            NOTE: we can not have an option that has a zero key ( 0 => "none"), as this does not work
303
     *            (as it is equal to not completed yet - not yet entered in the Database).
304
     */
305
    protected $showProductLevels = array(
306
        99 => 'All Child Products (default)',
307
        -2 => 'None',
308
        -1 => 'All products',
309
        1 => 'Direct Child Products',
310
        2 => 'Direct Child Products + Grand Child Products',
311
        3 => 'Direct Child Products + Grand Child Products + Great Grand Child Products',
312
        4 => 'Direct Child Products + Grand Child Products + Great Grand Child Products + Great Great Grand Child Products',
313
    );
314
315
    /**
316
     * variable to speed up methods in this class.
317
     *
318
     * @var array
319
     */
320
    protected $configOptionsCache = array();
321
322
    /**
323
     * cache variable for default preference key.
324
     *
325
     * @var array
326
     */
327
    protected $myUserPreferencesDefaultCache = array();
328
329
    /**
330
     * count before limit.
331
     *
332
     * @var int
333
     */
334
    protected $rawCount = 0;
335
336
    /**
337
     * count after limit.
338
     *
339
     * @var int
340
     */
341
    protected $totalCount = 0;
342
343
     /**
344
      * Can product list (and related) be cached at all?
345
      * Set this to FALSE if the product details can be changed
346
      * for an individual user.
347
      *
348
      * @var bool
349
      */
350
     protected $allowCaching = true;
351
352
    /**
353
     * return the options for one type.
354
     * This method solely exists to speed up processing.
355
     *
356
     * @param string $type - options are FILTER | SORT | DISPLAY
357
     *
358
     * @return array
359
     */
360
    protected function getConfigOptions($type)
361
    {
362
        if (!isset($this->configOptionsCache[$type])) {
363
            $configName = $this->sortFilterDisplayNames[$type]['configName'];
364
            $this->configOptionsCache[$type] = EcommerceConfig::get($this->ClassName, $configName);
365
        }
366
367
        return $this->configOptionsCache[$type];
368
    }
369
370
    /**
371
     * returns the full sortFilterDisplayNames set, a subset, or one value
372
     * by either type (e.g. FILER) or variable (e.g dbFieldName)
373
     * or both.
374
     *
375
     * @param string $typeOfVariableName FILTER | SORT | DISPLAY or sessionName, getVariable, etc...
376
     * @param string $variable:          sessionName, getVariable, etc...
377
     *
378
     * @return array | String
379
     */
380
    protected function getSortFilterDisplayNames($typeOrVariable = '', $variable = '')
381
    {
382
        //return a string ...
383
        if ($variable) {
384
            return $this->sortFilterDisplayNames[$typeOrVariable][$variable];
385
        }
386
        //return an array ...
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% 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...
387
        $data = array();
388
        if (isset($this->sortFilterDisplayNames[$typeOrVariable])) {
389
            $data = $this->sortFilterDisplayNames[$typeOrVariable];
390
        } elseif ($typeOrVariable) {
391
            foreach ($this->sortFilterDisplayNames as $group) {
392
                $data[] = $group[$typeOrVariable];
393
            }
394
        } else {
395
            $data = $this->sortFilterDisplayNames;
396
        }
397
398
        return $data;
399
    }
400
401
    /**
402
     * sets a user preference.  This is typically used by the controller
403
     * to set filter and sort.
404
     *
405
     * @param string $type  SORT | FILTER | DISPLAY
406
     * @param string $value
407
     */
408
    protected function setCurrentUserPreference($type, $value)
409
    {
410
        $this->sortFilterDisplayNames[$type]['value'] = $value;
411
    }
412
413
    /**
414
     * Get a user preference.
415
     * This value can be updated by the controller
416
     * For example, the filter can be changed, based on a session value.
417
     *
418
     * @param string $type SORT | FILTER | DISPLAY
419
     *
420
     * @return string
421
     */
422
    protected function getCurrentUserPreferences($type)
423
    {
424
        return $this->sortFilterDisplayNames[$type]['value'];
425
    }
426
427
    /*********************
428
     * SETTINGS: Default Key
429
     *********************/
430
431
    /**
432
     * Checks for the most applicable user preferences for this page:
433
     * 1. what is saved in Database for this page.
434
     * 2. what the parent product group has saved in the database
435
     * 3. what the standard default is.
436
     *
437
     * @param string $type - FILTER | SORT | DISPLAY
438
     *
439
     * @return string - returns the key
440
     */
441
    protected function getMyUserPreferencesDefault($type)
442
    {
443
        if (!isset($this->myUserPreferencesDefaultCache[$type]) || !$this->myUserPreferencesDefaultCache[$type]) {
444
            $options = $this->getConfigOptions($type);
445
            $dbVariableName = $this->sortFilterDisplayNames[$type]['dbFieldName'];
446
            $defaultOption = '';
447
            if ($defaultOption == 'inherit' && $parent = $this->ParentGroup()) {
448
                $defaultOption = $parent->getMyUserPreferencesDefault($type);
449
            } elseif ($this->$dbVariableName && array_key_exists($this->$dbVariableName, $options)) {
450
                $defaultOption = $this->$dbVariableName;
451
            }
452
            if (!$defaultOption) {
453
                if (isset($options['default'])) {
454
                    $defaultOption = 'default';
455
                } else {
456
                    user_error("It is recommended that you have a default (key) option for $type", E_USER_NOTICE);
457
                    $keys = array_keys($options);
458
                    $defaultOption = $keys[0];
459
                }
460
            }
461
            $this->myUserPreferencesDefaultCache[$type] = $defaultOption;
462
        }
463
464
        return $this->myUserPreferencesDefaultCache[$type];
465
    }
466
467
    /*********************
468
     * SETTINGS: Dropdowns
469
     *********************/
470
    /**
471
     * SORT:
472
     * returns an array of Key => Title for sort options.
473
     *
474
     * FILTER:
475
     * Returns options for the dropdown of filter options.
476
     *
477
     * DISPLAY:
478
     * Returns the options for product display styles.
479
     * In the configuration you can set which ones are available.
480
     * If one is available then you must make sure that the corresponding template is available.
481
     * For example, if the display style is
482
     * MyTemplate => "All Details"
483
     * Then you must make sure MyTemplate.ss exists.
484
     *
485
     * @param string $type - FILTER | SORT | DISPLAY
486
     *
487
     * @return array
488
     */
489
    protected function getUserPreferencesOptionsForDropdown($type)
490
    {
491
        $options = $this->getConfigOptions($type);
492
        $inheritTitle = _t('ProductGroup.INHERIT', 'Inherit');
493
        $array = array('inherit' => $inheritTitle);
494
        if (is_array($options) && count($options)) {
495
            foreach ($options as $key => $option) {
496
                if (is_array($option)) {
497
                    $array[$key] = $option['Title'];
498
                } else {
499
                    $array[$key] = $option;
500
                }
501
            }
502
        }
503
504
        return $array;
505
    }
506
507
    /*********************
508
     * SETTINGS: SQL
509
     *********************/
510
511
    /**
512
     * SORT:
513
     * Returns the sort sql for a particular sorting key.
514
     * If no key is provided then the default key will be returned.
515
     *
516
     * @param string $key
517
     *
518
     * @return array (e.g. Array(MyField => "ASC", "MyOtherField" => "DESC")
519
     *
520
     * FILTER:
521
     * Returns the sql associated with a filter option.
522
     *
523
     * @param string $type - FILTER | SORT | DISPLAY
524
     * @param string $key  - the options selected
525
     *
526
     * @return array | String (e.g. array("MyField" => 1, "MyOtherField" => 0)) OR STRING
527
     */
528
    protected function getUserSettingsOptionSQL($type, $key = '')
529
    {
530
        $options = $this->getConfigOptions($type);
531
            //if we cant find the current one, use the default
532
        if (!$key || (!isset($options[$key]))) {
533
            $key = $this->getMyUserPreferencesDefault($type);
534
        }
535
        if ($key) {
536
            return $options[$key]['SQL'];
537
        } else {
538
            if ($type == 'FILTER') {
539
                return array('Sort' => 'ASC');
540
            } elseif ($type == 'SORT') {
541
                return array('ShowInSearch' => 1);
542
            }
543
        }
544
    }
545
546
    /*********************
547
     * SETTINGS: Title
548
     *********************/
549
550
    /**
551
     * Returns the Title for a type key.
552
     * If no key is provided then the default key is used.
553
     *
554
     * @param string $type - FILTER | SORT | DISPLAY
555
     * @param string $key
556
     *
557
     * @return string
558
     */
559
    public function getUserPreferencesTitle($type, $key = '')
560
    {
561
        $options = $this->getConfigOptions($type);
562
        if (!$key || (!isset($options[$key]))) {
563
            $key = $this->getMyUserPreferencesDefault($type);
564
        }
565
        if ($key && isset($options[$key]['Title'])) {
566
            return $options[$key]['Title'];
567
        } else {
568
            return _t('ProductGroup.UNKNOWN', 'UNKNOWN USER SETTING');
569
        }
570
    }
571
572
    /*********************
573
     * SETTINGS: products per page
574
     *********************/
575
576
    /**
577
     *@return int
578
     **/
579
    public function ProductsPerPage()
580
    {
581
        return $this->MyNumberOfProductsPerPage();
582
    }
583
    public function MyNumberOfProductsPerPage()
584
    {
585
        $productsPagePage = 0;
586
        if ($this->NumberOfProductsPerPage) {
587
            $productsPagePage = $this->NumberOfProductsPerPage;
588
        } else {
589
            if ($parent = $this->ParentGroup()) {
590
                $productsPagePage = $parent->MyNumberOfProductsPerPage();
591
            } else {
592
                $productsPagePage = $this->EcomConfig()->NumberOfProductsPerPage;
593
            }
594
        }
595
596
        return $productsPagePage;
597
    }
598
599
    /*********************
600
     * SETTINGS: level of products to show
601
     *********************/
602
603
    /**
604
     * returns the number of product groups (children)
605
     * to show in the current product list
606
     * based on the user setting for this page.
607
     *
608
     * @return int
609
     */
610
    public function MyLevelOfProductsToShow()
611
    {
612
        if ($this->LevelOfProductsToShow == 0) {
613
            if ($parent = $this->ParentGroup()) {
614
                $this->LevelOfProductsToShow = $parent->MyLevelOfProductsToShow();
615
            }
616
        }
617
        //reset to default
618
        if ($this->LevelOfProductsToShow     == 0) {
619
            $defaults = Config::inst()->get('ProductGroup', 'defaults');
620
621
            return isset($defaults['LevelOfProductsToShow']) ? $defaults['LevelOfProductsToShow'] : 99;
622
        }
623
624
        return $this->LevelOfProductsToShow;
625
    }
626
627
    /*********************
628
     * CMS Fields
629
     *********************/
630
631
    /**
632
     * standard SS method.
633
     *
634
     * @return FieldList
635
     */
636
    public function getCMSFields()
637
    {
638
        $fields = parent::getCMSFields();
639
        //dirty hack to show images!
640
        $fields->addFieldToTab('Root.Images', Product_ProductImageUploadField::create('Image', _t('Product.IMAGE', 'Product Group Image')));
641
        //number of products
642
        $calculatedNumberOfProductsPerPage = $this->MyNumberOfProductsPerPage();
643
        $numberOfProductsPerPageExplanation = $calculatedNumberOfProductsPerPage != $this->NumberOfProductsPerPage ? _t('ProductGroup.CURRENTLVALUE', 'Current value: ').$calculatedNumberOfProductsPerPage.' '._t('ProductGroup.INHERITEDFROMPARENTSPAGE', ' (inherited from parent page because the current page is set to zero)') : '';
644
        $fields->addFieldToTab(
645
            'Root',
646
            Tab::create(
647
                'ProductDisplay',
648
                _t('ProductGroup.DISPLAY', 'Display'),
649
                $productsToShowField = DropdownField::create('LevelOfProductsToShow', _t('ProductGroup.PRODUCTSTOSHOW', 'Products to show'), $this->showProductLevels),
650
                HeaderField::create('WhatProductsAreShown', _t('ProductGroup.WHATPRODUCTSSHOWN', _t('ProductGroup.OPTIONSSELECTEDBELOWAPPLYTOCHILDGROUPS', 'Inherited options'))),
651
                $numberOfProductsPerPageField = NumericField::create('NumberOfProductsPerPage', _t('ProductGroup.PRODUCTSPERPAGE', 'Number of products per page'))
652
            )
653
        );
654
        $numberOfProductsPerPageField->setRightTitle($numberOfProductsPerPageExplanation);
655
        if ($calculatedNumberOfProductsPerPage && !$this->NumberOfProductsPerPage) {
656
            $this->NumberOfProductsPerPage = null;
657
            $numberOfProductsPerPageField->setAttribute('placeholder', $calculatedNumberOfProductsPerPage);
658
        }
659
        //sort
660
        $sortDropdownList = $this->getUserPreferencesOptionsForDropdown('SORT');
661
        if (count($sortDropdownList) > 1) {
662
            $sortOrderKey = $this->getMyUserPreferencesDefault('SORT');
663
            if ($this->DefaultSortOrder == 'inherit') {
664
                $actualValue = ' ('.(isset($sortDropdownList[$sortOrderKey]) ? $sortDropdownList[$sortOrderKey] : _t('ProductGroup.ERROR', 'ERROR')).')';
665
                $sortDropdownList['inherit'] = _t('ProductGroup.INHERIT', 'Inherit').$actualValue;
666
            }
667
            $fields->addFieldToTab(
668
                'Root.ProductDisplay',
669
                $defaultSortOrderField = DropdownField::create('DefaultSortOrder', _t('ProductGroup.DEFAULTSORTORDER', 'Default Sort Order'), $sortDropdownList)
670
            );
671
            $defaultSortOrderField->setRightTitle(_t('ProductGroup.INHERIT_RIGHT_TITLE', "Inherit means that the parent page value is used - and if there is no relevant parent page then the site's default value is used."));
672
        }
673
        //filter
674
        $filterDropdownList = $this->getUserPreferencesOptionsForDropdown('FILTER');
675
        if (count($filterDropdownList) > 1) {
676
            $filterKey = $this->getMyUserPreferencesDefault('FILTER');
677
            if ($this->DefaultFilter == 'inherit') {
678
                $actualValue = ' ('.(isset($filterDropdownList[$filterKey]) ? $filterDropdownList[$filterKey] : _t('ProductGroup.ERROR', 'ERROR')).')';
679
                $filterDropdownList['inherit'] = _t('ProductGroup.INHERIT', 'Inherit').$actualValue;
680
            }
681
            $fields->addFieldToTab(
682
                'Root.ProductDisplay',
683
                $defaultFilterField = DropdownField::create('DefaultFilter', _t('ProductGroup.DEFAULTFILTER', 'Default Filter'), $filterDropdownList)
684
            );
685
            $defaultFilterField->setRightTitle(_t('ProductGroup.INHERIT_RIGHT_TITLE', "Inherit means that the parent page value is used - and if there is no relevant parent page then the site's default value is used."));
686
        }
687
        //display style
688
        $displayStyleDropdownList = $this->getUserPreferencesOptionsForDropdown('DISPLAY');
689
        if (count($displayStyleDropdownList) > 2) {
690
            $displayStyleKey = $this->getMyUserPreferencesDefault('DISPLAY');
691
            if ($this->DisplayStyle == 'inherit') {
692
                $actualValue = ' ('.(isset($displayStyleDropdownList[$displayStyleKey]) ? $displayStyleDropdownList[$displayStyleKey] : _t('ProductGroup.ERROR', 'ERROR')).')';
693
                $displayStyleDropdownList['inherit'] = _t('ProductGroup.INHERIT', 'Inherit').$actualValue;
694
            }
695
            $fields->addFieldToTab(
696
                'Root.ProductDisplay',
697
                DropdownField::create('DisplayStyle', _t('ProductGroup.DEFAULTDISPLAYSTYLE', 'Default Display Style'), $displayStyleDropdownList)
698
            );
699
        }
700
        if ($this->EcomConfig()->ProductsAlsoInOtherGroups) {
701
            if (!$this instanceof ProductGroupSearchPage) {
702
                $fields->addFieldsToTab(
703
                    'Root.OtherProductsShown',
704
                    array(
705
                        HeaderField::create('ProductGroupsHeader', _t('ProductGroup.OTHERPRODUCTSTOSHOW', 'Other products to show ...')),
706
                        $this->getProductGroupsTable(),
707
                    )
708
                );
709
            }
710
        }
711
712
        return $fields;
713
    }
714
715
    /**
716
     * used if you install lumberjack
717
     * @return string
718
     */
719
    public function getLumberjackTitle()
720
    {
721
        return _t('ProductGroup.BUYABLES', 'Products');
722
    }
723
724
    // /**
725
    //  * used if you install lumberjack
726
    //  * @return string
727
    //  */
728
    // public function getLumberjackGridFieldConfig()
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...
729
    // {
730
    //     return GridFieldConfig_RelationEditor::create();
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% 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...
731
    // }
732
733
    /**
734
     * Used in getCSMFields.
735
     *
736
     * @return GridField
737
     **/
738
    protected function getProductGroupsTable()
739
    {
740
        $gridField = GridField::create(
741
            'AlsoShowProducts',
742
            _t('ProductGroup.OTHER_PRODUCTS_SHOWN_IN_THIS_GROUP', 'Other products shown in this group ...'),
743
            $this->AlsoShowProducts(),
744
            GridFieldBasicPageRelationConfig::create()
745
        );
746
        //make sure edits are done in the right place ...
747
        return $gridField;
748
    }
749
750
    /*****************************************************
751
     *
752
     *
753
     *
754
     * PRODUCTS THAT BELONG WITH THIS PRODUCT GROUP
755
     *
756
     *
757
     *
758
     *****************************************************/
759
760
    /**
761
     * returns the inital (all) products, based on the all the eligible products
762
     * for the page.
763
     *
764
     * This is THE pivotal method that probably changes for classes that
765
     * extend ProductGroup as here you can determine what products or other buyables are shown.
766
     *
767
     * The return from this method will then be sorted to produce the final product list.
768
     *
769
     * There is no sort for the initial retrieval
770
     *
771
     * This method is public so that you can retrieve a list of products for a product group page.
772
     *
773
     * @param array | string $extraFilter          Additional SQL filters to apply to the Product retrieval
774
     * @param string         $alternativeFilterKey Alternative standard filter to be used.
775
     *
776
     * @return DataList
777
     **/
778
    public function currentInitialProducts($extraFilter = null, $alternativeFilterKey = '')
779
    {
780
781
        //INIT ALLPRODUCTS
782
        unset($this->allProducts);
783
        $className = $this->getBuyableClassName();
784
        $this->allProducts = $className::get();
785
786
        // GROUP FILTER (PRODUCTS FOR THIS GROUP)
787
        $this->allProducts = $this->getGroupFilter();
788
789
        // STANDARD FILTER (INCLUDES USER PREFERENCE)
790
        $filterStatement = $this->allowPurchaseWhereStatement();
791
        if ($filterStatement) {
792
            if (is_array($filterStatement)) {
793
                $this->allProducts = $this->allProducts->filter($filterStatement);
794
            } elseif (is_string($filterStatement)) {
795
                $this->allProducts = $this->allProducts->where($filterStatement);
796
            }
797
        }
798
        $this->allProducts = $this->getStandardFilter($alternativeFilterKey);
799
800
        // EXTRA FILTER (ON THE FLY FROM CONTROLLER)
801
        if (is_array($extraFilter) && count($extraFilter)) {
802
            $this->allProducts = $this->allProducts->filter($extraFilter);
803
        } elseif (is_string($extraFilter) && strlen($extraFilter) > 2) {
804
            $this->allProducts = $this->allProducts->where($extraFilter);
805
        }
806
807
        //JOINS
808
        $this->allProducts = $this->getGroupJoin();
809
810
        return $this->allProducts;
811
    }
812
813
    /**
814
     * this method can be used quickly current initial products
815
     * whenever you write:
816
     *  ```php
817
     *   currentInitialProducts->(null, $key)->map("ID", "ID")->toArray();
818
     *  ```
819
     * this is the better replacement.
820
     *
821
     * @param string $filterKey
822
     *
823
     * @return array
824
     */
825
    public function currentInitialProductsAsCachedArray($filterKey)
826
    {
827
        $cacheKey = 'CurrentInitialProductsArray'.$filterKey;
828
        if ($array = $this->retrieveObjectStore($cacheKey)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
829
            //do nothing
830
        } else {
831
            $array = $this->currentInitialProducts(null, $filterKey)->map('ID', 'ID')->toArray();
832
            $this->saveObjectStore($array, $cacheKey);
833
        }
834
835
        return $array;
836
    }
837
838
    /*****************************************************
839
     * DATALIST: adjusters
840
     * these are the methods you want to override in
841
     * any clases that extend ProductGroup
842
     *****************************************************/
843
844
    /**
845
     * Do products occur in more than one group.
846
     *
847
     * @return bool
848
     */
849
    protected function getProductsAlsoInOtherGroups()
850
    {
851
        return $this->EcomConfig()->ProductsAlsoInOtherGroups;
852
    }
853
854
    /**
855
     * Returns the class we are working with.
856
     *
857
     * @return string
858
     */
859
    protected function getBuyableClassName()
860
    {
861
        return EcommerceConfig::get('ProductGroup', 'base_buyable_class');
862
    }
863
864
    /**
865
     * @SEE: important notes at the top of this file / class
866
     *
867
     * IMPORTANT: Adjusts allProducts and returns it...
868
     *
869
     * @return DataList
870
     */
871
    protected function getGroupFilter()
872
    {
873
        $levelToShow = $this->MyLevelOfProductsToShow();
874
        $cacheKey = 'GroupFilter_'.abs(intval($levelToShow + 999));
875
        if ($groupFilter = $this->retrieveObjectStore($cacheKey)) {
876
            $this->allProducts = $this->allProducts->where($groupFilter);
877
        } else {
878
            $groupFilter = '';
879
            $productFilterArray = array();
880
            //special cases
881
            if ($levelToShow < 0) {
882
                //no produts but if LevelOfProductsToShow = -1 then show all
883
                $groupFilter = ' ('.$levelToShow.' = -1) ';
884
            } elseif ($levelToShow > 0) {
885
                $groupIDs = array($this->ID => $this->ID);
886
                $productFilterTemp = $this->getProductsToBeIncludedFromOtherGroups();
887
                $productFilterArray[$productFilterTemp] = $productFilterTemp;
888
                $childGroups = $this->ChildGroups($levelToShow);
889
                if ($childGroups && $childGroups->count()) {
890
                    foreach ($childGroups as $childGroup) {
891
                        $groupIDs[$childGroup->ID] = $childGroup->ID;
892
                        $productFilterTemp = $childGroup->getProductsToBeIncludedFromOtherGroups();
893
                        $productFilterArray[$productFilterTemp] = $productFilterTemp;
894
                    }
895
                }
896
                $groupFilter = ' ( "ParentID" IN ('.implode(',', $groupIDs).') ) '.implode($productFilterArray).' ';
897
            } else {
898
                //fall-back
899
                $groupFilter = '"ParentID" < 0';
900
            }
901
            $this->allProducts = $this->allProducts->where($groupFilter);
902
            $this->saveObjectStore($groupFilter, $cacheKey);
903
        }
904
905
        return $this->allProducts;
906
    }
907
908
    /**
909
     * If products are show in more than one group
910
     * Then this returns a where phrase for any products that are linked to this
911
     * product group.
912
     *
913
     * @return string
914
     */
915
    protected function getProductsToBeIncludedFromOtherGroups()
916
    {
917
        //TO DO: this should actually return
918
        //Product.ID = IN ARRAY(bla bla)
919
        $array = array();
920
        if ($this->getProductsAlsoInOtherGroups()) {
921
            $array = $this->AlsoShowProducts()->map('ID', 'ID')->toArray();
922
        }
923
        if (count($array)) {
924
            return " OR (\"Product\".\"ID\" IN (".implode(',', $array).')) ';
925
        }
926
927
        return '';
928
    }
929
930
    /**
931
     * @SEE: important notes at the top of this class / file for more information!
932
     *
933
     * IMPORTANT: Adjusts allProducts and returns it...
934
     *
935
     * @param string $alternativeFilterKey - filter key to be used... if none is specified then we use the current one.
936
     *
937
     * @return DataList
938
     */
939
    protected function getStandardFilter($alternativeFilterKey = '')
940
    {
941
        if ($alternativeFilterKey) {
942
            $filterKey = $alternativeFilterKey;
943
        } else {
944
            $filterKey = $this->getCurrentUserPreferences('FILTER');
945
        }
946
        $filter = $this->getUserSettingsOptionSQL('FILTER', $filterKey);
947
        if (is_array($filter)) {
948
            $this->allProducts = $this->allProducts->Filter($filter);
949
        } elseif (is_string($filter) && strlen($filter) > 2) {
950
            $this->allProducts = $this->allProducts->Where($filter);
951
        }
952
953
        return $this->allProducts;
954
    }
955
956
    /**
957
     * Join statement for the product groups.
958
     *
959
     * IMPORTANT: Adjusts allProducts and returns it...
960
     *
961
     * @return DataList
962
     */
963
    protected function getGroupJoin()
964
    {
965
        return $this->allProducts;
966
    }
967
968
    /**
969
     * Quick - dirty hack - filter to
970
     * only show relevant products.
971
     *
972
     * @param bool   $asArray
973
     * @param string $table
974
     */
975
    protected function allowPurchaseWhereStatement($asArray = true, $table = 'Product')
976
    {
977
        if ($this->EcomConfig()->OnlyShowProductsThatCanBePurchased) {
978
            if ($asArray) {
979
                $allowPurchaseWhereStatement = array('AllowPurchase' => 1);
980
            } else {
981
                $allowPurchaseWhereStatement = "\"$table\".\"AllowPurchase\" = 1  ";
982
            }
983
984
            return $allowPurchaseWhereStatement;
985
        }
986
    }
987
988
    /*****************************************************
989
     *
990
     *
991
     *
992
     *
993
     * FINAL PRODUCTS
994
     *
995
     *
996
     *
997
     *
998
     *****************************************************/
999
1000
    /**
1001
     * This is the dataList that contains all the products.
1002
     *
1003
     * @var DataList
1004
     */
1005
    protected $allProducts = null;
1006
1007
    /**
1008
     * a list of relevant buyables that can
1009
     * not be purchased and therefore should be excluded.
1010
     * Should be set to NULL to start with so we know if it has been
1011
     * set yet.
1012
     *
1013
     * @var null | Array (like so: array(1,2,4,5,99))
1014
     */
1015
    private $canNOTbePurchasedArray = null;
1016
1017
    /**
1018
     * a list of relevant buyables that can
1019
     * be purchased.  We keep this so that
1020
     * that we can save to session, etc... for future use.
1021
     * Should be set to NULL to start with so we know if it has been
1022
     * set yet.
1023
     *
1024
     * @var null | Array (like so: array(1,2,4,5,99))
1025
     */
1026
    protected $canBePurchasedArray = null;
1027
1028
    /**
1029
     * returns the total numer of products (before pagination).
1030
     *
1031
     * @return int
1032
     **/
1033
    public function RawCount()
1034
    {
1035
        return $this->rawCount ? $this->rawCount : 0;
1036
    }
1037
1038
    /**
1039
     * returns the total numer of products (before pagination).
1040
     *
1041
     * @return int
1042
     **/
1043
    public function TotalCount()
1044
    {
1045
        return $this->totalCount ? $this->totalCount : 0;
1046
    }
1047
1048
    /**
1049
     * this is used to save a list of sorted products
1050
     * so that you can find a previous and a next button, etc...
1051
     *
1052
     * @return array
1053
     */
1054
    public function getProductsThatCanBePurchasedArray()
1055
    {
1056
        return $this->canBePurchasedArray;
1057
    }
1058
1059
    /**
1060
     * Retrieve a set of products, based on the given parameters.
1061
     * This method is usually called by the various controller methods.
1062
     * The extraFilter helps you to select different products,
1063
     * depending on the method used in the controller.
1064
     *
1065
     * Furthermore, extrafilter can take all sorts of variables.
1066
     * This is basically setup like this so that in ProductGroup extensions you
1067
     * can setup all sorts of filters, while still using the ProductsShowable method.
1068
     *
1069
     * The extra filter can be supplied as array (e.g. array("ID" => 12) or array("ID" => array(12,13,45)))
1070
     * or as string. Arrays are used like this $productDataList->filter($array) and
1071
     * strings are used with the where commands $productDataList->where($string).
1072
     *
1073
     * @param array | string $extraFilter          Additional SQL filters to apply to the Product retrieval
1074
     * @param array | string $alternativeSort      Additional SQL for sorting
1075
     * @param string         $alternativeFilterKey alternative filter key to be used
1076
     *
1077
     * @return DataList | Null
1078
     */
1079
    public function ProductsShowable($extraFilter = null, $alternativeSort = null, $alternativeFilterKey = '')
1080
    {
1081
1082
        //get original products without sort
1083
        $this->allProducts = $this->currentInitialProducts($extraFilter, $alternativeFilterKey);
1084
1085
        //sort products
1086
        $this->allProducts = $this->currentFinalProducts($alternativeSort);
1087
1088
        return $this->allProducts;
1089
    }
1090
1091
    /**
1092
     * returns the final products, based on the all the eligile products
1093
     * for the page.
1094
     *
1095
     * In the process we also save a list of included products
1096
     * and we sort them.  We also keep a record of the total count.
1097
     *
1098
     * All of the 'current' methods are to support the currentFinalProducts Method.
1099
     *
1100
     * @TODO: cache data for faster access.
1101
     *
1102
     * @param array | string $alternativeSort = Alternative Sort String or array
1103
     *
1104
     * @return DataList
1105
     **/
1106
    protected function currentFinalProducts($alternativeSort = null)
1107
    {
1108
        if ($this->allProducts) {
1109
1110
            //limit to maximum number of products for speed's sake
1111
            $this->allProducts = $this->sortCurrentFinalProducts($alternativeSort);
1112
            $this->allProducts = $this->limitCurrentFinalProducts();
1113
            $this->allProducts = $this->removeExcludedProductsAndSaveIncludedProducts($this->allProducts);
1114
1115
            return $this->allProducts;
1116
        }
1117
    }
1118
1119
    /**
1120
     * returns the SORT part of the final selection of products.
1121
     *
1122
     * @return DataList (allProducts)
1123
     */
1124
    protected function sortCurrentFinalProducts($alternativeSort)
1125
    {
1126
        if ($alternativeSort) {
1127
            if ($this->IsIDarray($alternativeSort)) {
1128
                $sort = $this->createSortStatementFromIDArray($alternativeSort);
1129
            } else {
1130
                $sort = $alternativeSort;
1131
            }
1132
        } else {
1133
            $sort = $this->currentSortSQL();
1134
        }
1135
        $this->allProducts = $this->allProducts->Sort($sort);
1136
1137
        return $this->allProducts;
1138
    }
1139
1140
    /**
1141
     * is the variable provided is an array
1142
     * that can be used as a list of IDs?
1143
     *
1144
     * @param mixed
1145
     *
1146
     * @return bool
1147
     */
1148
    protected function IsIDarray($variable)
1149
    {
1150
        return $variable && is_array($variable) && count($variable) && intval(current($variable)) == current($variable);
1151
    }
1152
1153
    /**
1154
     * returns the SORT part of the final selection of products.
1155
     *
1156
     * @return string | Array
1157
     */
1158
    protected function currentSortSQL()
1159
    {
1160
        $sortKey = $this->getCurrentUserPreferences('SORT');
1161
1162
        return $this->getUserSettingsOptionSQL('SORT', $sortKey);
1163
    }
1164
1165
    /**
1166
     * creates a sort string from a list of ID arrays...
1167
     *
1168
     * @param array $IDarray - list of product IDs
1169
     *
1170
     * @return string
1171
     */
1172
    protected function createSortStatementFromIDArray($IDarray, $table = 'Product')
1173
    {
1174
        $ifStatement = 'CASE ';
1175
        $sortStatement = '';
1176
        $stage = $this->getStage();
1177
        $count = 0;
1178
        foreach ($IDarray as $productID) {
1179
            $ifStatement .= ' WHEN "'.$table.$stage."\".\"ID\" = $productID THEN $count";
1180
            ++$count;
1181
        }
1182
        $sortStatement = $ifStatement.' END';
1183
1184
        return $sortStatement;
1185
    }
1186
1187
    /**
1188
     * limits the products to a maximum number (for speed's sake).
1189
     *
1190
     * @return DataList (this->allProducts adjusted!)
1191
     */
1192
    protected function limitCurrentFinalProducts()
1193
    {
1194
        $this->rawCount = $this->allProducts->count();
1195
        $max = EcommerceConfig::get('ProductGroup', 'maximum_number_of_products_to_list');
1196
        if ($this->rawCount > $max) {
1197
            $this->allProducts = $this->allProducts->limit($max);
1198
            $this->totalCount = $max;
1199
        } else {
1200
            $this->totalCount = $this->rawCount;
1201
        }
1202
1203
        return $this->allProducts;
1204
    }
1205
1206
    /**
1207
     * Excluded products that can not be purchased
1208
     * We all make a record of all the products that are in the current list
1209
     * For efficiency sake, we do both these things at the same time.
1210
     * IMPORTANT: Adjusts allProducts and returns it...
1211
     *
1212
     * @todo: cache data per user ....
1213
     *
1214
     * @return DataList
1215
     */
1216
    protected function removeExcludedProductsAndSaveIncludedProducts()
1217
    {
1218
        if (is_array($this->canBePurchasedArray) && is_array($this->canNOTbePurchasedArray)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
1219
            //already done!
1220
        } else {
1221
            $this->canNOTbePurchasedArray = array();
1222
            $this->canBePurchasedArray = array();
1223
            if ($this->config()->get('actively_check_for_can_purchase')) {
1224
                foreach ($this->allProducts as $buyable) {
1225
                    if ($buyable->canPurchase()) {
1226
                        $this->canBePurchasedArray[$buyable->ID] = $buyable->ID;
1227
                    } else {
1228
                        $this->canNOTbePurchasedArray[$buyable->ID] = $buyable->ID;
1229
                    }
1230
                }
1231
            } else {
1232
                if ($this->rawCount > 0) {
1233
                    $this->canBePurchasedArray = $this->allProducts->map('ID', 'ID')->toArray();
1234
                } else {
1235
                    $this->canBePurchasedArray = array();
1236
                }
1237
            }
1238
            if (count($this->canNOTbePurchasedArray)) {
1239
                $this->allProducts = $this->allProducts->Exclude(array('ID' => $this->canNOTbePurchasedArray));
1240
            }
1241
        }
1242
1243
        return $this->allProducts;
1244
    }
1245
1246
    /*****************************************************
1247
     * Children and Parents
1248
     *****************************************************/
1249
1250
    /**
1251
     * Returns children ProductGroup pages of this group.
1252
     *
1253
     * @param int            $maxRecursiveLevel  - maximum depth , e.g. 1 = one level down - so no Child Groups are returned...
1254
     * @param string | Array $filter             - additional filter to be added
1255
     * @param int            $numberOfRecursions - current level of depth
1256
     *
1257
     * @return ArrayList (ProductGroups)
1258
     */
1259
    public function ChildGroups($maxRecursiveLevel, $filter = null, $numberOfRecursions = 0)
1260
    {
1261
        $arrayList = ArrayList::create();
1262
        ++$numberOfRecursions;
1263
        if ($numberOfRecursions < $maxRecursiveLevel) {
1264
            if ($filter && is_string($filter)) {
1265
                $filterWithAND = " AND $filter";
1266
                $where = "\"ParentID\" = '$this->ID' $filterWithAND";
1267
                $children = ProductGroup::get()->where($where);
1268
            } elseif (is_array($filter) && count($filter)) {
1269
                $filter = $filter + array('ParentID' => $this->ID);
1270
                $children = ProductGroup::get()->filter($filter);
1271
            } else {
1272
                $children = ProductGroup::get()->filter(array('ParentID' => $this->ID));
1273
            }
1274
1275
            if ($children->count()) {
1276
                foreach ($children as $child) {
1277
                    $arrayList->push($child);
1278
                    $arrayList->merge($child->ChildGroups($maxRecursiveLevel, $filter, $numberOfRecursions));
1279
                }
1280
            }
1281
        }
1282
        if (!$arrayList instanceof ArrayList) {
1283
            user_error('We expect an array list as output');
1284
        }
1285
1286
        return $arrayList;
1287
    }
1288
1289
    /**
1290
     * Deprecated method.
1291
     */
1292
    public function ChildGroupsBackup($maxRecursiveLevel, $filter = '')
1293
    {
1294
        Deprecation::notice('3.1', 'No longer in use');
1295
        if ($maxRecursiveLevel > 24) {
1296
            $maxRecursiveLevel = 24;
1297
        }
1298
1299
        $stage = $this->getStage();
1300
        $select = 'P1.ID as ID1 ';
1301
        $from = "ProductGroup$stage as P1 ";
1302
        $join = " INNER JOIN SiteTree$stage AS S1 ON P1.ID = S1.ID";
1303
        $where = '1 = 1';
1304
        $ids = array(-1);
1305
        for ($i = 1; $i < $maxRecursiveLevel; ++$i) {
1306
            $j = $i + 1;
1307
            $select .= ", P$j.ID AS ID$j, S$j.ParentID";
1308
            $join .= "
1309
                LEFT JOIN ProductGroup$stage AS P$j ON P$j.ID = S$i.ParentID
1310
                LEFT JOIN SiteTree$stage AS S$j ON P$j.ID = S$j.ID
1311
            ";
1312
        }
1313
        $rows = DB::Query(' SELECT '.$select.' FROM '.$from.$join.' WHERE '.$where);
1314
        if ($rows) {
1315
            foreach ($rows as $row) {
1316
                for ($i = 1; $i < $maxRecursiveLevel; ++$i) {
1317
                    if ($row['ID'.$i]) {
1318
                        $ids[$row['ID'.$i]] = $row['ID'.$i];
1319
                    }
1320
                }
1321
            }
1322
        }
1323
1324
        return ProductGroup::get()->where("\"ProductGroup$stage\".\"ID\" IN (".implode(',', $ids).')'.$filterWithAND);
1325
    }
1326
1327
    /**
1328
     * returns the parent page, but only if it is an instance of Product Group.
1329
     *
1330
     * @return DataObject | Null (ProductGroup)
1331
     **/
1332
    public function ParentGroup()
1333
    {
1334
        if ($this->ParentID) {
1335
            return ProductGroup::get()->byID($this->ParentID);
1336
        }
1337
    }
1338
1339
    /*****************************************************
1340
     * Other Stuff
1341
     *****************************************************/
1342
1343
    /**
1344
     * Recursively generate a product menu.
1345
     *
1346
     * @param string $filter
1347
     *
1348
     * @return ArrayList (ProductGroups)
1349
     */
1350
    public function GroupsMenu($filter = 'ShowInMenus = 1')
1351
    {
1352
        if ($parent = $this->ParentGroup()) {
1353
            return is_a($parent, Object::getCustomClass('ProductGroup')) ? $parent->GroupsMenu() : $this->ChildGroups($filter);
1354
        } else {
1355
            return $this->ChildGroups($filter);
1356
        }
1357
    }
1358
1359
    /**
1360
     * returns a "BestAvailable" image if the current one is not available
1361
     * In some cases this is appropriate and in some cases this is not.
1362
     * For example, consider the following setup
1363
     * - product A with three variations
1364
     * - Product A has an image, but the variations have no images
1365
     * With this scenario, you want to show ONLY the product image
1366
     * on the product page, but if one of the variations is added to the
1367
     * cart, then you want to show the product image.
1368
     * This can be achieved bu using the BestAvailable image.
1369
     *
1370
     * @return Image | Null
1371
     */
1372
    public function BestAvailableImage()
1373
    {
1374
        $image = $this->Image();
1375
        if ($image && $image->exists() && file_exists($image->getFullPath())) {
1376
            return $image;
1377
        } elseif ($parent = $this->ParentGroup()) {
1378
            return $parent->BestAvailableImage();
1379
        }
1380
    }
1381
1382
    /*****************************************************
1383
     * Other related products
1384
     *****************************************************/
1385
1386
    /**
1387
     * returns a list of Product Groups that have the products for
1388
     * the CURRENT product group listed as part of their AlsoShowProducts list.
1389
     *
1390
     * EXAMPLE:
1391
     * You can use the AlsoShowProducts to list products by Brand.
1392
     * In general, they are listed under type product groups (e.g. socks, sweaters, t-shirts),
1393
     * and you create a list of separate ProductGroups (brands) that do not have ANY products as children,
1394
     * but link to products using the AlsoShowProducts many_many relation.
1395
     *
1396
     * With the method below you can work out a list of brands that apply to the
1397
     * current product group (e.g. socks come in three brands - namely A, B and C)
1398
     *
1399
     * @return DataList
1400
     */
1401
    public function ProductGroupsFromAlsoShowProducts()
1402
    {
1403
        $parentIDs = array();
1404
        //we need to add the last array to make sure we have some products...
1405
        $myProductsArray = $this->currentInitialProductsAsCachedArray($this->getMyUserPreferencesDefault('FILTER'));
1406
        $rows = array();
1407
        if (count($myProductsArray)) {
1408
            $rows = DB::query('
1409
                SELECT "ProductGroupID"
1410
                FROM "Product_ProductGroups"
1411
                WHERE "ProductID" IN ('.implode(',', $myProductsArray).')
1412
                GROUP BY "ProductGroupID";
1413
            ');
1414
        }
1415
        foreach ($rows as $row) {
1416
            $parentIDs[$row['ProductGroupID']] = $row['ProductGroupID'];
1417
        }
1418
        //just in case
1419
        unset($parentIDs[$this->ID]);
1420
        if (!count($parentIDs)) {
1421
            $parentIDs = array(0 => 0);
1422
        }
1423
1424
        return ProductGroup::get()->filter(array('ID' => $parentIDs, 'ShowInSearch' => 1));
1425
    }
1426
1427
    /**
1428
     * This is the inverse of ProductGroupsFromAlsoShowProducts
1429
     * That is, it list the product groups that a product is primarily listed under (exact parents only)
1430
     * from a "AlsoShow" product List.
1431
     *
1432
     * @return DataList
1433
     */
1434
    public function ProductGroupsFromAlsoShowProductsInverse()
1435
    {
1436
        $alsoShowProductsArray = $this->AlsoShowProducts()
1437
            ->filter($this->getUserSettingsOptionSQL('FILTER', $this->getMyUserPreferencesDefault('FILTER')))
1438
            ->map('ID', 'ID')->toArray();
1439
        $alsoShowProductsArray[0] = 0;
1440
        $parentIDs = Product::get()->filter(array('ID' => $alsoShowProductsArray))->map('ParentID', 'ParentID')->toArray();
1441
        //just in case
1442
        unset($parentIDs[$this->ID]);
1443
        if (! count($parentIDs)) {
1444
            $parentIDs = array(0 => 0);
1445
        }
1446
1447
        return ProductGroup::get()->filter(array('ID' => $parentIDs, 'ShowInMenus' => 1));
1448
    }
1449
1450
    /**
1451
     * given the products for this page,
1452
     * retrieve the parent groups excluding the current one.
1453
     *
1454
     * @return DataList
1455
     */
1456
    public function ProductGroupsParentGroups()
1457
    {
1458
        $arrayOfIDs = $this->currentInitialProductsAsCachedArray($this->getMyUserPreferencesDefault('FILTER')) + array(0 => 0);
1459
        $parentIDs = Product::get()->filter(array('ID' => $arrayOfIDs))->map('ParentID', 'ParentID')->toArray();
1460
        //just in case
1461
        unset($parentIDs[$this->ID]);
1462
        if (! count($parentIDs)) {
1463
            $parentIDs = array(0 => 0);
1464
        }
1465
1466
        return ProductGroup::get()->filter(array('ID' => $parentIDs, 'ShowInSearch' => 1));
1467
    }
1468
1469
    /**
1470
     * returns stage as "" or "_Live".
1471
     *
1472
     * @return string
1473
     */
1474
    protected function getStage()
1475
    {
1476
        $stage = '';
1477
        if (Versioned::current_stage() == 'Live') {
1478
            $stage = '_Live';
1479
        }
1480
1481
        return $stage;
1482
    }
1483
1484
    /*****************************************************
1485
     * STANDARD SS METHODS
1486
     *****************************************************/
1487
1488
    /**
1489
     * tells us if the current page is part of e-commerce.
1490
     *
1491
     * @return bool
1492
     */
1493
    public function IsEcommercePage()
1494
    {
1495
        return true;
1496
    }
1497
1498
    public function onAfterWrite()
1499
    {
1500
        parent::onAfterWrite();
1501
1502
        if ($this->ImageID) {
1503
            if ($normalImage = Image::get()->exclude(array('ClassName' => 'Product_Image'))->byID($this->ImageID)) {
1504
                $normalImage = $normalImage->newClassInstance('Product_Image');
1505
                $normalImage->write();
1506
            }
1507
        }
1508
    }
1509
1510
    function requireDefaultRecords()
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1511
    {
1512
        parent::requireDefaultRecords();
1513
        $rows = DB::query('SELECT URLSegment, COUNT(ID) AS C FROM SiteTree GROUP BY URLSegment HAVING COUNT(ID) > 1; ');
1514
        foreach($rows as $row) {
1515
            DB::alteration_message($row['URLSegment'].' '.$row['C']);
1516
            $checkForDuplicatesURLSegments = ProductGroup::get()
1517
                ->filter(array('URLSegment' => $this->URLSegment))
1518
            foreach($checkForDuplicatesURLSegments as $productGroup) {
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_FOREACH
Loading history...
1519
                $oldURLSegment = $productGroup->URLSegment;
1520
                DB::alteration_message('Correcting URLSegment for '.$productGroup->Title.' with ID: '.$productGroup->ID, 'deleted');
1521
                $productGroup->writeToStage('Stage');
1522
                $productGroup->publish('Stage', 'Live');
1523
                $newURLSegment = $productGroup->URLSegment;
1524
                DB::alteration_message(' ... from '.$oldURLSegment.' to '.$newURLSegment, 'created');
1525
            }
1526
        }
1527
    }
1528
1529
    /*****************************************************
1530
     * CACHING
1531
     *****************************************************/
1532
    /**
1533
     *
1534
     * @return bool
1535
     */
1536
    public function AllowCaching()
1537
    {
1538
        return $this->allowCaching;
1539
    }
1540
1541
    /**
1542
     * keeps a cache of the common caching key element
1543
     * @var string
1544
     */
1545
    private static $_product_group_cache_key_cache = null;
1546
1547
    /**
1548
     *
1549
     * @param string $name
1550
     * @param string $filterKey
1551
     *
1552
     * @return string
1553
     */
1554
    public function cacheKey($cacheKey)
1555
    {
1556
        $cacheKey = $cacheKey.'_'.$this->ID;
1557
        if (self::$_product_group_cache_key_cache === null) {
1558
            self::$_product_group_cache_key_cache = "_PR_"
1559
                .strtotime(Product::get()->max('LastEdited')). "_"
1560
                .Product::get()->count();
1561
            self::$_product_group_cache_key_cache .= "PG_"
1562
                .strtotime(ProductGroup::get()->max('LastEdited')). "_"
1563
                .ProductGroup::get()->count();
1564
            if (class_exists('ProductVariation')) {
1565
                self::$_product_group_cache_key_cache .= "PV_"
1566
                  .strtotime(ProductVariation::get()->max('LastEdited')). "_"
1567
                  .ProductVariation::get()->count();
1568
            }
1569
        }
1570
        $cacheKey .= self::$_product_group_cache_key_cache;
1571
1572
        return $cacheKey;
1573
    }
1574
1575
    /**
1576
     * @var Zend_Cache_Core
1577
     */
1578
    protected $silverstripeCoreCache = null;
1579
1580
    /**
1581
     * Set the cache object to use when storing / retrieving partial cache blocks.
1582
     *
1583
     * @param Zend_Cache_Core $silverstripeCoreCache
1584
     */
1585
    public function setSilverstripeCoreCache($silverstripeCoreCache)
1586
    {
1587
        $this->silverstripeCoreCache = $silverstripeCoreCache;
1588
    }
1589
1590
    /**
1591
     * Get the cache object to use when storing / retrieving stuff in the Silverstripe Cache
1592
     *
1593
     * @return Zend_Cache_Core
1594
     */
1595
    protected function getSilverstripeCoreCache()
1596
    {
1597
        return $this->silverstripeCoreCache ? $this->silverstripeCoreCache : SS_Cache::factory('EcomPG');
1598
    }
1599
1600
    /**
1601
     * saving an object to the.
1602
     *
1603
     * @param string $cacheKey
1604
     *
1605
     * @return mixed
1606
     */
1607
    protected function retrieveObjectStore($cacheKey)
1608
    {
1609
        $cacheKey = $this->cacheKey($cacheKey);
1610
        if ($this->AllowCaching()) {
1611
            $cache = $this->getSilverstripeCoreCache();
1612
            $data = $cache->load($cacheKey);
1613
            if (!$data) {
1614
                return;
1615
            }
1616
            if (! $cache->getOption('automatic_serialization')) {
1617
                $data = @unserialize($data);
1618
            }
1619
            return $data;
1620
        }
1621
1622
        return;
1623
    }
1624
1625
    /**
1626
     * returns true when the data is saved...
1627
     *
1628
     * @param mixed  $data
1629
     * @param string $cacheKey - key under which the data is saved...
1630
     *
1631
     * @return bool
1632
     */
1633
    protected function saveObjectStore($data, $cacheKey)
1634
    {
1635
        $cacheKey = $this->cacheKey($cacheKey);
1636
        if ($this->AllowCaching()) {
1637
            $cache = $this->getSilverstripeCoreCache();
1638
            if (! $cache->getOption('automatic_serialization')) {
1639
                $data = serialize($data);
1640
            }
1641
            $cache->save($data, $cacheKey);
1642
            return true;
1643
        }
1644
1645
        return false;
1646
    }
1647
1648
    public function SearchResultsSessionVariable($isForGroups = false)
1649
    {
1650
        $idString = '_'.$this->ID;
1651
        if ($isForGroups) {
1652
            return Config::inst()->get('ProductSearchForm', 'product_session_variable').$idString;
1653
        } else {
1654
            return Config::inst()->get('ProductSearchForm', 'product_group_session_variable').$idString;
1655
        }
1656
    }
1657
1658
    /**
1659
     * cache for result array.
1660
     *
1661
     * @var array
1662
     */
1663
    private static $_result_array = array();
1664
1665
    /**
1666
     * @return array
1667
     */
1668
    public function searchResultsArrayFromSession()
1669
    {
1670
        if (! isset(self::$_result_array[$this->ID]) || self::$_result_array[$this->ID] === null) {
1671
            self::$_result_array[$this->ID] = explode(',', Session::get($this->SearchResultsSessionVariable(false)));
1672
        }
1673
        if (! is_array(self::$_result_array[$this->ID]) || ! count(self::$_result_array[$this->ID])) {
1674
            self::$_result_array[$this->ID] = array(0 => 0);
1675
        }
1676
1677
        return self::$_result_array[$this->ID];
1678
    }
1679
1680
    public function getNumberOfProducts()
1681
    {
1682
        return Product::get()->filter(array('ParentID' => $this->ID))->count();
1683
    }
1684
}
1685
1686
class ProductGroup_Controller extends Page_Controller
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
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...
1687
{
1688
    /**
1689
     * standard SS variable.
1690
     *
1691
     * @var array
1692
     */
1693
    private static $allowed_actions = array(
1694
        'debug' => 'ADMIN',
1695
        'filterforgroup' => true,
1696
        'ProductSearchForm' => true,
1697
        'searchresults' => true,
1698
        'resetfilter' => true,
1699
    );
1700
1701
    /**
1702
     * The original Title of this page before filters, etc...
1703
     *
1704
     * @var string
1705
     */
1706
    protected $originalTitle = '';
1707
1708
    /**
1709
     * list of products that are going to be shown.
1710
     *
1711
     * @var DataList
1712
     */
1713
    protected $products = null;
1714
1715
    /**
1716
     * Show all products on one page?
1717
     *
1718
     * @var bool
1719
     */
1720
    protected $showFullList = false;
1721
1722
    /**
1723
     * The group filter that is applied to this page.
1724
     *
1725
     * @var ProductGroup
1726
     */
1727
    protected $filterForGroupObject = null;
1728
1729
    /**
1730
     * Is this a product search?
1731
     *
1732
     * @var bool
1733
     */
1734
    protected $isSearchResults = false;
1735
1736
    /**
1737
     * standard SS method.
1738
     */
1739
    public function init()
1740
    {
1741
        parent::init();
1742
        $this->originalTitle = $this->Title;
1743
        Requirements::themedCSS('ProductGroup', 'ecommerce');
1744
        Requirements::themedCSS('ProductGroupPopUp', 'ecommerce');
1745
        Requirements::javascript('ecommerce/javascript/EcomProducts.js');
1746
        //we save data from get variables...
1747
        $this->saveUserPreferences();
1748
    }
1749
1750
    /****************************************************
1751
     *  ACTIONS
1752
    /****************************************************/
1753
1754
    /**
1755
     * standard selection of products.
1756
     */
1757
    public function index()
1758
    {
1759
        //set the filter and the sort...
1760
        $this->addSecondaryTitle();
1761
        $this->products = $this->paginateList($this->ProductsShowable(null));
1762
        if ($this->returnAjaxifiedProductList()) {
1763
            return $this->renderWith('AjaxProductList');
1764
        }
1765
        return array();
1766
    }
1767
1768
    /**
1769
     * cross filter with another product group..
1770
     *
1771
     * e.g. socks (current product group) for brand A or B (the secondary product group)
1772
     *
1773
     * @param HTTPRequest
1774
     */
1775
    public function filterforgroup($request)
1776
    {
1777
        $this->resetfilter();
1778
        $otherGroupURLSegment = Convert::raw2sql($request->param('ID'));
1779
        $arrayOfIDs = array(0 => 0);
1780
        if ($otherGroupURLSegment) {
1781
            $otherProductGroup = ProductGroup::get()->filter(array('URLSegment' => $otherGroupURLSegment))->first();
1782
            if ($otherProductGroup) {
1783
                $this->filterForGroupObject = $otherProductGroup;
1784
                $arrayOfIDs = $otherProductGroup->currentInitialProductsAsCachedArray($this->getMyUserPreferencesDefault('FILTER'));
1785
            }
1786
        }
1787
        $this->addSecondaryTitle();
1788
        $this->products = $this->paginateList($this->ProductsShowable(array('ID' => $arrayOfIDs)));
1789
        if ($this->returnAjaxifiedProductList()) {
1790
            return $this->renderWith('AjaxProductList');
1791
        }
1792
1793
        return array();
1794
    }
1795
1796
    /**
1797
     * get the search results.
1798
     *
1799
     * @param HTTPRequest
1800
     */
1801
    public function searchresults($request)
1802
    {
1803
        $this->resetfilter();
1804
        $this->isSearchResults = true;
1805
        //reset filter and sort
1806
        $resultArray = $this->searchResultsArrayFromSession();
1807
        if (!$resultArray || !count($resultArray)) {
1808
            $resultArray = array(0 => 0);
1809
        }
1810
        $defaultKeySort = $this->getMyUserPreferencesDefault('SORT');
1811
        $myKeySort = $this->getCurrentUserPreferences('SORT');
1812
        $searchArray = null;
1813
        if ($defaultKeySort == $myKeySort) {
1814
            $searchArray = $resultArray;
1815
        }
1816
        $this->addSecondaryTitle();
1817
        $this->products = $this->paginateList($this->ProductsShowable(array('ID' => $resultArray), $searchArray));
1818
1819
        return array();
1820
    }
1821
1822
    /**
1823
     * resets the filter only.
1824
     */
1825
    public function resetfilter()
1826
    {
1827
        $defaultKey = $this->getMyUserPreferencesDefault('FILTER');
1828
        $filterGetVariable = $this->getSortFilterDisplayNames('FILTER', 'getVariable');
1829
        $this->saveUserPreferences(
1830
            array(
1831
                $filterGetVariable => $defaultKey,
1832
            )
1833
        );
1834
1835
        return array();
1836
    }
1837
1838
    /****************************************************
1839
     *  TEMPLATE METHODS PRODUCTS
1840
    /****************************************************/
1841
1842
    /**
1843
     * Return the products for this group.
1844
     * This is the call that is made from the template...
1845
     * The actual final products being shown.
1846
     *
1847
     * @return PaginatedList
1848
     **/
1849
    public function Products()
1850
    {
1851
        //IMPORTANT!
1852
        //two universal actions!
1853
        $this->addSecondaryTitle();
1854
        $this->cachingRelatedJavascript();
1855
1856
        //save products to session for later use
1857
        $stringOfIDs = '';
1858
        $array = $this->getProductsThatCanBePurchasedArray();
1859
        if (is_array($array)) {
1860
            $stringOfIDs = implode(',', $array);
1861
        }
1862
        //save list for future use
1863
        Session::set(EcommerceConfig::get('ProductGroup', 'session_name_for_product_array'), $stringOfIDs);
1864
1865
        return $this->products;
1866
    }
1867
1868
    /**
1869
     * you can overload this function of ProductGroup Extensions.
1870
     *
1871
     * @return bool
1872
     */
1873
    protected function returnAjaxifiedProductList()
1874
    {
1875
        return Director::is_ajax() ? true : false;
1876
    }
1877
1878
    /**
1879
     * is the product list cache-able?
1880
     *
1881
     * @return bool
1882
     */
1883
    public function ProductGroupListAreCacheable()
1884
    {
1885
        if ($this->productListsHTMLCanBeCached()) {
1886
            //exception 1
1887
            if ($this->IsSearchResults()) {
1888
                return false;
1889
            }
1890
            //exception 2
1891
            $currentOrder = ShoppingCart::current_order();
1892
            if ($currentOrder->getHasAlternativeCurrency()) {
1893
                return false;
1894
            }
1895
            //can be cached...
1896
            return true;
1897
        }
1898
1899
        return false;
1900
    }
1901
1902
    /**
1903
     * is the product list ajaxified.
1904
     *
1905
     * @return bool
1906
     */
1907
    public function ProductGroupListAreAjaxified()
1908
    {
1909
        return $this->IsSearchResults() ? false : true;
1910
    }
1911
1912
    /**
1913
     * Unique caching key for the product list...
1914
     *
1915
     * @return string | Null
1916
     */
1917
    public function ProductGroupListCachingKey()
1918
    {
1919
        if ($this->ProductGroupListAreCacheable()) {
1920
            $displayKey = $this->getCurrentUserPreferences('DISPLAY');
1921
            $filterKey = $this->getCurrentUserPreferences('FILTER');
1922
            $filterForGroupKey = $this->filterForGroupObject ? $this->filterForGroupObject->ID : 0;
1923
            $sortKey = $this->getCurrentUserPreferences('SORT');
1924
            $pageStart = isset($_GET['start']) ? intval($_GET['start']) : 0;
1925
            $isFullList = $this->IsShowFullList() ? 'Y' : 'N';
1926
1927
            $this->cacheKey(
1928
                implode(
1929
                    '_',
1930
                    array(
1931
                        $displayKey,
1932
                        $filterKey,
1933
                        $filterForGroupKey,
1934
                        $sortKey,
1935
                        $pageStart,
1936
                        $isFullList,
1937
                    )
1938
                )
1939
            );
1940
        }
1941
1942
        return;
1943
    }
1944
1945
    /**
1946
     * adds Javascript to the page to make it work when products are cached.
1947
     */
1948
    public function CachingRelatedJavascript()
1949
    {
1950
        if ($this->ProductGroupListAreAjaxified()) {
1951
            Requirements::customScript("
1952
                    if(typeof EcomCartOptions === 'undefined') {
1953
                        var EcomCartOptions = {};
1954
                    }
1955
                    EcomCartOptions.ajaxifyProductList = true;
1956
                    EcomCartOptions.ajaxifiedListHolderSelector = '#".$this->AjaxDefinitions()->ProductListHolderID()."';
1957
                    EcomCartOptions.ajaxifiedListAdjusterSelectors = '.".$this->AjaxDefinitions()->ProductListAjaxifiedLinkClassName()."';
1958
                    EcomCartOptions.hiddenPageTitleID = '#".$this->AjaxDefinitions()->HiddenPageTitleID()."';
1959
                ",
1960
                'cachingRelatedJavascript_AJAXlist'
1961
            );
1962
        } else {
1963
            Requirements::customScript("
1964
                    if(typeof EcomCartOptions === 'undefined') {
1965
                        var EcomCartOptions = {};
1966
                    }
1967
                    EcomCartOptions.ajaxifyProductList = false;
1968
                ",
1969
                'cachingRelatedJavascript_AJAXlist'
1970
            );
1971
        }
1972
        $currentOrder = ShoppingCart::current_order();
1973
        if ($currentOrder->TotalItems(true)) {
1974
            $responseClass = EcommerceConfig::get('ShoppingCart', 'response_class');
1975
            $obj = new $responseClass();
1976
            $obj->setIncludeHeaders(false);
1977
            $json = $obj->ReturnCartData();
1978
            Requirements::customScript("
1979
                    if(typeof EcomCartOptions === 'undefined') {
1980
                        var EcomCartOptions = {};
1981
                    }
1982
                    EcomCartOptions.initialData= ".$json.";
1983
                ",
1984
                'cachingRelatedJavascript_JSON'
1985
            );
1986
        }
1987
    }
1988
1989
    /**
1990
     * you can overload this function of ProductGroup Extensions.
1991
     *
1992
     * @return bool
1993
     */
1994
    protected function productListsHTMLCanBeCached()
1995
    {
1996
        return Config::inst()->get('ProductGroup', 'actively_check_for_can_purchase') ? false : true;
1997
    }
1998
1999
    /*****************************************************
2000
     * DATALIST: totals, number per page, etc..
2001
     *****************************************************/
2002
2003
    /**
2004
     * returns the total numer of products (before pagination).
2005
     *
2006
     * @return bool
2007
     **/
2008
    public function TotalCountGreaterThanOne($greaterThan = 1)
2009
    {
2010
        return $this->TotalCount() > $greaterThan;
2011
    }
2012
2013
    /**
2014
     * have the ProductsShowable been limited.
2015
     *
2016
     * @return bool
2017
     **/
2018
    public function TotalCountGreaterThanMax()
2019
    {
2020
        return $this->RawCount() >  $this->TotalCount();
2021
    }
2022
2023
    /****************************************************
2024
     *  TEMPLATE METHODS MENUS AND SIDEBARS
2025
    /****************************************************/
2026
2027
    /**
2028
     * title without additions.
2029
     *
2030
     * @return string
2031
     */
2032
    public function OriginalTitle()
2033
    {
2034
        return $this->originalTitle;
2035
    }
2036
    /**
2037
     * This method can be extended to show products in the side bar.
2038
     */
2039
    public function SidebarProducts()
2040
    {
2041
        return;
2042
    }
2043
2044
    /**
2045
     * returns child product groups for use in
2046
     * 'in this section'. For example the vegetable Product Group
2047
     * May have listed here: Carrot, Cabbage, etc...
2048
     *
2049
     * @return ArrayList (ProductGroups)
2050
     */
2051
    public function MenuChildGroups()
2052
    {
2053
        return $this->ChildGroups(2, '"ShowInMenus" = 1');
2054
    }
2055
2056
    /**
2057
     * After a search is conducted you may end up with a bunch
2058
     * of recommended product groups. They will be returned here...
2059
     * We sort the list in the order that it is provided.
2060
     *
2061
     * @return DataList | Null (ProductGroups)
2062
     */
2063
    public function SearchResultsChildGroups()
2064
    {
2065
        $groupArray = explode(',', Session::get($this->SearchResultsSessionVariable($isForGroup = true)));
2066
        if (is_array($groupArray) && count($groupArray)) {
2067
            $sortStatement = $this->createSortStatementFromIDArray($groupArray, 'ProductGroup');
2068
2069
            return ProductGroup::get()->filter(array('ID' => $groupArray, 'ShowInSearch' => 1))->sort($sortStatement);
2070
        }
2071
2072
        return;
2073
    }
2074
2075
    /****************************************************
2076
     *  Search Form Related controllers
2077
    /****************************************************/
2078
2079
    /**
2080
     * returns a search form to search current products.
2081
     *
2082
     * @return ProductSearchForm object
2083
     */
2084
    public function ProductSearchForm()
2085
    {
2086
        $onlySearchTitle = $this->originalTitle;
2087
        if ($this->dataRecord instanceof ProductGroupSearchPage) {
2088
            if ($this->HasSearchResults()) {
2089
                $onlySearchTitle = 'Last Search Results';
2090
            }
2091
        }
2092
        $form = ProductSearchForm::create(
2093
            $this,
2094
            'ProductSearchForm',
2095
            $onlySearchTitle,
2096
            $this->currentInitialProducts(null, $this->getMyUserPreferencesDefault('FILTER'))
2097
        );
2098
        $filterGetVariable = $this->getSortFilterDisplayNames('FILTER', 'getVariable');
2099
        $sortGetVariable = $this->getSortFilterDisplayNames('SORT', 'getVariable');
2100
        $additionalGetParameters = $filterGetVariable.'='.$this->getMyUserPreferencesDefault('FILTER').'&'.
2101
                                   $sortGetVariable.'='.$this->getMyUserPreferencesDefault('SORT');
2102
        $form->setAdditionalGetParameters($additionalGetParameters);
2103
2104
        return $form;
2105
    }
2106
2107
    /**
2108
     * Does this page have any search results?
2109
     * If search was carried out without returns
2110
     * then it returns zero (false).
2111
     *
2112
     * @return int | false
2113
     */
2114
    public function HasSearchResults()
2115
    {
2116
        $resultArray = $this->searchResultsArrayFromSession();
2117
        if ($resultArray) {
2118
            $count = count($resultArray) - 1;
2119
2120
            return $count ? $count : 0;
2121
        }
2122
2123
        return 0;
2124
    }
2125
2126
    /**
2127
     * Should the product search form be shown immediately?
2128
     *
2129
     * @return bool
2130
     */
2131
    public function ShowSearchFormImmediately()
2132
    {
2133
        if ($this->IsSearchResults()) {
2134
            return true;
2135
        }
2136
        if ((!$this->products) || ($this->products && $this->products->count())) {
2137
            return false;
2138
        }
2139
2140
        return true;
2141
    }
2142
2143
    /**
2144
     * Show a search form on this page?
2145
     *
2146
     * @return bool
2147
     */
2148
    public function ShowSearchFormAtAll()
2149
    {
2150
        return true;
2151
    }
2152
2153
    /**
2154
     * Is the current page a display of search results.
2155
     *
2156
     * This does not mean that something is actively being search for,
2157
     * it could also be just "showing the search results"
2158
     *
2159
     * @return bool
2160
     */
2161
    public function IsSearchResults()
2162
    {
2163
        return $this->isSearchResults;
2164
    }
2165
2166
    /**
2167
     * Is there something actively being searched for?
2168
     *
2169
     * This is different from IsSearchResults.
2170
     *
2171
     * @return bool
2172
     */
2173
    public function ActiveSearchTerm()
2174
    {
2175
        $data = Session::get(Config::inst()->get('ProductSearchForm', 'form_data_session_variable'));
2176
        if (!empty($data['Keyword'])) {
2177
            return $this->IsSearchResults();
2178
        }
2179
    }
2180
2181
    /****************************************************
2182
     *  Filter / Sort / Display related controllers
2183
    /****************************************************/
2184
2185
    /**
2186
     * Do we show all products on one page?
2187
     *
2188
     * @return bool
2189
     */
2190
    public function ShowFiltersAndDisplayLinks()
2191
    {
2192
        if ($this->TotalCountGreaterThanOne()) {
2193
            if ($this->HasFilters()) {
2194
                return true;
2195
            }
2196
            if ($this->DisplayLinks()) {
2197
                return true;
2198
            }
2199
        }
2200
2201
        return false;
2202
    }
2203
2204
    /**
2205
     * Do we show the sort links.
2206
     *
2207
     * A bit arbitrary to say three,
2208
     * but there is not much point to sort three or less products
2209
     *
2210
     * @return bool
2211
     */
2212
    public function ShowSortLinks($minimumCount = 3)
2213
    {
2214
        if ($this->TotalCountGreaterThanOne($minimumCount)) {
2215
            return true;
2216
        }
2217
2218
        return false;
2219
    }
2220
2221
    /**
2222
     * Is there a special filter operating at the moment?
2223
     * Is the current filter the default one (return inverse!)?
2224
     *
2225
     * @return bool
2226
     */
2227
    public function HasFilter()
2228
    {
2229
        return $this->getCurrentUserPreferences('FILTER') != $this->getMyUserPreferencesDefault('FILTER')
2230
        || $this->filterForGroupObject;
2231
    }
2232
2233
    /**
2234
     * Is there a special sort operating at the moment?
2235
     * Is the current sort the default one (return inverse!)?
2236
     *
2237
     * @return bool
2238
     */
2239
    public function HasSort()
2240
    {
2241
        $sort = $this->getCurrentUserPreferences('SORT');
2242
        if ($sort != $this->getMyUserPreferencesDefault('SORT')) {
2243
            return true;
2244
        }
2245
    }
2246
2247
    /**
2248
     * @return boolean
2249
     */
2250
    public function HasFilterOrSort()
2251
    {
2252
        return $this->HasFilter() || $this->HasSort();
2253
    }
2254
2255
    /**
2256
     * @return boolean
2257
     */
2258
    public function HasFilterOrSortFullList()
2259
    {
2260
        return $this->HasFilterOrSort() || $this->IsShowFullList();
2261
    }
2262
2263
    /**
2264
     * are filters available?
2265
     * we check one at the time so that we do the least
2266
     * amount of DB queries.
2267
     *
2268
     * @return bool
2269
     */
2270
    public function HasFilters()
2271
    {
2272
        $countFilters = $this->FilterLinks()->count();
2273
        if ($countFilters > 1) {
2274
            return true;
2275
        }
2276
        $countGroupFilters = $this->ProductGroupFilterLinks()->count();
2277
        if ($countGroupFilters > 1) {
2278
            return true;
2279
        }
2280
        if ($countFilters + $countGroupFilters > 1) {
2281
            return true;
2282
        }
2283
2284
        return false;
2285
    }
2286
2287
    /**
2288
     * Do we show all products on one page?
2289
     *
2290
     * @return bool
2291
     */
2292
    public function IsShowFullList()
2293
    {
2294
        return $this->showFullList;
2295
    }
2296
2297
    /**
2298
     * returns the current filter applied to the list
2299
     * in a human readable string.
2300
     *
2301
     * @return string
2302
     */
2303
    public function CurrentDisplayTitle()
2304
    {
2305
        $displayKey = $this->getCurrentUserPreferences('DISPLAY');
2306
        if ($displayKey != $this->getMyUserPreferencesDefault('DISPLAY')) {
2307
            return $this->getUserPreferencesTitle('DISPLAY', $displayKey);
2308
        }
2309
    }
2310
2311
    /**
2312
     * returns the current filter applied to the list
2313
     * in a human readable string.
2314
     *
2315
     * @return string
2316
     */
2317
    public function CurrentFilterTitle()
2318
    {
2319
        $filterKey = $this->getCurrentUserPreferences('FILTER');
2320
        $filters = array();
2321
        if ($filterKey != $this->getMyUserPreferencesDefault('FILTER')) {
2322
            $filters[] = $this->getUserPreferencesTitle('FILTER', $filterKey);
2323
        }
2324
        if ($this->filterForGroupObject) {
2325
            $filters[] = $this->filterForGroupObject->MenuTitle;
2326
        }
2327
        if (count($filters)) {
2328
            return implode(', ', $filters);
2329
        }
2330
    }
2331
2332
    /**
2333
     * returns the current sort applied to the list
2334
     * in a human readable string.
2335
     *
2336
     * @return string
2337
     */
2338
    public function CurrentSortTitle()
2339
    {
2340
        $sortKey = $this->getCurrentUserPreferences('SORT');
2341
        if ($sortKey != $this->getMyUserPreferencesDefault('SORT')) {
2342
            return $this->getUserPreferencesTitle('SORT', $sortKey);
2343
        }
2344
    }
2345
2346
    /**
2347
     * short-cut for getMyUserPreferencesDefault("DISPLAY")
2348
     * for use in templtes.
2349
     *
2350
     * @return string - key
2351
     */
2352
    public function MyDefaultDisplayStyle()
2353
    {
2354
        return $this->getMyUserPreferencesDefault('DISPLAY');
2355
    }
2356
2357
    /**
2358
     * Number of entries per page limited by total number of pages available...
2359
     *
2360
     * @return int
2361
     */
2362
    public function MaxNumberOfProductsPerPage()
2363
    {
2364
        return $this->MyNumberOfProductsPerPage() > $this->TotalCount() ? $this->TotalCount() : $this->MyNumberOfProductsPerPage();
2365
    }
2366
2367
    /****************************************************
2368
     *  TEMPLATE METHODS FILTER LINK
2369
    /****************************************************/
2370
2371
    /**
2372
     * Provides a ArrayList of links for filters products.
2373
     *
2374
     * @return ArrayList( ArrayData(Name, Link, SelectKey, Current (boolean), LinkingMode))
2375
     */
2376
    public function FilterLinks()
2377
    {
2378
        $cacheKey = 'FilterLinks_'.($this->filterForGroupObject ? $this->filterForGroupObject->ID : 0);
2379
        if ($list = $this->retrieveObjectStore($cacheKey)) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
2380
            //do nothing
2381
        } else {
2382
            $list = $this->userPreferencesLinks('FILTER');
2383
            foreach ($list as $obj) {
2384
                $key = $obj->SelectKey;
2385
                if ($key != $this->getMyUserPreferencesDefault('FILTER')) {
2386
                    $count = count($this->currentInitialProductsAsCachedArray($key));
2387
                    if ($count == 0) {
2388
                        $list->remove($obj);
2389
                    } else {
2390
                        $obj->Count = $count;
2391
                    }
2392
                }
2393
            }
2394
            $this->saveObjectStore($list, $cacheKey);
2395
        }
2396
        $selectedItem = $this->getCurrentUserPreferences('FILTER');
2397
        foreach ($list as $obj) {
2398
            $canHaveCurrent = true;
2399
            if ($this->filterForGroupObject) {
2400
                $canHaveCurrent = false;
2401
            }
2402
            $obj->Current = $selectedItem == $obj->SelectKey && $canHaveCurrent ? true : false;
2403
            $obj->LinkingMode = $obj->Current ? 'current' : 'link';
2404
            $obj->Ajaxify = true;
2405
        }
2406
2407
        return $list;
2408
    }
2409
2410
    /**
2411
     * returns a list of items (with links).
2412
     *
2413
     * @return ArrayList( ArrayData(Name, FilterLink,  SelectKey, Current (boolean), LinkingMode))
2414
     */
2415
    public function ProductGroupFilterLinks()
2416
    {
2417
        if ($array = $this->retrieveObjectStore('ProductGroupFilterLinks')) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
2418
            //do nothing
2419
        } else {
2420
            $arrayOfItems = array();
2421
2422
            $baseArray = $this->currentInitialProductsAsCachedArray($this->getMyUserPreferencesDefault('FILTER'));
2423
2424
            //also show
2425
            $items = $this->ProductGroupsFromAlsoShowProducts();
2426
            $arrayOfItems = array_merge($arrayOfItems, $this->productGroupFilterLinksCount($items, $baseArray, true));
2427
            //also show inverse
2428
            $items = $this->ProductGroupsFromAlsoShowProductsInverse();
2429
            $arrayOfItems = array_merge($arrayOfItems, $this->productGroupFilterLinksCount($items, $baseArray, true));
2430
2431
            //parent groups
2432
            $items = $this->ProductGroupsParentGroups();
2433
            $arrayOfItems = array_merge($arrayOfItems, $this->productGroupFilterLinksCount($items, $baseArray, true));
2434
2435
            //child groups
2436
            $items = $this->MenuChildGroups();
2437
            $arrayOfItems = array_merge($arrayOfItems,  $this->productGroupFilterLinksCount($items, $baseArray, true));
2438
2439
            ksort($arrayOfItems);
2440
            $array = array();
2441
            foreach ($arrayOfItems as $arrayOfItem) {
2442
                $array[] = $this->makeArrayItem($arrayOfItem);
2443
            }
2444
            $this->saveObjectStore($array, 'ProductGroupFilterLinks');
2445
        }
2446
        $arrayList = ArrayList::create();
2447
        foreach ($array as $item) {
2448
            $arrayList->push(ArrayData::create($item));
2449
        }
2450
        return $arrayList;
2451
    }
2452
2453
    /**
2454
     * counts the total number in the combination....
2455
     *
2456
     * @param DataList $items     - list of
2457
     * @param Arary    $baseArray - list of products on the current page
2458
     *
2459
     * @return array
2460
     */
2461
    protected function productGroupFilterLinksCount($items, $baseArray, $ajaxify = true)
2462
    {
2463
        $array = array();
2464
        if ($items && $items->count()) {
2465
            foreach ($items as $item) {
2466
                $arrayOfIDs = $item->currentInitialProductsAsCachedArray($this->getMyUserPreferencesDefault('FILTER'));
2467
                $newArray = array_intersect_key(
2468
                    $arrayOfIDs,
2469
                    $baseArray
2470
                );
2471
                $count = count($newArray);
2472
                if ($count) {
2473
                    $array[$item->Title] = array(
2474
                        'Item' => $item,
2475
                        'Count' => $count,
2476
                        'Ajaxify' => $ajaxify,
2477
                    );
2478
                }
2479
            }
2480
        }
2481
2482
        return $array;
2483
    }
2484
2485
    /**
2486
     * @param array itemInArray (Item, Count, UserFilterAction)
2487
     *
2488
     * @return ArrayData
2489
     */
2490
    protected function makeArrayItem($itemInArray)
2491
    {
2492
        $item = $itemInArray['Item'];
2493
        $count = $itemInArray['Count'];
2494
        $ajaxify = $itemInArray['Ajaxify'];
2495
        $filterForGroupObjectID = $this->filterForGroupObject ? $this->filterForGroupObject->ID : 0;
2496
        $isCurrent = $item->ID == $filterForGroupObjectID;
2497
        if ($ajaxify) {
2498
            $link = $this->Link('filterforgroup/'.$item->URLSegment);
2499
        } else {
2500
            $link = $item->Link();
2501
        }
2502
        return array(
2503
            'Title' => $item->Title,
2504
            'Count' => $count,
2505
            'SelectKey' => $item->URLSegment,
2506
            'Current' => $isCurrent ? true : false,
2507
            'MyLinkingMode' => $isCurrent ? 'current' : 'link',
2508
            'FilterLink' => $link,
2509
            'Ajaxify' => $ajaxify ? true : false,
2510
        );
2511
    }
2512
2513
    /**
2514
     * Provides a ArrayList of links for sorting products.
2515
     */
2516
    public function SortLinks()
2517
    {
2518
        $list = $this->userPreferencesLinks('SORT');
2519
        $selectedItem = $this->getCurrentUserPreferences('SORT');
2520
        if ($list) {
2521
            foreach ($list as $obj) {
2522
                $obj->Current = $selectedItem == $obj->SelectKey ? true : false;
2523
                $obj->LinkingMode = $obj->Current ? 'current' : 'link';
2524
                $obj->Ajaxify = true;
2525
            }
2526
2527
            return $list;
2528
        }
2529
    }
2530
2531
    /**
2532
     * Provides a ArrayList for displaying display links.
2533
     */
2534
    public function DisplayLinks()
2535
    {
2536
        $list = $this->userPreferencesLinks('DISPLAY');
2537
        $selectedItem = $this->getCurrentUserPreferences('DISPLAY');
2538
        if ($list) {
2539
            foreach ($list as $obj) {
2540
                $obj->Current = $selectedItem == $obj->SelectKey ? true : false;
2541
                $obj->LinkingMode = $obj->Current ? 'current' : 'link';
2542
                $obj->Ajaxify = true;
2543
            }
2544
2545
            return $list;
2546
        }
2547
    }
2548
2549
    /**
2550
     * Link that returns a list of all the products
2551
     * for this product group as a simple list.
2552
     *
2553
     * @return string
2554
     */
2555
    public function ListAllLink()
2556
    {
2557
        if ($this->filterForGroupObject) {
2558
            return $this->Link('filterforgroup/'.$this->filterForGroupObject->URLSegment).'?showfulllist=1';
2559
        } else {
2560
            return $this->Link().'?showfulllist=1';
2561
        }
2562
    }
2563
2564
    /**
2565
     * Link that returns a list of all the products
2566
     * for this product group as a simple list.
2567
     *
2568
     * @return string
2569
     */
2570
    public function ListAFewLink()
2571
    {
2572
        return str_replace('?showfulllist=1', '', $this->ListAllLink());
2573
    }
2574
2575
    /**
2576
     * Link that returns a list of all the products
2577
     * for this product group as a simple list.
2578
     *
2579
     * It resets everything - not just filter....
2580
     *
2581
     * @return string
2582
     */
2583
    public function ResetPreferencesLink($escapedAmpersands = true)
2584
    {
2585
        $ampersand = '&';
2586
        if ($escapedAmpersands) {
2587
            $ampersand = '&amp;';
2588
        }
2589
        $getVariableNameFilter = $this->getSortFilterDisplayNames('FILTER', 'getVariable');
2590
        $getVariableNameSort = $this->getSortFilterDisplayNames('SORT', 'getVariable');
2591
2592
        return $this->Link().'?'.
2593
            $getVariableNameFilter.'='.$this->getMyUserPreferencesDefault('FILTER').$ampersand.
2594
            $getVariableNameSort.'='.$this->getMyUserPreferencesDefault('SORT').$ampersand.
2595
            'reload=1';
2596
    }
2597
2598
    /**
2599
     * Link to the search results.
2600
     *
2601
     * @return string
2602
     */
2603
    public function SearchResultLink()
2604
    {
2605
        if ($this->HasSearchResults() && !$this->isSearchResults) {
2606
            return $this->Link('searchresults');
2607
        }
2608
    }
2609
2610
    /****************************************************
2611
     *  INTERNAL PROCESSING: PRODUCT LIST
2612
    /****************************************************/
2613
2614
    /**
2615
     * turns full list into paginated list.
2616
     *
2617
     * @param SS_List
2618
     *
2619
     * @return PaginatedList
2620
     */
2621
    protected function paginateList(SS_List $list)
2622
    {
2623
        if ($list && $list->count()) {
2624
            if ($this->IsShowFullList()) {
2625
                $obj = PaginatedList::create($list, $this->request);
2626
                $obj->setPageLength(EcommerceConfig::get('ProductGroup', 'maximum_number_of_products_to_list') + 1);
2627
2628
                return $obj;
2629
            } else {
2630
                $obj = PaginatedList::create($list, $this->request);
2631
                $obj->setPageLength($this->MyNumberOfProductsPerPage());
2632
2633
                return $obj;
2634
            }
2635
        }
2636
    }
2637
2638
    /****************************************************
2639
     *  INTERNAL PROCESSING: USER PREFERENCES
2640
    /****************************************************/
2641
2642
    /**
2643
     * Checks out a bunch of $_GET variables
2644
     * that are used to work out user preferences
2645
     * Some of these are saved to session.
2646
     *
2647
     * @param array $overrideArray - override $_GET variable settings
2648
     */
2649
    protected function saveUserPreferences($overrideArray = array())
2650
    {
2651
2652
        //save sort - filter - display
2653
        $sortFilterDisplayNames = $this->getSortFilterDisplayNames();
2654
        foreach ($sortFilterDisplayNames as $type => $oneTypeArray) {
2655
            $getVariableName = $oneTypeArray['getVariable'];
2656
            $sessionName = $oneTypeArray['sessionName'];
2657
            if (isset($overrideArray[$getVariableName])) {
2658
                $newPreference = $overrideArray[$getVariableName];
2659
            } else {
2660
                $newPreference = $this->request->getVar($getVariableName);
2661
            }
2662
            if ($newPreference) {
2663
                $optionsVariableName = $oneTypeArray['configName'];
2664
                $options = EcommerceConfig::get($this->ClassName, $optionsVariableName);
2665
                if (isset($options[$newPreference])) {
2666
                    Session::set('ProductGroup_'.$sessionName, $newPreference);
2667
                    //save in model as well...
2668
                }
2669
            } else {
2670
                $newPreference = Session::get('ProductGroup_'.$sessionName);
2671
            }
2672
            //save data in model...
2673
            $this->setCurrentUserPreference($type, $newPreference);
2674
        }
2675
        /* save URLSegments in model
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% 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...
2676
        $this->setCurrentUserPreference(
2677
            "URLSegments",
2678
            array(
2679
                "Action" => $this->request->param("Action"),
2680
                "ID" => $this->request->param("ID")
2681
            )
2682
        );
2683
        */
2684
2685
        //clearing data..
2686
        if ($this->request->getVar('reload')) {
2687
            //reset other session variables...
2688
            Session::set($this->SearchResultsSessionVariable(false), '');
2689
            Session::set($this->SearchResultsSessionVariable(true), '');
2690
2691
            return $this->redirect($this->Link());
2692
        }
2693
2694
        //full list ....
2695
        if ($this->request->getVar('showfulllist')) {
2696
            $this->showFullList = true;
2697
        }
2698
    }
2699
2700
    /**
2701
     * Checks for the most applicable user preferences for this user:
2702
     * 1. session value
2703
     * 2. getMyUserPreferencesDefault.
2704
     *
2705
     * @param string $type - FILTER | SORT | DISPLAY
2706
     *
2707
     * @return string
2708
     *
2709
     * @todo: move to controller?
2710
     */
2711
    protected function getCurrentUserPreferences($type)
2712
    {
2713
        $sessionName = $this->getSortFilterDisplayNames($type, 'sessionName');
2714
        if ($sessionValue = Session::get('ProductGroup_'.$sessionName)) {
2715
            $key = Convert::raw2sql($sessionValue);
2716
        } else {
2717
            $key = $this->getMyUserPreferencesDefault($type);
2718
        }
2719
2720
        return $key;
2721
    }
2722
2723
    /**
2724
     * Provides a dataset of links for a particular user preference.
2725
     *
2726
     * @param string $type SORT | FILTER | DISPLAY - e.g. sort_options
2727
     *
2728
     * @return ArrayList( ArrayData(Name, Link,  SelectKey, Current (boolean), LinkingMode))
2729
     */
2730
    protected function userPreferencesLinks($type)
2731
    {
2732
        //get basics
2733
        $sortFilterDisplayNames = $this->getSortFilterDisplayNames();
2734
        $options = $this->getConfigOptions($type);
2735
2736
        //if there is only one option then do not bother
2737
        if (count($options) < 2) {
2738
            return;
2739
        }
2740
2741
        //get more config names
2742
        $translationCode = $sortFilterDisplayNames[$type]['translationCode'];
2743
        $getVariableName = $sortFilterDisplayNames[$type]['getVariable'];
2744
        $arrayList = ArrayList::create();
2745
        if (count($options)) {
2746
            foreach ($options as $key => $array) {
2747
                //$isCurrent = ($key == $selectedItem) ? true : false;
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...
2748
2749
                $link = '?'.$getVariableName."=$key";
2750
                if ($type == 'FILTER') {
2751
                    $link = $this->Link().$link;
2752
                } else {
2753
                    $link = $this->request->getVar('url').$link;
2754
                }
2755
                $arrayList->push(ArrayData::create(array(
2756
                    'Name' => _t('ProductGroup.'.$translationCode.strtoupper(str_replace(' ', '', $array['Title'])), $array['Title']),
2757
                    'Link' => $link,
2758
                    'SelectKey' => $key,
2759
                    //we add current at runtime, so we can store the object without current set...
2760
                    //'Current' => $isCurrent,
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% 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...
2761
                    //'LinkingMode' => $isCurrent ? "current" : "link"
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% 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...
2762
                )));
2763
            }
2764
        }
2765
2766
        return $arrayList;
2767
    }
2768
2769
    /****************************************************
2770
     *  INTERNAL PROCESSING: TITLES
2771
    /****************************************************/
2772
2773
    /**
2774
     * variable to make sure secondary title only gets
2775
     * added once.
2776
     *
2777
     * @var bool
2778
     */
2779
    protected $secondaryTitleHasBeenAdded = false;
2780
2781
    /**
2782
     * add a secondary title to the main title
2783
     * in case there is, for example, a filter applied
2784
     * e.g. Socks | MyBrand.
2785
     *
2786
     * @param string
2787
     */
2788
    protected function addSecondaryTitle($secondaryTitle = '')
2789
    {
2790
        $pipe = _t('ProductGroup.TITLE_SEPARATOR', ' | ');
2791
        if (! $this->secondaryTitleHasBeenAdded) {
2792
            if (trim($secondaryTitle)) {
2793
                $secondaryTitle = $pipe.$secondaryTitle;
2794
            }
2795
            if ($this->IsSearchResults()) {
2796
                if ($array = $this->searchResultsArrayFromSession()) {
2797
                    //we remove 1 item here, because the array starts with 0 => 0
2798
                    $count = count($array) - 1;
2799
                    if ($count > 3) {
2800
                        $toAdd = $count. ' '._t('ProductGroup.PRODUCTS_FOUND', 'Products Found');
2801
                        $secondaryTitle .= $this->cleanSecondaryTitleForAddition($pipe, $toAdd);
2802
                    }
2803
                } else {
2804
                    $toAdd = _t('ProductGroup.SEARCH_RESULTS', 'Search Results');
2805
                    $secondaryTitle .= $this->cleanSecondaryTitleForAddition($pipe, $toAdd);
2806
                }
2807
            }
2808
            if (is_object($this->filterForGroupObject)) {
2809
                $toAdd = $this->filterForGroupObject->Title;
2810
                $secondaryTitle .= $this->cleanSecondaryTitleForAddition($pipe, $toAdd);
2811
            }
2812
            if ($this->IsShowFullList()) {
2813
                $toAdd = _t('ProductGroup.LIST_VIEW', 'List View');
2814
                $secondaryTitle .= $this->cleanSecondaryTitleForAddition($pipe, $toAdd);
2815
            }
2816
            $filter = $this->getCurrentUserPreferences('FILTER');
2817
            if ($filter != $this->getMyUserPreferencesDefault('FILTER')) {
2818
                $toAdd = $this->getUserPreferencesTitle('FILTER', $this->getCurrentUserPreferences('FILTER'));
2819
                $secondaryTitle .= $this->cleanSecondaryTitleForAddition($pipe, $toAdd);
2820
            }
2821
            if ($this->HasSort()) {
2822
                $toAdd = $this->getUserPreferencesTitle('SORT', $this->getCurrentUserPreferences('SORT'));
2823
                $secondaryTitle .= $this->cleanSecondaryTitleForAddition($pipe, $toAdd);
2824
            }
2825
            if ($secondaryTitle) {
2826
                $this->Title .= $secondaryTitle;
2827
                if (isset($this->MetaTitle)) {
2828
                    $this->MetaTitle .= $secondaryTitle;
2829
                }
2830
            }
2831
            //dont update menu title, because the entry in the menu
2832
            //should stay the same as it links back to the unfiltered
2833
            //page (in some cases).
2834
            $this->secondaryTitleHasBeenAdded = true;
2835
        }
2836
    }
2837
2838
    /**
2839
     * removes any spaces from the 'toAdd' bit and adds the pipe if there is
2840
     * anything to add at all.  Through the lang files, you can change the pipe
2841
     * symbol to anything you like.
2842
     *
2843
     * @param  string $pipe
2844
     * @param  string $toAdd
2845
     * @return string
2846
     */
2847
    protected function cleanSecondaryTitleForAddition($pipe, $toAdd)
2848
    {
2849
        $toAdd = trim($toAdd);
2850
        $length = strlen($toAdd);
2851
        if ($length > 0) {
2852
            $toAdd = $pipe.$toAdd;
2853
        }
2854
        return $toAdd;
2855
    }
2856
2857
    /****************************************************
2858
     *  DEBUG
2859
    /****************************************************/
2860
2861
    public function debug()
2862
    {
2863
        $member = Member::currentUser();
2864
        if (!$member || !$member->IsShopAdmin()) {
2865
            $messages = array(
2866
                'default' => 'You must login as an admin to use debug functions.',
2867
            );
2868
            Security::permissionFailure($this, $messages);
2869
        }
2870
        $this->ProductsShowable();
2871
        $html = EcommerceTaskDebugCart::debug_object($this->dataRecord);
2872
        $html .= '<ul>';
2873
2874
        $html .= '<li><hr /><h3>Available options</h3><hr /></li>';
2875
        $html .= '<li><b>Sort Options for Dropdown:</b><pre> '.print_r($this->getUserPreferencesOptionsForDropdown('SORT'), 1).'</pre> </li>';
2876
        $html .= '<li><b>Filter Options for Dropdown:</b><pre> '.print_r($this->getUserPreferencesOptionsForDropdown('FILTER'), 1).'</pre></li>';
2877
        $html .= '<li><b>Display Styles for Dropdown:</b><pre> '.print_r($this->getUserPreferencesOptionsForDropdown('DISPLAY'), 1).'</pre> </li>';
2878
2879
        $html .= '<li><hr /><h3>Selection Setting (what is set as default for this page)</h3><hr /></li>';
2880
        $html .= '<li><b>MyDefaultFilter:</b> '.$this->getMyUserPreferencesDefault('FILTER').' </li>';
2881
        $html .= '<li><b>MyDefaultSortOrder:</b> '.$this->getMyUserPreferencesDefault('SORT').' </li>';
2882
        $html .= '<li><b>MyDefaultDisplayStyle:</b> '.$this->getMyUserPreferencesDefault('DISPLAY').' </li>';
2883
        $html .= '<li><b>MyNumberOfProductsPerPage:</b> '.$this->MyNumberOfProductsPerPage().' </li>';
2884
        $html .= '<li><b>MyLevelOfProductsToshow:</b> '.$this->MyLevelOfProductsToShow().' = '.(isset($this->showProductLevels[$this->MyLevelOfProductsToShow()]) ? $this->showProductLevels[$this->MyLevelOfProductsToShow()] : 'ERROR!!!! $this->showProductLevels not set for '.$this->MyLevelOfProductsToShow()).' </li>';
2885
2886
        $html .= '<li><hr /><h3>Current Settings</h3><hr /></li>';
2887
        $html .= '<li><b>Current Sort Order:</b> '.$this->getCurrentUserPreferences('SORT').' </li>';
2888
        $html .= '<li><b>Current Filter:</b> '.$this->getCurrentUserPreferences('FILTER').' </li>';
2889
        $html .= '<li><b>Current display style:</b> '.$this->getCurrentUserPreferences('DISPLAY').' </li>';
2890
2891
        $html .= '<li><hr /><h3>DATALIST: totals, numbers per page etc</h3><hr /></li>';
2892
        $html .= '<li><b>Total number of products:</b> '.$this->TotalCount().' </li>';
2893
        $html .= '<li><b>Is there more than one product:</b> '.($this->TotalCountGreaterThanOne() ? 'YES' : 'NO').' </li>';
2894
        $html .= '<li><b>Number of products per page:</b> '.$this->MyNumberOfProductsPerPage().' </li>';
2895
2896
        $html .= '<li><hr /><h3>SQL Factors</h3><hr /></li>';
2897
        $html .= '<li><b>Default sort SQL:</b> '.print_r($this->getUserSettingsOptionSQL('SORT'), 1).' </li>';
2898
        $html .= '<li><b>User sort SQL:</b> '.print_r($this->getUserSettingsOptionSQL('SORT',  $this->getCurrentUserPreferences('SORT')), 1).' </li>';
2899
        $html .= '<li><b>Default Filter SQL:</b> <pre>'.print_r($this->getUserSettingsOptionSQL('FILTER'), 1).'</pre> </li>';
2900
        $html .= '<li><b>User Filter SQL:</b> <pre>'.print_r($this->getUserSettingsOptionSQL('FILTER',  $this->getCurrentUserPreferences('FILTER')), 1).'</pre> </li>';
2901
        $html .= '<li><b>Buyable Class name:</b> '.$this->getBuyableClassName().' </li>';
2902
        $html .= '<li><b>allProducts:</b> '.print_r(str_replace('"', '`', $this->allProducts->sql()), 1).' </li>';
2903
2904
        $html .= '<li><hr /><h3>Search</h3><hr /></li>';
2905
        $resultArray = $this->searchResultsArrayFromSession();
2906
        $productGroupArray = explode(',', Session::get($this->SearchResultsSessionVariable(true)));
2907
        $html .= '<li><b>Is Search Results:</b> '.($this->IsSearchResults() ? 'YES' : 'NO').' </li>';
2908
        $html .= '<li><b>Products In Search (session variable : '.$this->SearchResultsSessionVariable(false).'):</b> '.print_r($resultArray, 1).' </li>';
2909
        $html .= '<li><b>Product Groups In Search (session variable : '.$this->SearchResultsSessionVariable(true).'):</b> '.print_r($productGroupArray, 1).' </li>';
2910
2911
        $html .= '<li><hr /><h3>Other</h3><hr /></li>';
2912
        if ($image = $this->BestAvailableImage()) {
2913
            $html .= '<li><b>Best Available Image:</b> <img src="'.$image->Link.'" /> </li>';
2914
        }
2915
        $html .= '<li><b>BestAvailableImage:</b> '.($this->BestAvailableImage() ? $this->BestAvailableImage()->Link : 'no image available').' </li>';
2916
        $html .= '<li><b>Is this an ecommerce page:</b> '.($this->IsEcommercePage() ? 'YES' : 'NO').' </li>';
2917
        $html .= '<li><hr /><h3>Related Groups</h3><hr /></li>';
2918
        $html .= '<li><b>Parent product group:</b> '.($this->ParentGroup() ? $this->ParentGroup()->Title : '[NO PARENT GROUP]').'</li>';
2919
2920
        $childGroups = $this->ChildGroups(99);
2921
        if ($childGroups->count()) {
2922
            $childGroups = $childGroups->map('ID', 'MenuTitle');
2923
            $html .= '<li><b>Child Groups (all):</b><pre> '.print_r($childGroups, 1).' </pre></li>';
2924
        } else {
2925
            $html .= '<li><b>Child Groups (full tree): </b>NONE</li>';
2926
        }
2927
        $html .= '<li><b>a list of Product Groups that have the products for the CURRENT product group listed as part of their AlsoShowProducts list:</b><pre>'.print_r($this->ProductGroupsFromAlsoShowProducts()->map('ID', 'Title')->toArray(), 1).' </pre></li>';
2928
        $html .= '<li><b>the inverse of ProductGroupsFromAlsoShowProducts:</b><pre> '.print_r($this->ProductGroupsFromAlsoShowProductsInverse()->map('ID', 'Title')->toArray(), 1).' </pre></li>';
2929
        $html .= '<li><b>all product parent groups:</b><pre> '.print_r($this->ProductGroupsParentGroups()->map('ID', 'Title')->toArray(), 1).' </pre></li>';
2930
2931
        $html .= '<li><hr /><h3>Product Example and Links</h3><hr /></li>';
2932
        $product = Product::get()->filter(array('ParentID' => $this->ID))->first();
2933
        if ($product) {
2934
            $html .= '<li><b>Product View:</b> <a href="'.$product->Link().'">'.$product->Title.'</a> </li>';
2935
            $html .= '<li><b>Product Debug:</b> <a href="'.$product->Link('debug').'">'.$product->Title.'</a> </li>';
2936
            $html .= '<li><b>Product Admin Page:</b> <a href="'.'/admin/pages/edit/show/'.$product->ID.'">'.$product->Title.'</a> </li>';
2937
            $html .= '<li><b>ProductGroup Admin Page:</b> <a href="'.'/admin/pages/edit/show/'.$this->ID.'">'.$this->Title.'</a> </li>';
2938
        } else {
2939
            $html .= '<li>this page has no products of its own</li>';
2940
        }
2941
        $html .= '</ul>';
2942
2943
        return $html;
2944
    }
2945
}
2946