ProductGroup_Controller::addSecondaryTitle()   F
last analyzed

Complexity

Conditions 16
Paths 2561

Size

Total Lines 64

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
dl 0
loc 64
rs 1.4
c 0
b 0
f 0
nc 2561
nop 1

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
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)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
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);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (canEdit() instead of canCreate()). Are you sure this is correct? If so, you might want to change this to $this->canEdit().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
199
    }
200
201
    /**
202
     * Shop Admins can edit.
203
     *
204
     * @param Member $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be Member|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
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
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be Member|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
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);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (canEdit() instead of canDelete()). Are you sure this is correct? If so, you might want to change this to $this->canEdit().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
248
    }
249
250
    /**
251
     * Standard SS method.
252
     *
253
     * @param Member $member
0 ignored issues
show
Documentation introduced by
Should the type for parameter $member not be Member|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
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);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (canEdit() instead of canPublish()). Are you sure this is correct? If so, you might want to change this to $this->canEdit().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
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...
0 ignored issues
show
Documentation introduced by
There is no parameter named $typeOfVariableName. Did you maybe mean $variable?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
376
     * @param string $variable:          sessionName, getVariable, etc...
0 ignored issues
show
Documentation introduced by
There is no parameter named $variable:. Did you maybe mean $variable?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
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 ...
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
509
     *********************/
510
511
    /**
512
     * check if the key is valid.
513
     *
514
     * @param  string $type     e.g. SORT | FILTER
515
     * @param  string $key      e.g. best_match | price | lastest
516
     * @param  string $variable e.g. SQL | Title
517
518
     * @return string - empty if not found
519
     */
520
    protected function getBestKeyAndValidateKey($type, $key = '', $variable = '')
521
    {
522
        $options = $this->getConfigOptions($type);
523
        //check !!!
524
        if($key && isset($options[$key])) {
525
            //all good
526
        } else {
527
            //reset
528
            $key = $this->getMyUserPreferencesDefault($type);
529
            $myGetVariable = $this->getSortFilterDisplayNames($type, 'getVariable');
0 ignored issues
show
Unused Code introduced by
$myGetVariable is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
530
            //clear bogus value from session ...
531
            $sessionName = $this->getSortFilterDisplayNames($type, 'sessionName');
532
            Session::set('ProductGroup_'.$sessionName, '');
533
        }
534
        if($key) {
535
            if($variable) {
536
                return $options[$key][$variable];
537
            }
538
        }
539
540
        return $key;
541
    }
542
543
544
    /**
545
     * SORT:
546
     * Returns the sort sql for a particular sorting key.
547
     * If no key is provided then the default key will be returned.
548
     *
549
     * @param string $key
550
     *
551
     * @return array (e.g. Array(MyField => "ASC", "MyOtherField" => "DESC")
552
     *
553
     * FILTER:
554
     * Returns the sql associated with a filter option.
555
     *
556
     * @param string $type - FILTER | SORT | DISPLAY
557
     * @param string $key  - the options selected
558
     *
559
     * @return array | String (e.g. array("MyField" => 1, "MyOtherField" => 0)) OR STRING
0 ignored issues
show
Documentation introduced by
Should the return type not be string|array<string,stri...ay<string,integer>|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
560
     */
561
    protected function getUserSettingsOptionSQL($type, $key = '')
562
    {
563
        $value = $this->getBestKeyAndValidateKey($type, $key, 'SQL');
564
        if($value) {
565
            return $value;
566
        } else {
567
            if ($type == 'FILTER') {
568
                return array('Sort' => 'ASC');
569
            } elseif ($type == 'SORT') {
570
                return array('ShowInSearch' => 1);
571
            }
572
        }
573
    }
574
575
    /*********************
576
     * SETTINGS: Title
577
     *********************/
578
579
    /**
580
     * Returns the Title for a type key.
581
     * If no key is provided then the default key is used.
582
     *
583
     * @param string $type - FILTER | SORT | DISPLAY
584
     * @param string $key
585
     *
586
     * @return string
587
     */
588
    public function getUserPreferencesTitle($type, $key = '')
589
    {
590
        $value = $this->getBestKeyAndValidateKey($type, $key, 'Title');
591
        if($value) {
592
            return $value;
593
        } else {
594
            return _t('ProductGroup.UNKNOWN', 'UNKNOWN USER SETTING');
595
        }
596
    }
597
598
    /*********************
599
     * SETTINGS: products per page
600
     *********************/
601
602
    /**
603
     * @return int
604
     **/
605
    public function ProductsPerPage()
606
    {
607
        return $this->MyNumberOfProductsPerPage();
608
    }
609
610
    private $_numberOfProductsPerPage = null;
611
612
    /**
613
     * @return int
614
     **/
615
    public function MyNumberOfProductsPerPage()
616
    {
617
        if ($this->_numberOfProductsPerPage === null) {
618
            $productsPagePage = 0;
0 ignored issues
show
Unused Code introduced by
$productsPagePage is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
619
            if ($this->NumberOfProductsPerPage) {
620
                $productsPagePage = $this->NumberOfProductsPerPage;
621
            } else {
622
                if ($parent = $this->ParentGroup()) {
623
                    $productsPagePage = $parent->MyNumberOfProductsPerPage();
624
                } else {
625
                    $productsPagePage = $this->EcomConfig()->NumberOfProductsPerPage;
626
                }
627
            }
628
            $this->_numberOfProductsPerPage = $productsPagePage;
629
        }
630
        return $this->_numberOfProductsPerPage;
631
    }
632
633
    /*********************
634
     * SETTINGS: level of products to show
635
     *********************/
636
637
    /**
638
     * returns the number of product groups (children)
639
     * to show in the current product list
640
     * based on the user setting for this page.
641
     *
642
     * @return int
643
     */
644
    public function MyLevelOfProductsToShow()
645
    {
646
        if ($this->LevelOfProductsToShow == 0) {
647
            if ($parent = $this->ParentGroup()) {
648
                $this->LevelOfProductsToShow = $parent->MyLevelOfProductsToShow();
649
            }
650
        }
651
        //reset to default
652
        if ($this->LevelOfProductsToShow     == 0) {
653
            $defaults = Config::inst()->get('ProductGroup', 'defaults');
654
655
            return isset($defaults['LevelOfProductsToShow']) ? $defaults['LevelOfProductsToShow'] : 99;
656
        }
657
658
        return $this->LevelOfProductsToShow;
659
    }
660
661
    /*********************
662
     * CMS Fields
663
     *********************/
664
665
    /**
666
     * standard SS method.
667
     *
668
     * @return FieldList
669
     */
670
    public function getCMSFields()
671
    {
672
        $fields = parent::getCMSFields();
673
        //dirty hack to show images!
674
        $fields->addFieldToTab('Root.Images', Product_ProductImageUploadField::create('Image', _t('Product.IMAGE', 'Product Group Image')));
675
        //number of products
676
        $calculatedNumberOfProductsPerPage = $this->MyNumberOfProductsPerPage();
677
        $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)') : '';
678
        $fields->addFieldToTab(
679
            'Root',
680
            Tab::create(
681
                'ProductDisplay',
682
                _t('ProductGroup.DISPLAY', 'Display'),
683
                $productsToShowField = DropdownField::create('LevelOfProductsToShow', _t('ProductGroup.PRODUCTSTOSHOW', 'Products to show'), $this->showProductLevels),
684
                HeaderField::create('WhatProductsAreShown', _t('ProductGroup.WHATPRODUCTSSHOWN', _t('ProductGroup.OPTIONSSELECTEDBELOWAPPLYTOCHILDGROUPS', 'Inherited options'))),
685
                $numberOfProductsPerPageField = NumericField::create('NumberOfProductsPerPage', _t('ProductGroup.PRODUCTSPERPAGE', 'Number of products per page'))
686
            )
687
        );
688
        $numberOfProductsPerPageField->setRightTitle($numberOfProductsPerPageExplanation);
689
        if ($calculatedNumberOfProductsPerPage && !$this->NumberOfProductsPerPage) {
690
            $this->NumberOfProductsPerPage = null;
691
            $numberOfProductsPerPageField->setAttribute('placeholder', $calculatedNumberOfProductsPerPage);
692
        }
693
        //sort
694
        $sortDropdownList = $this->getUserPreferencesOptionsForDropdown('SORT');
695
        if (count($sortDropdownList) > 1) {
696
            $sortOrderKey = $this->getMyUserPreferencesDefault('SORT');
697
            if ($this->DefaultSortOrder == 'inherit') {
698
                $actualValue = ' ('.(isset($sortDropdownList[$sortOrderKey]) ? $sortDropdownList[$sortOrderKey] : _t('ProductGroup.ERROR', 'ERROR')).')';
699
                $sortDropdownList['inherit'] = _t('ProductGroup.INHERIT', 'Inherit').$actualValue;
700
            }
701
            $fields->addFieldToTab(
702
                'Root.ProductDisplay',
703
                $defaultSortOrderField = DropdownField::create('DefaultSortOrder', _t('ProductGroup.DEFAULTSORTORDER', 'Default Sort Order'), $sortDropdownList)
704
            );
705
            $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."));
706
        }
707
        //filter
708
        $filterDropdownList = $this->getUserPreferencesOptionsForDropdown('FILTER');
709
        if (count($filterDropdownList) > 1) {
710
            $filterKey = $this->getMyUserPreferencesDefault('FILTER');
711
            if ($this->DefaultFilter == 'inherit') {
712
                $actualValue = ' ('.(isset($filterDropdownList[$filterKey]) ? $filterDropdownList[$filterKey] : _t('ProductGroup.ERROR', 'ERROR')).')';
713
                $filterDropdownList['inherit'] = _t('ProductGroup.INHERIT', 'Inherit').$actualValue;
714
            }
715
            $fields->addFieldToTab(
716
                'Root.ProductDisplay',
717
                $defaultFilterField = DropdownField::create('DefaultFilter', _t('ProductGroup.DEFAULTFILTER', 'Default Filter'), $filterDropdownList)
718
            );
719
            $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."));
720
        }
721
        //display style
722
        $displayStyleDropdownList = $this->getUserPreferencesOptionsForDropdown('DISPLAY');
723
        if (count($displayStyleDropdownList) > 2) {
724
            $displayStyleKey = $this->getMyUserPreferencesDefault('DISPLAY');
725
            if ($this->DisplayStyle == 'inherit') {
726
                $actualValue = ' ('.(isset($displayStyleDropdownList[$displayStyleKey]) ? $displayStyleDropdownList[$displayStyleKey] : _t('ProductGroup.ERROR', 'ERROR')).')';
727
                $displayStyleDropdownList['inherit'] = _t('ProductGroup.INHERIT', 'Inherit').$actualValue;
728
            }
729
            $fields->addFieldToTab(
730
                'Root.ProductDisplay',
731
                DropdownField::create('DisplayStyle', _t('ProductGroup.DEFAULTDISPLAYSTYLE', 'Default Display Style'), $displayStyleDropdownList)
732
            );
733
        }
734
        if ($this->EcomConfig()->ProductsAlsoInOtherGroups) {
735
            if (!$this instanceof ProductGroupSearchPage) {
736
                $fields->addFieldsToTab(
737
                    'Root.OtherProductsShown',
738
                    array(
739
                        HeaderField::create('ProductGroupsHeader', _t('ProductGroup.OTHERPRODUCTSTOSHOW', 'Other products to show ...')),
740
                        $this->getProductGroupsTable(),
741
                    )
742
                );
743
            }
744
        }
745
746
        return $fields;
747
    }
748
749
    /**
750
     * used if you install lumberjack
751
     * @return string
752
     */
753
    public function getLumberjackTitle()
754
    {
755
        return _t('ProductGroup.BUYABLES', 'Products');
756
    }
757
758
    /**
759
     * add this segment to the end of a Product Group
760
     * link to create a cross-filter between the two categories.
761
     *
762
     * @return string
763
     */
764
    public function FilterForGroupLinkSegment()
765
    {
766
        return 'filterforgroup/'.$this->URLSegment.'/';
767
    }
768
769
    // /**
770
    //  * used if you install lumberjack
771
    //  * @return string
772
    //  */
773
    // public function getLumberjackGridFieldConfig()
774
    // {
775
    //     return GridFieldConfig_RelationEditor::create();
776
    // }
777
778
    /**
779
     * Used in getCSMFields.
780
     *
781
     * @return GridField
782
     **/
783
    protected function getProductGroupsTable()
784
    {
785
        $gridField = GridField::create(
786
            'AlsoShowProducts',
787
            _t('ProductGroup.OTHER_PRODUCTS_SHOWN_IN_THIS_GROUP', 'Other products shown in this group ...'),
788
            $this->AlsoShowProducts(),
789
            GridFieldBasicPageRelationConfig::create()
790
        );
791
        //make sure edits are done in the right place ...
792
        return $gridField;
793
    }
794
795
    /*****************************************************
796
     *
797
     *
798
     *
799
     * PRODUCTS THAT BELONG WITH THIS PRODUCT GROUP
800
     *
801
     *
802
     *
803
     *****************************************************/
804
805
    /**
806
     * returns the inital (all) products, based on the all the eligible products
807
     * for the page.
808
     *
809
     * This is THE pivotal method that probably changes for classes that
810
     * extend ProductGroup as here you can determine what products or other buyables are shown.
811
     *
812
     * The return from this method will then be sorted to produce the final product list.
813
     *
814
     * There is no sort for the initial retrieval
815
     *
816
     * This method is public so that you can retrieve a list of products for a product group page.
817
     *
818
     * @param array | string $extraFilter          Additional SQL filters to apply to the Product retrieval
819
     * @param string         $alternativeFilterKey Alternative standard filter to be used.
820
     *
821
     * @return DataList
822
     **/
823
    public function currentInitialProducts($extraFilter = null, $alternativeFilterKey = '')
824
    {
825
826
        //INIT ALLPRODUCTS
827
        $this->setProductBase();
828
829
        // GROUP FILTER (PRODUCTS FOR THIS GROUP)
830
        $this->allProducts = $this->getGroupFilter();
831
832
        // STANDARD FILTER (INCLUDES USER PREFERENCE)
833
        $filterStatement = $this->allowPurchaseWhereStatement();
834
        if ($filterStatement) {
835
            if (is_array($filterStatement)) {
836
                $this->allProducts = $this->allProducts->filter($filterStatement);
837
            } elseif (is_string($filterStatement)) {
838
                $this->allProducts = $this->allProducts->where($filterStatement);
839
            }
840
        }
841
        $this->allProducts = $this->getStandardFilter($alternativeFilterKey);
842
843
        // EXTRA FILTER (ON THE FLY FROM CONTROLLER)
844
        if (is_array($extraFilter) && count($extraFilter)) {
845
            $this->allProducts = $this->allProducts->filter($extraFilter);
846
        } elseif (is_string($extraFilter) && strlen($extraFilter) > 2) {
847
            $this->allProducts = $this->allProducts->where($extraFilter);
848
        }
849
850
        //JOINS
851
        $this->allProducts = $this->getGroupJoin();
852
853
        return $this->allProducts;
854
    }
855
856
    protected function setProductBase()
857
    {
858
        unset($this->allProducts);
859
        $className = $this->getBuyableClassName();
860
        $this->allProducts = $className::get();
861
    }
862
863
    /**
864
     * this method can be used quickly get current initial products
865
     * whenever you write:
866
     *  ```php
867
     *   currentInitialProducts->(null, $key)->map("ID", "ID")->toArray();
868
     *  ```
869
     * this is the better replacement.
870
     *
871
     * @param string $filterKey
872
     *
873
     * @return array
874
     */
875
    public function currentInitialProductsAsCachedArray($filterKey)
876
    {
877
        //no need to add ID here ...
878
        $cacheKey = 'CurrentInitialProductsArray'.$filterKey;
879
        if ($array = $this->retrieveObjectStore($cacheKey)) {
880
            //do nothing
881
        } else {
882
            $array = $this->currentInitialProducts(null, $filterKey)->map('ID', 'ID')->toArray();
883
            $this->saveObjectStore($array, $cacheKey);
884
        }
885
886
        return $array;
887
    }
888
889
    /*****************************************************
890
     * DATALIST: adjusters
891
     * these are the methods you want to override in
892
     * any clases that extend ProductGroup
893
     *****************************************************/
894
895
    /**
896
     * Do products occur in more than one group.
897
     *
898
     * @return bool
899
     */
900
    protected function getProductsAlsoInOtherGroups()
901
    {
902
        return $this->EcomConfig()->ProductsAlsoInOtherGroups;
903
    }
904
905
    /**
906
     * Returns the class we are working with.
907
     *
908
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be array|integer|double|string|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
909
     */
910
    protected function getBuyableClassName()
911
    {
912
        return EcommerceConfig::get('ProductGroup', 'base_buyable_class');
913
    }
914
915
    /**
916
     * @SEE: important notes at the top of this file / class
917
     *
918
     * IMPORTANT: Adjusts allProducts and returns it...
919
     *
920
     * @return DataList
921
     */
922
    protected function getGroupFilter()
923
    {
924
        $levelToShow = $this->MyLevelOfProductsToShow();
925
        $cacheKey = 'GroupFilter_'.abs(intval($levelToShow + 999));
926
        if ($groupFilter = $this->retrieveObjectStore($cacheKey)) {
927
            $this->allProducts = $this->allProducts->where($groupFilter);
928
        } else {
929
            $groupFilter = '';
0 ignored issues
show
Unused Code introduced by
$groupFilter is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
930
            $productFilterArray = array();
931
            //special cases
932
            if ($levelToShow < 0) {
933
                //no produts but if LevelOfProductsToShow = -1 then show all
934
                $groupFilter = ' ('.$levelToShow.' = -1) ';
935
            } elseif ($levelToShow > 0) {
936
                $groupIDs = array($this->ID => $this->ID);
937
                $productFilterTemp = $this->getProductsToBeIncludedFromOtherGroups();
938
                $productFilterArray[$productFilterTemp] = $productFilterTemp;
939
                $childGroups = $this->ChildGroups($levelToShow);
940
                if ($childGroups && $childGroups->count()) {
941
                    foreach ($childGroups as $childGroup) {
942
                        $groupIDs[$childGroup->ID] = $childGroup->ID;
943
                        $productFilterTemp = $childGroup->getProductsToBeIncludedFromOtherGroups();
944
                        $productFilterArray[$productFilterTemp] = $productFilterTemp;
945
                    }
946
                }
947
                $groupFilter = ' ( "ParentID" IN ('.implode(',', $groupIDs).') ) '.implode($productFilterArray).' ';
948
            } else {
949
                //fall-back
950
                $groupFilter = '"ParentID" < 0';
951
            }
952
            $this->allProducts = $this->allProducts->where($groupFilter);
953
            $this->saveObjectStore($groupFilter, $cacheKey);
954
        }
955
956
        return $this->allProducts;
957
    }
958
959
    /**
960
     * If products are show in more than one group
961
     * Then this returns a where phrase for any products that are linked to this
962
     * product group.
963
     *
964
     * @return string
965
     */
966
    protected function getProductsToBeIncludedFromOtherGroups()
967
    {
968
        //TO DO: this should actually return
969
        //Product.ID = IN ARRAY(bla bla)
970
        $array = array();
971
        if ($this->getProductsAlsoInOtherGroups()) {
972
            $array = $this->AlsoShowProducts()->map('ID', 'ID')->toArray();
973
        }
974
        if (count($array)) {
975
            return " OR (\"Product\".\"ID\" IN (".implode(',', $array).')) ';
976
        }
977
978
        return '';
979
    }
980
981
    /**
982
     * @SEE: important notes at the top of this class / file for more information!
983
     *
984
     * IMPORTANT: Adjusts allProducts and returns it...
985
     *
986
     * @param string $alternativeFilterKey - filter key to be used... if none is specified then we use the current one.
987
     *
988
     * @return DataList
989
     */
990
    protected function getStandardFilter($alternativeFilterKey = '')
991
    {
992
        if ($alternativeFilterKey) {
993
            $filterKey = $alternativeFilterKey;
994
        } else {
995
            $filterKey = $this->getCurrentUserPreferences('FILTER');
996
        }
997
        $filter = $this->getUserSettingsOptionSQL('FILTER', $filterKey);
998
        if (is_array($filter)) {
999
            $this->allProducts = $this->allProducts->Filter($filter);
1000
        } elseif (is_string($filter) && strlen($filter) > 2) {
1001
            $this->allProducts = $this->allProducts->Where($filter);
1002
        }
1003
1004
        return $this->allProducts;
1005
    }
1006
1007
    /**
1008
     * Join statement for the product groups.
1009
     *
1010
     * IMPORTANT: Adjusts allProducts and returns it...
1011
     *
1012
     * @return DataList
1013
     */
1014
    protected function getGroupJoin()
1015
    {
1016
        return $this->allProducts;
1017
    }
1018
1019
    /**
1020
     * Quick - dirty hack - filter to
1021
     * only show relevant products.
1022
     *
1023
     * @param bool   $asArray
1024
     * @param string $table
1025
     */
1026
    protected function allowPurchaseWhereStatement($asArray = true, $table = 'Product')
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
1027
    {
1028
        if ($this->EcomConfig()->OnlyShowProductsThatCanBePurchased) {
1029
            if ($asArray) {
1030
                $allowPurchaseWhereStatement = array('AllowPurchase' => 1);
1031
            } else {
1032
                $allowPurchaseWhereStatement = "\"$table\".\"AllowPurchase\" = 1  ";
1033
            }
1034
1035
            return $allowPurchaseWhereStatement;
1036
        }
1037
    }
1038
1039
    /*****************************************************
1040
     *
1041
     *
1042
     *
1043
     *
1044
     * FINAL PRODUCTS
1045
     *
1046
     *
1047
     *
1048
     *
1049
     *****************************************************/
1050
1051
    /**
1052
     * This is the dataList that contains all the products.
1053
     *
1054
     * @var DataList
1055
     */
1056
    protected $allProducts = null;
1057
1058
    /**
1059
     * a list of relevant buyables that can
1060
     * not be purchased and therefore should be excluded.
1061
     * Should be set to NULL to start with so we know if it has been
1062
     * set yet.
1063
     *
1064
     * @var null | Array (like so: array(1,2,4,5,99))
1065
     */
1066
    private $canNOTbePurchasedArray = null;
1067
1068
    /**
1069
     * a list of relevant buyables that can
1070
     * be purchased.  We keep this so that
1071
     * that we can save to session, etc... for future use.
1072
     * Should be set to NULL to start with so we know if it has been
1073
     * set yet.
1074
     *
1075
     * @var null | Array (like so: array(1,2,4,5,99))
1076
     */
1077
    protected $canBePurchasedArray = null;
1078
1079
    /**
1080
     * returns the total numer of products
1081
     * (before pagination AND before MAX is applie).
1082
     *
1083
     * @return int
1084
     **/
1085
    public function RawCount()
1086
    {
1087
        return $this->rawCount ? $this->rawCount : 0;
1088
    }
1089
1090
    /**
1091
     * returns the total numer of products
1092
     * (before pagination but after MAX is applied).
1093
     *
1094
     * @return int
1095
     **/
1096
    public function TotalCount()
1097
    {
1098
        return $this->totalCount ? $this->totalCount : 0;
1099
    }
1100
1101
    /**
1102
     * this is used to save a list of sorted products
1103
     * so that you can find a previous and a next button, etc...
1104
     *
1105
     * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1106
     */
1107
    public function getProductsThatCanBePurchasedArray()
1108
    {
1109
        return $this->canBePurchasedArray;
1110
    }
1111
1112
    /**
1113
     * Retrieve a set of products, based on the given parameters.
1114
     * This method is usually called by the various controller methods.
1115
     * The extraFilter helps you to select different products,
1116
     * depending on the method used in the controller.
1117
     *
1118
     * Furthermore, extrafilter can take all sorts of variables.
1119
     * This is basically setup like this so that in ProductGroup extensions you
1120
     * can setup all sorts of filters, while still using the ProductsShowable method.
1121
     *
1122
     * The extra filter can be supplied as array (e.g. array("ID" => 12) or array("ID" => array(12,13,45)))
1123
     * or as string. Arrays are used like this $productDataList->filter($array) and
1124
     * strings are used with the where commands $productDataList->where($string).
1125
     *
1126
     * @param array | string $extraFilter          Additional SQL filters to apply to the Product retrieval
1127
     * @param array | string $alternativeSort      Additional SQL for sorting
1128
     * @param string         $alternativeFilterKey alternative filter key to be used
1129
     *
1130
     * @return DataList | Null
0 ignored issues
show
Documentation introduced by
Should the return type not be DataList|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1131
     */
1132
    public function ProductsShowable($extraFilter = null, $alternativeSort = null, $alternativeFilterKey = '')
1133
    {
1134
1135
        //get original products without sort
1136
        $this->allProducts = $this->currentInitialProducts($extraFilter, $alternativeFilterKey);
1137
1138
        //sort products
1139
        $this->allProducts = $this->currentFinalProducts($alternativeSort);
1140
1141
        return $this->allProducts;
1142
    }
1143
1144
    /**
1145
     * returns the final products, based on the all the eligile products
1146
     * for the page.
1147
     *
1148
     * In the process we also save a list of included products
1149
     * and we sort them.  We also keep a record of the total count.
1150
     *
1151
     * All of the 'current' methods are to support the currentFinalProducts Method.
1152
     *
1153
     * @TODO: cache data for faster access.
1154
     *
1155
     * @param array | string $alternativeSort = Alternative Sort String or array
1156
     *
1157
     * @return DataList
0 ignored issues
show
Documentation introduced by
Should the return type not be DataList|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1158
     **/
1159
    protected function currentFinalProducts($alternativeSort = null)
1160
    {
1161
        if ($this->allProducts) {
1162
            //limit to maximum number of products for speed's sake
1163
            $this->allProducts = $this->sortCurrentFinalProducts($alternativeSort);
1164
            $this->allProducts = $this->limitCurrentFinalProducts();
1165
            $this->allProducts = $this->removeExcludedProductsAndSaveIncludedProducts($this->allProducts);
1166
1167
            return $this->allProducts;
1168
        }
1169
    }
1170
1171
    /**
1172
     * returns the SORT part of the final selection of products.
1173
     *
1174
     * @return DataList (allProducts)
1175
     */
1176
    protected function sortCurrentFinalProducts($alternativeSort)
1177
    {
1178
        if ($alternativeSort) {
1179
            if ($this->IsIDarray($alternativeSort)) {
1180
                $sort = $this->createSortStatementFromIDArray($alternativeSort);
1181
            } else {
1182
                $sort = $alternativeSort;
1183
            }
1184
        } else {
1185
            $sort = $this->currentSortSQL();
1186
        }
1187
        $this->allProducts = $this->allProducts->Sort($sort);
1188
1189
        return $this->allProducts;
1190
    }
1191
1192
    /**
1193
     * is the variable provided is an array
1194
     * that can be used as a list of IDs?
1195
     *
1196
     * @param mixed
1197
     *
1198
     * @return bool
1199
     */
1200
    protected function IsIDarray($variable)
1201
    {
1202
        return $variable && is_array($variable) && count($variable) && intval(current($variable)) == current($variable);
1203
    }
1204
1205
    /**
1206
     * returns the SORT part of the final selection of products.
1207
     *
1208
     * @return string | Array
0 ignored issues
show
Documentation introduced by
Should the return type not be string|array<string,stri...ay<string,integer>|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1209
     */
1210
    protected function currentSortSQL()
1211
    {
1212
        $sortKey = $this->getCurrentUserPreferences('SORT');
1213
1214
        return $this->getUserSettingsOptionSQL('SORT', $sortKey);
1215
    }
1216
1217
    /**
1218
     * creates a sort string from a list of ID arrays...
1219
     *
1220
     * @param array $IDarray - list of product IDs
1221
     *
1222
     * @return string
1223
     */
1224
    protected function createSortStatementFromIDArray($IDarray, $table = 'Product')
1225
    {
1226
        $ifStatement = 'CASE ';
1227
        $sortStatement = '';
0 ignored issues
show
Unused Code introduced by
$sortStatement is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1228
        $stage = $this->getStage();
1229
        $count = 0;
1230
        foreach ($IDarray as $productID) {
1231
            $ifStatement .= ' WHEN "'.$table.$stage."\".\"ID\" = $productID THEN $count";
1232
            ++$count;
1233
        }
1234
        $sortStatement = $ifStatement.' END';
1235
1236
        return $sortStatement;
1237
    }
1238
1239
    /**
1240
     * limits the products to a maximum number (for speed's sake).
1241
     *
1242
     * @return DataList (this->allProducts adjusted!)
1243
     */
1244
    protected function limitCurrentFinalProducts()
1245
    {
1246
        $this->rawCount = $this->allProducts->count();
1247
        $max = EcommerceConfig::get('ProductGroup', 'maximum_number_of_products_to_list');
1248
        if ($this->rawCount > $max) {
1249
            $this->allProducts = $this->allProducts->limit($max - 1);
1250
            $this->totalCount = $max;
0 ignored issues
show
Documentation Bug introduced by
It seems like $max can also be of type double. However, the property $totalCount is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1251
        } else {
1252
            $this->totalCount = $this->rawCount;
1253
        }
1254
1255
        return $this->allProducts;
1256
    }
1257
1258
    /**
1259
     * Excluded products that can not be purchased
1260
     * We all make a record of all the products that are in the current list
1261
     * For efficiency sake, we do both these things at the same time.
1262
     * IMPORTANT: Adjusts allProducts and returns it...
1263
     *
1264
     * @todo: cache data per user ....
1265
     *
1266
     * @return DataList
1267
     */
1268
    protected function removeExcludedProductsAndSaveIncludedProducts()
1269
    {
1270
        if (is_array($this->canBePurchasedArray) && is_array($this->canNOTbePurchasedArray)) {
1271
            //already done!
1272
        } else {
1273
            $this->canNOTbePurchasedArray = array();
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type null of property $canNOTbePurchasedArray.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1274
            $this->canBePurchasedArray = array();
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type null of property $canBePurchasedArray.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1275
            if ($this->config()->get('actively_check_for_can_purchase')) {
1276
                foreach ($this->allProducts as $buyable) {
1277
                    if ($buyable->canPurchase()) {
1278
                        $this->canBePurchasedArray[$buyable->ID] = $buyable->ID;
1279
                    } else {
1280
                        $this->canNOTbePurchasedArray[$buyable->ID] = $buyable->ID;
1281
                    }
1282
                }
1283
            } else {
1284
                if ($this->rawCount > 0) {
1285
                    $this->canBePurchasedArray = $this->allProducts->map('ID', 'ID')->toArray();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->allProducts->map('ID', 'ID')->toArray() of type array is incompatible with the declared type null of property $canBePurchasedArray.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1286
                } else {
1287
                    $this->canBePurchasedArray = array();
1288
                }
1289
            }
1290
            if (count($this->canNOTbePurchasedArray)) {
1291
                $this->allProducts = $this->allProducts->exclude(array('ID' => $this->canNOTbePurchasedArray));
1292
            }
1293
        }
1294
1295
        return $this->allProducts;
1296
    }
1297
1298
    /*****************************************************
1299
     * Children and Parents
1300
     *****************************************************/
1301
1302
    /**
1303
     * Returns children ProductGroup pages of this group.
1304
     *
1305
     * @param int            $maxRecursiveLevel  - maximum depth , e.g. 1 = one level down - so no Child Groups are returned...
1306
     * @param string | Array $filter             - additional filter to be added
1307
     * @param int            $numberOfRecursions - current level of depth
1308
     *
1309
     * @return ArrayList (ProductGroups)
1310
     */
1311
    public function ChildGroups($maxRecursiveLevel, $filter = null, $numberOfRecursions = 0)
1312
    {
1313
        $arrayList = ArrayList::create();
1314
        ++$numberOfRecursions;
1315
        if ($numberOfRecursions < $maxRecursiveLevel) {
1316
            if ($filter && is_string($filter)) {
1317
                $filterWithAND = " AND $filter";
1318
                $where = "\"ParentID\" = '$this->ID' $filterWithAND";
1319
                $children = ProductGroup::get()->where($where);
1320
            } elseif (is_array($filter) && count($filter)) {
1321
                $filter = $filter + array('ParentID' => $this->ID);
1322
                $children = ProductGroup::get()->filter($filter);
1323
            } else {
1324
                $children = ProductGroup::get()->filter(array('ParentID' => $this->ID));
1325
            }
1326
1327
            if ($children->count()) {
1328
                foreach ($children as $child) {
1329
                    $arrayList->push($child);
1330
                    $arrayList->merge($child->ChildGroups($maxRecursiveLevel, $filter, $numberOfRecursions));
1331
                }
1332
            }
1333
        }
1334
        if (! ($arrayList instanceof SS_List)) {
1335
            user_error('We expect an SS_List as output');
1336
        }
1337
1338
        return $arrayList;
1339
    }
1340
1341
    /**
1342
     * Deprecated method.
1343
     */
1344
    public function ChildGroupsBackup($maxRecursiveLevel, $filter = '')
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
1345
    {
1346
        Deprecation::notice('3.1', 'No longer in use');
1347
        if ($maxRecursiveLevel > 24) {
1348
            $maxRecursiveLevel = 24;
1349
        }
1350
1351
        $stage = $this->getStage();
1352
        $select = 'P1.ID as ID1 ';
1353
        $from = "ProductGroup$stage as P1 ";
1354
        $join = " INNER JOIN SiteTree$stage AS S1 ON P1.ID = S1.ID";
1355
        $where = '1 = 1';
1356
        $ids = array(-1);
1357
        for ($i = 1; $i < $maxRecursiveLevel; ++$i) {
1358
            $j = $i + 1;
1359
            $select .= ", P$j.ID AS ID$j, S$j.ParentID";
1360
            $join .= "
1361
                LEFT JOIN ProductGroup$stage AS P$j ON P$j.ID = S$i.ParentID
1362
                LEFT JOIN SiteTree$stage AS S$j ON P$j.ID = S$j.ID
1363
            ";
1364
        }
1365
        $rows = DB::Query(' SELECT '.$select.' FROM '.$from.$join.' WHERE '.$where);
1366
        if ($rows) {
1367
            foreach ($rows as $row) {
1368
                for ($i = 1; $i < $maxRecursiveLevel; ++$i) {
1369
                    if ($row['ID'.$i]) {
1370
                        $ids[$row['ID'.$i]] = $row['ID'.$i];
1371
                    }
1372
                }
1373
            }
1374
        }
1375
1376
        return ProductGroup::get()->where("\"ProductGroup$stage\".\"ID\" IN (".implode(',', $ids).')'.$filterWithAND);
0 ignored issues
show
Bug introduced by
The variable $filterWithAND does not exist. Did you mean $filter?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
1377
    }
1378
1379
    /**
1380
     * returns the parent page, but only if it is an instance of Product Group.
1381
     *
1382
     * @return DataObject | Null (ProductGroup)
1383
     **/
1384
    public function ParentGroup()
1385
    {
1386
        if ($this->ParentID) {
1387
            return ProductGroup::get()->byID($this->ParentID);
1388
        }
1389
    }
1390
1391
    /*****************************************************
1392
     * Other Stuff
1393
     *****************************************************/
1394
1395
    /**
1396
     * Recursively generate a product menu.
1397
     *
1398
     * @param string $filter
1399
     *
1400
     * @return ArrayList (ProductGroups)
1401
     */
1402
    public function GroupsMenu($filter = 'ShowInMenus = 1')
1403
    {
1404
        if ($parent = $this->ParentGroup()) {
1405
            return is_a($parent, Object::getCustomClass('ProductGroup')) ? $parent->GroupsMenu() : $this->ChildGroups($filter);
1406
        } else {
1407
            return $this->ChildGroups($filter);
1408
        }
1409
    }
1410
1411
    /**
1412
     * returns a "BestAvailable" image if the current one is not available
1413
     * In some cases this is appropriate and in some cases this is not.
1414
     * For example, consider the following setup
1415
     * - product A with three variations
1416
     * - Product A has an image, but the variations have no images
1417
     * With this scenario, you want to show ONLY the product image
1418
     * on the product page, but if one of the variations is added to the
1419
     * cart, then you want to show the product image.
1420
     * This can be achieved bu using the BestAvailable image.
1421
     *
1422
     * @return Image | Null
1423
     */
1424
    public function BestAvailableImage()
1425
    {
1426
        $image = $this->Image();
1427
        if ($image && $image->exists() && file_exists($image->getFullPath())) {
1428
            return $image;
1429
        } elseif ($parent = $this->ParentGroup()) {
1430
            return $parent->BestAvailableImage();
1431
        }
1432
    }
1433
1434
    /*****************************************************
1435
     * Other related products
1436
     *****************************************************/
1437
1438
    /**
1439
     * returns a list of Product Groups that have the products for
1440
     * the CURRENT product group listed as part of their AlsoShowProducts list.
1441
     *
1442
     * EXAMPLE:
1443
     * You can use the AlsoShowProducts to list products by Brand.
1444
     * In general, they are listed under type product groups (e.g. socks, sweaters, t-shirts),
1445
     * and you create a list of separate ProductGroups (brands) that do not have ANY products as children,
1446
     * but link to products using the AlsoShowProducts many_many relation.
1447
     *
1448
     * With the method below you can work out a list of brands that apply to the
1449
     * current product group (e.g. socks come in three brands - namely A, B and C)
1450
     *
1451
     * @return DataList
1452
     */
1453
    public function ProductGroupsFromAlsoShowProducts()
1454
    {
1455
        $parentIDs = array();
1456
        //we need to add the last array to make sure we have some products...
1457
        $myProductsArray = $this->currentInitialProductsAsCachedArray($this->getMyUserPreferencesDefault('FILTER'));
1458
        $rows = array();
1459
        if (count($myProductsArray)) {
1460
            $rows = DB::query('
1461
                SELECT "ProductGroupID"
1462
                FROM "Product_ProductGroups"
1463
                WHERE "ProductID" IN ('.implode(',', $myProductsArray).')
1464
                GROUP BY "ProductGroupID";
1465
            ');
1466
        }
1467
        foreach ($rows as $row) {
1468
            $parentIDs[$row['ProductGroupID']] = $row['ProductGroupID'];
1469
        }
1470
        //just in case
1471
        unset($parentIDs[$this->ID]);
1472
        if (!count($parentIDs)) {
1473
            $parentIDs = array(0 => 0);
1474
        }
1475
1476
        return ProductGroup::get()->filter(array('ID' => $parentIDs, 'ShowInSearch' => 1));
1477
    }
1478
1479
    /**
1480
     * This is the inverse of ProductGroupsFromAlsoShowProducts
1481
     * That is, it list the product groups that a product is primarily listed under (exact parents only)
1482
     * from a "AlsoShow" product List.
1483
     *
1484
     * @return DataList
1485
     */
1486
    public function ProductGroupsFromAlsoShowProductsInverse()
1487
    {
1488
        $alsoShowProductsArray = $this->AlsoShowProducts()
1489
            ->filter($this->getUserSettingsOptionSQL('FILTER', $this->getMyUserPreferencesDefault('FILTER')))
1490
            ->map('ID', 'ID')->toArray();
1491
        $alsoShowProductsArray[0] = 0;
1492
        $parentIDs = Product::get()->filter(array('ID' => $alsoShowProductsArray))->map('ParentID', 'ParentID')->toArray();
1493
        //just in case
1494
        unset($parentIDs[$this->ID]);
1495
        if (! count($parentIDs)) {
1496
            $parentIDs = array(0 => 0);
1497
        }
1498
1499
        return ProductGroup::get()->filter(array('ID' => $parentIDs, 'ShowInMenus' => 1));
1500
    }
1501
1502
    /**
1503
     * given the products for this page,
1504
     * retrieve the parent groups excluding the current one.
1505
     *
1506
     * @return DataList
1507
     */
1508
    public function ProductGroupsParentGroups()
1509
    {
1510
        $arrayOfIDs = $this->currentInitialProductsAsCachedArray($this->getMyUserPreferencesDefault('FILTER')) + array(0 => 0);
1511
        $parentIDs = Product::get()->filter(array('ID' => $arrayOfIDs))->map('ParentID', 'ParentID')->toArray();
1512
        //just in case
1513
        unset($parentIDs[$this->ID]);
1514
        if (! count($parentIDs)) {
1515
            $parentIDs = array(0 => 0);
1516
        }
1517
1518
        return ProductGroup::get()->filter(array('ID' => $parentIDs, 'ShowInSearch' => 1));
1519
    }
1520
1521
    /**
1522
     * returns stage as "" or "_Live".
1523
     *
1524
     * @return string
1525
     */
1526
    protected function getStage()
1527
    {
1528
        $stage = '';
1529
        if (Versioned::current_stage() == 'Live') {
1530
            $stage = '_Live';
1531
        }
1532
1533
        return $stage;
1534
    }
1535
1536
    /*****************************************************
1537
     * STANDARD SS METHODS
1538
     *****************************************************/
1539
1540
    /**
1541
     * tells us if the current page is part of e-commerce.
1542
     *
1543
     * @return bool
1544
     */
1545
    public function IsEcommercePage()
1546
    {
1547
        return true;
1548
    }
1549
1550
    public function onAfterWrite()
1551
    {
1552
        parent::onAfterWrite();
1553
1554
        if ($this->ImageID) {
1555
            if ($normalImage = Image::get()->exclude(array('ClassName' => 'Product_Image'))->byID($this->ImageID)) {
1556
                $normalImage = $normalImage->newClassInstance('Product_Image');
1557
                $normalImage->write();
1558
            }
1559
        }
1560
    }
1561
1562
    public function requireDefaultRecords()
1563
    {
1564
        parent::requireDefaultRecords();
1565
        $urlSegments = ProductGroup::get()->column('URLSegment');
1566
        foreach ($urlSegments as $urlSegment) {
1567
            $counts = array_count_values($urlSegments);
1568
            $hasDuplicates = $counts[$urlSegment]  > 1 ? true : false;
1569
            if ($hasDuplicates) {
1570
                DB::alteration_message('found duplicates for '.$urlSegment, 'deleted');
1571
                $checkForDuplicatesURLSegments = ProductGroup::get()
1572
                    ->filter(array('URLSegment' => $urlSegment));
1573
                if ($checkForDuplicatesURLSegments->count()) {
1574
                    $count = 0;
1575
                    foreach ($checkForDuplicatesURLSegments as $productGroup) {
1576
                        if ($count > 0) {
1577
                            $oldURLSegment = $productGroup->URLSegment;
1578
                            DB::alteration_message(' ... Correcting URLSegment for '.$productGroup->Title.' with ID: '.$productGroup->ID, 'deleted');
1579
                            $productGroup->writeToStage('Stage');
1580
                            $productGroup->publish('Stage', 'Live');
1581
                            $newURLSegment = $productGroup->URLSegment;
1582
                            DB::alteration_message(' ... .... from '.$oldURLSegment.' to '.$newURLSegment, 'created');
1583
                        }
1584
                        $count++;
1585
                    }
1586
                }
1587
            }
1588
        }
1589
    }
1590
1591
    /*****************************************************
1592
     * CACHING
1593
     *****************************************************/
1594
    /**
1595
     *
1596
     * @return bool
1597
     */
1598
    public function AllowCaching()
1599
    {
1600
        return $this->allowCaching;
1601
    }
1602
1603
    /**
1604
     * keeps a cache of the common caching key element
1605
     * @var string
1606
     */
1607
    private static $_product_group_cache_key_cache = null;
1608
1609
    /**
1610
     *
1611
     * @param string $name
0 ignored issues
show
Bug introduced by
There is no parameter named $name. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1612
     * @param string $filterKey
0 ignored issues
show
Bug introduced by
There is no parameter named $filterKey. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1613
     *
1614
     * @return string
1615
     */
1616
    public function cacheKey($cacheKey)
1617
    {
1618
        $cacheKey = $cacheKey.'_'.$this->ID;
1619
        if (self::$_product_group_cache_key_cache === null) {
1620
            self::$_product_group_cache_key_cache = "_PR_"
1621
                .strtotime(Product::get()->max('LastEdited')). "_"
1622
                .Product::get()->count();
1623
            self::$_product_group_cache_key_cache .= "PG_"
1624
                .strtotime(ProductGroup::get()->max('LastEdited')). "_"
1625
                .ProductGroup::get()->count();
1626
            if (class_exists('ProductVariation')) {
1627
                self::$_product_group_cache_key_cache .= "PV_"
1628
                  .strtotime(ProductVariation::get()->max('LastEdited')). "_"
1629
                  .ProductVariation::get()->count();
1630
            }
1631
        }
1632
        $cacheKey .= self::$_product_group_cache_key_cache;
1633
1634
        return $cacheKey;
1635
    }
1636
1637
    /**
1638
     * @var Zend_Cache_Core
1639
     */
1640
    protected $silverstripeCoreCache = null;
1641
1642
    /**
1643
     * Set the cache object to use when storing / retrieving partial cache blocks.
1644
     *
1645
     * @param Zend_Cache_Core $silverstripeCoreCache
1646
     */
1647
    public function setSilverstripeCoreCache($silverstripeCoreCache)
1648
    {
1649
        $this->silverstripeCoreCache = $silverstripeCoreCache;
1650
    }
1651
1652
    /**
1653
     * Get the cache object to use when storing / retrieving stuff in the Silverstripe Cache
1654
     *
1655
     * @return Zend_Cache_Core
1656
     */
1657
    protected function getSilverstripeCoreCache()
1658
    {
1659
        return $this->silverstripeCoreCache ? $this->silverstripeCoreCache : SS_Cache::factory('EcomPG');
1660
    }
1661
1662
    /**
1663
     * saving an object to the.
1664
     *
1665
     * @param string $cacheKey
1666
     *
1667
     * @return mixed
1668
     */
1669
    protected function retrieveObjectStore($cacheKey)
1670
    {
1671
        $cacheKey = $this->cacheKey($cacheKey);
1672
        if ($this->AllowCaching()) {
1673
            $cache = $this->getSilverstripeCoreCache();
1674
            $data = $cache->load($cacheKey);
1675
            if (!$data) {
1676
                return;
1677
            }
1678
            if (! $cache->getOption('automatic_serialization')) {
1679
                $data = @unserialize($data);
1680
            }
1681
1682
            return $data;
1683
        }
1684
1685
        return;
1686
    }
1687
1688
    /**
1689
     * returns true when the data is saved...
1690
     *
1691
     * @param mixed  $data
1692
     * @param string $cacheKey - key under which the data is saved...
1693
     *
1694
     * @return bool
1695
     */
1696
    protected function saveObjectStore($data, $cacheKey)
1697
    {
1698
        $cacheKey = $this->cacheKey($cacheKey);
1699
        if ($this->AllowCaching()) {
1700
            $cache = $this->getSilverstripeCoreCache();
1701
            if (! $cache->getOption('automatic_serialization')) {
1702
                $data = serialize($data);
1703
            }
1704
            $cache->save($data, $cacheKey);
1705
            return true;
1706
        }
1707
1708
        return false;
1709
    }
1710
1711
    /**
1712
     *
1713
     * @param Boolean $isForGroups OPTIONAL
1714
     *
1715
     * @return string
1716
     */
1717
    public function SearchResultsSessionVariable($isForGroups = false)
1718
    {
1719
        $idString = '_'.$this->ID;
1720
        if ($isForGroups) {
1721
            return Config::inst()->get('ProductSearchForm', 'product_session_variable').$idString;
1722
        } else {
1723
            return Config::inst()->get('ProductSearchForm', 'product_group_session_variable').$idString;
1724
        }
1725
    }
1726
1727
    /**
1728
     * cache for result array.
1729
     *
1730
     * @var array
1731
     */
1732
    private static $_result_array = array();
1733
1734
    /**
1735
     * @return array
1736
     */
1737
    public function searchResultsArrayFromSession()
1738
    {
1739
        if (! isset(self::$_result_array[$this->ID]) || self::$_result_array[$this->ID] === null) {
1740
            self::$_result_array[$this->ID] = explode(',', Session::get($this->SearchResultsSessionVariable(false)));
1741
        }
1742
        if (! is_array(self::$_result_array[$this->ID]) || ! count(self::$_result_array[$this->ID])) {
1743
            self::$_result_array[$this->ID] = array(0 => 0);
1744
        }
1745
1746
        return self::$_result_array[$this->ID];
1747
    }
1748
1749
    public function getNumberOfProducts()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
1750
    {
1751
        return Product::get()->filter(array('ParentID' => $this->ID))->count();
1752
    }
1753
}
1754
1755
class ProductGroup_Controller extends Page_Controller
1756
{
1757
    /**
1758
     * standard SS variable.
1759
     *
1760
     * @var array
1761
     */
1762
    private static $allowed_actions = array(
1763
        'debug' => 'ADMIN',
1764
        'filterforgroup' => true,
1765
        'ProductSearchForm' => true,
1766
        'searchresults' => true,
1767
        'resetfilter' => true,
1768
    );
1769
1770
    /**
1771
     * The original Title of this page before filters, etc...
1772
     *
1773
     * @var string
1774
     */
1775
    protected $originalTitle = '';
1776
1777
    /**
1778
     * list of products that are going to be shown.
1779
     *
1780
     * @var DataList
1781
     */
1782
    protected $products = null;
1783
1784
    /**
1785
     * Show all products on one page?
1786
     *
1787
     * @var bool
1788
     */
1789
    protected $showFullList = false;
1790
1791
    /**
1792
     * The group filter that is applied to this page.
1793
     *
1794
     * @var ProductGroup
1795
     */
1796
    protected $filterForGroupObject = null;
1797
1798
    /**
1799
     * Is this a product search?
1800
     *
1801
     * @var bool
1802
     */
1803
    protected $isSearchResults = false;
1804
1805
    /**
1806
     * standard SS method.
1807
     */
1808
    public function init()
1809
    {
1810
        parent::init();
1811
        $this->originalTitle = $this->Title;
1812
        Requirements::themedCSS('ProductGroup', 'ecommerce');
1813
        Requirements::themedCSS('ProductGroupPopUp', 'ecommerce');
1814
        Requirements::javascript('ecommerce/javascript/EcomProducts.js');
1815
        //we save data from get variables...
1816
        $this->saveUserPreferences();
1817
        //makes sure best match only applies to search -i.e. reset otherwise.
1818
        if($this->request->param('Action') !== 'searchresults') {
1819
            $sortKey = $this->getCurrentUserPreferences('SORT');
1820
            if ($sortKey === Config::inst()->get('ProductGroupSearchPage', 'best_match_key')) {
1821
                $this->resetsort();
1822
            }
1823
        }
1824
    }
1825
1826
    /****************************************************
1827
     *  ACTIONS
1828
    /****************************************************/
1829
1830
    /**
1831
     * standard selection of products.
1832
     */
1833
    public function index()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
1834
    {
1835
        //set the filter and the sort...
1836
        $this->addSecondaryTitle();
1837
        $this->products = $this->paginateList($this->ProductsShowable(null));
1838
        if ($this->returnAjaxifiedProductList()) {
1839
            return $this->renderWith('AjaxProductList');
1840
        }
1841
        return array();
1842
    }
1843
1844
    /**
1845
     * cross filter with another product group..
1846
     *
1847
     * e.g. socks (current product group) for brand A or B (the secondary product group)
1848
     *
1849
     * @param HTTPRequest
1850
     */
1851
    public function filterforgroup($request)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
1852
    {
1853
        $this->resetfilter();
1854
        $otherGroupURLSegment = Convert::raw2sql($request->param('ID'));
1855
        $arrayOfIDs = array(0 => 0);
1856
        if ($otherGroupURLSegment) {
1857
            $otherProductGroup = DataObject::get_one(
1858
                'ProductGroup',
1859
                array('URLSegment' => $otherGroupURLSegment)
1860
            );
1861
            if ($otherProductGroup) {
1862
                $this->filterForGroupObject = $otherProductGroup;
1863
                $arrayOfIDs = $otherProductGroup->currentInitialProductsAsCachedArray($this->getMyUserPreferencesDefault('FILTER'));
1864
            }
1865
        }
1866
        $this->addSecondaryTitle();
1867
        $this->products = $this->paginateList($this->ProductsShowable(array('ID' => $arrayOfIDs)));
1868
        if ($this->returnAjaxifiedProductList()) {
1869
            return $this->renderWith('AjaxProductList');
1870
        }
1871
1872
        return array();
1873
    }
1874
1875
1876
    /**
1877
     * get the search results.
1878
     *
1879
     * @param HTTPRequest
1880
     */
1881
    public function searchresults($request)
1882
    {
1883
        $this->isSearchResults = true;
1884
        //filters are irrelevant right now
1885
        $this->resetfilter();
1886
        //get results array
1887
        $resultArray = $this->searchResultsArrayFromSession();
1888
        if ((is_array($resultArray)  && count($resultArray))) {
1889
            //all ok
1890
        } else {
1891
            $resultArray = array(0 => 0);
1892
        }
1893
        $title = ProductSearchForm::get_last_search_phrase();
1894
        if ($title) {
1895
            $title = _t('Ecommerce.SEARCH_FOR', 'search for: ').substr($title, 0, 25);
1896
        }
1897
        $this->addSecondaryTitle($title);
1898
        $this->products = $this->paginateList(
1899
            $this->ProductsShowable(
1900
                ['ID' => $resultArray],
1901
                $this->getSearchResultsDefaultSort($resultArray)
1902
            )
1903
        );
1904
1905
        return array();
1906
    }
1907
1908
    protected function getSearchResultsDefaultSort($idArray, $alternativeSort = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
1909
    {
1910
        if(! $alternativeSort) {
1911
            $sortGetVariable = $this->getSortFilterDisplayNames('SORT', 'getVariable');
1912
            if(! $this->request->getVar($sortGetVariable)) {
1913
                $suggestion = Config::inst()->get('ProductGroupSearchPage', 'best_match_key');
1914
                if($suggestion) {
1915
                    $this->saveUserPreferences(
1916
                        [
1917
                            $sortGetVariable => $suggestion
1918
                        ]
1919
                    );
1920
                    $alternativeSort = $this->createSortStatementFromIDArray($idArray);
1921
                }
1922
            }
1923
        }
1924
        return $alternativeSort;
1925
    }
1926
1927
    /**
1928
     * resets the filter only.
1929
     */
1930
    public function resetfilter()
1931
    {
1932
        $defaultKey = $this->getMyUserPreferencesDefault('FILTER');
1933
        $filterGetVariable = $this->getSortFilterDisplayNames('FILTER', 'getVariable');
1934
        $this->saveUserPreferences(
1935
            array(
1936
                $filterGetVariable => $defaultKey,
1937
            )
1938
        );
1939
1940
        return array();
1941
    }
1942
    /**
1943
     * resets the filter only.
1944
     */
1945
    public function resetsort()
1946
    {
1947
        $defaultKey = $this->getMyUserPreferencesDefault('SORT');
1948
        $sortGetVariable = $this->getSortFilterDisplayNames('SORT', 'getVariable');
1949
        $this->saveUserPreferences(
1950
            array(
1951
                $sortGetVariable => $defaultKey,
1952
            )
1953
        );
1954
1955
        return array();
1956
    }
1957
1958
    /****************************************************
1959
     *  TEMPLATE METHODS PRODUCTS
1960
    /****************************************************/
1961
1962
    /**
1963
     * Return the products for this group.
1964
     * This is the call that is made from the template...
1965
     * The actual final products being shown.
1966
     *
1967
     * @return DataList
1968
     **/
1969
    public function Products()
1970
    {
1971
        //IMPORTANT!
1972
        //two universal actions!
1973
        $this->addSecondaryTitle();
1974
        $this->cachingRelatedJavascript();
1975
1976
        //save products to session for later use
1977
        $stringOfIDs = '';
1978
        $array = $this->getProductsThatCanBePurchasedArray();
1979
        if (is_array($array)) {
1980
            $stringOfIDs = implode(',', $array);
1981
        }
1982
        //save list for future use
1983
        Session::set(EcommerceConfig::get('ProductGroup', 'session_name_for_product_array'), $stringOfIDs);
1984
1985
        return $this->products;
1986
    }
1987
1988
    /**
1989
     * you can overload this function of ProductGroup Extensions.
1990
     *
1991
     * @return bool
1992
     */
1993
    protected function returnAjaxifiedProductList()
1994
    {
1995
        return Director::is_ajax() ? true : false;
1996
    }
1997
1998
    /**
1999
     * is the product list cache-able?
2000
     *
2001
     * @return bool
2002
     */
2003
    public function ProductGroupListAreCacheable()
2004
    {
2005
        if ($this->productListsHTMLCanBeCached()) {
2006
            //exception 1
2007
            if ($this->IsSearchResults()) {
2008
                return false;
2009
            }
2010
            //exception 2
2011
            $currentOrder = ShoppingCart::current_order();
2012
            if ($currentOrder->getHasAlternativeCurrency()) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return !$currentOrder->g...sAlternativeCurrency();.
Loading history...
2013
                return false;
2014
            }
2015
            //can be cached...
2016
            return true;
2017
        }
2018
2019
        return false;
2020
    }
2021
2022
    /**
2023
     * is the product list ajaxified.
2024
     *
2025
     * @return bool
2026
     */
2027
    public function ProductGroupListAreAjaxified()
2028
    {
2029
        return $this->IsSearchResults() ? false : true;
2030
    }
2031
2032
    /**
2033
     * Unique caching key for the product list...
2034
     *
2035
     * @return string | Null
2036
     */
2037
    public function ProductGroupListCachingKey()
2038
    {
2039
        if ($this->ProductGroupListAreCacheable()) {
2040
            $displayKey = $this->getCurrentUserPreferences('DISPLAY');
2041
            $filterKey = $this->getCurrentUserPreferences('FILTER');
2042
            $filterForGroupKey = $this->filterForGroupObject ? $this->filterForGroupObject->ID : 0;
2043
            $sortKey = $this->getCurrentUserPreferences('SORT');
2044
            $pageStart = $this->request->getVar('start') ? intval($this->request->getVar('start')) : 0;
2045
            $isFullList = $this->IsShowFullList() ? 'Y' : 'N';
2046
            return $this->cacheKey(
2047
                implode(
2048
                    '_',
2049
                    array(
2050
                        $displayKey,
2051
                        $filterKey,
2052
                        $filterForGroupKey,
2053
                        $sortKey,
2054
                        $pageStart,
2055
                        $isFullList,
2056
                    )
2057
                )
2058
            );
2059
        }
2060
2061
        return;
2062
    }
2063
2064
    /**
2065
     * adds Javascript to the page to make it work when products are cached.
2066
     */
2067
    public function CachingRelatedJavascript()
2068
    {
2069
        if ($this->ProductGroupListAreAjaxified()) {
2070
            Requirements::customScript(
2071
                "
2072
                    if(typeof EcomCartOptions === 'undefined') {
2073
                        var EcomCartOptions = {};
2074
                    }
2075
                    EcomCartOptions.ajaxifyProductList = true;
2076
                    EcomCartOptions.ajaxifiedListHolderSelector = '#".$this->AjaxDefinitions()->ProductListHolderID()."';
2077
                    EcomCartOptions.ajaxifiedListAdjusterSelectors = '.".$this->AjaxDefinitions()->ProductListAjaxifiedLinkClassName()."';
2078
                    EcomCartOptions.hiddenPageTitleID = '#".$this->AjaxDefinitions()->HiddenPageTitleID()."';
2079
                ",
2080
                'cachingRelatedJavascript_AJAXlist'
2081
            );
2082
        } else {
2083
            Requirements::customScript(
2084
                "
2085
                    if(typeof EcomCartOptions === 'undefined') {
2086
                        var EcomCartOptions = {};
2087
                    }
2088
                    EcomCartOptions.ajaxifyProductList = false;
2089
                ",
2090
                'cachingRelatedJavascript_AJAXlist'
2091
            );
2092
        }
2093
        $currentOrder = ShoppingCart::current_order();
2094
        if ($currentOrder->TotalItems(true)) {
2095
            $responseClass = EcommerceConfig::get('ShoppingCart', 'response_class');
2096
            $obj = new $responseClass();
2097
            $obj->setIncludeHeaders(false);
2098
            $json = $obj->ReturnCartData();
2099
            Requirements::customScript(
2100
                "
2101
                    if(typeof EcomCartOptions === 'undefined') {
2102
                        var EcomCartOptions = {};
2103
                    }
2104
                    EcomCartOptions.initialData= ".$json.";
2105
                ",
2106
                'cachingRelatedJavascript_JSON'
2107
            );
2108
        }
2109
    }
2110
2111
    /**
2112
     * you can overload this function of ProductGroup Extensions.
2113
     *
2114
     * @return bool
2115
     */
2116
    protected function productListsHTMLCanBeCached()
2117
    {
2118
        return Config::inst()->get('ProductGroup', 'actively_check_for_can_purchase') ? false : true;
2119
    }
2120
2121
    /*****************************************************
2122
     * DATALIST: totals, number per page, etc..
2123
     *****************************************************/
2124
2125
    /**
2126
     * returns the total numer of products (before pagination).
2127
     *
2128
     * @return bool
2129
     **/
2130
    public function TotalCountGreaterThanOne($greaterThan = 1)
2131
    {
2132
        return $this->TotalCount() > $greaterThan;
2133
    }
2134
2135
    /**
2136
     * have the ProductsShowable been limited.
2137
     *
2138
     * @return bool
2139
     **/
2140
    public function TotalCountGreaterThanMax()
2141
    {
2142
        return $this->RawCount() >  $this->TotalCount();
2143
    }
2144
2145
    /****************************************************
2146
     *  TEMPLATE METHODS MENUS AND SIDEBARS
2147
    /****************************************************/
2148
2149
    /**
2150
     * title without additions.
2151
     *
2152
     * @return string
2153
     */
2154
    public function OriginalTitle()
2155
    {
2156
        return $this->originalTitle;
2157
    }
2158
    /**
2159
     * This method can be extended to show products in the side bar.
2160
     */
2161
    public function SidebarProducts()
2162
    {
2163
        return;
2164
    }
2165
2166
    /**
2167
     * returns child product groups for use in
2168
     * 'in this section'. For example the vegetable Product Group
2169
     * May have listed here: Carrot, Cabbage, etc...
2170
     *
2171
     * @return ArrayList (ProductGroups)
2172
     */
2173
    public function MenuChildGroups()
2174
    {
2175
        return $this->ChildGroups(2, '"ShowInMenus" = 1');
2176
    }
2177
2178
    /**
2179
     * After a search is conducted you may end up with a bunch
2180
     * of recommended product groups. They will be returned here...
2181
     * We sort the list in the order that it is provided.
2182
     *
2183
     * @return DataList | Null (ProductGroups)
2184
     */
2185
    public function SearchResultsChildGroups()
2186
    {
2187
        $groupArray = explode(',', Session::get($this->SearchResultsSessionVariable($isForGroup = true)));
2188
        if (is_array($groupArray) && count($groupArray)) {
2189
            $sortStatement = $this->createSortStatementFromIDArray($groupArray, 'ProductGroup');
2190
2191
            return ProductGroup::get()->filter(array('ID' => $groupArray, 'ShowInSearch' => 1))->sort($sortStatement);
2192
        }
2193
2194
        return;
2195
    }
2196
2197
    /****************************************************
2198
     *  Search Form Related controllers
2199
    /****************************************************/
2200
2201
    /**
2202
     * returns a search form to search current products.
2203
     *
2204
     * @return ProductSearchForm object
2205
     */
2206
    public function ProductSearchForm()
2207
    {
2208
        $onlySearchTitle = $this->originalTitle;
2209
        if ($this->dataRecord instanceof ProductGroupSearchPage) {
2210
            if ($this->HasSearchResults()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->HasSearchResults() of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2211
                $onlySearchTitle = 'Last Search Results';
2212
            }
2213
        }
2214
        $form = ProductSearchForm::create(
2215
            $this,
2216
            'ProductSearchForm',
2217
            $onlySearchTitle,
2218
            $this->currentInitialProducts(null, $this->getMyUserPreferencesDefault('FILTER'))
2219
        );
2220
        $filterGetVariable = $this->getSortFilterDisplayNames('FILTER', 'getVariable');
2221
        $sortGetVariable = $this->getSortFilterDisplayNames('SORT', 'getVariable');
2222
        $additionalGetParameters =
2223
            $filterGetVariable.'='.$this->getMyUserPreferencesDefault('FILTER').'&'.
2224
            $sortGetVariable.'='.Config::inst()->get('ProductGroupSearchPage', 'best_match_key');
2225
        $form->setAdditionalGetParameters($additionalGetParameters);
2226
2227
        return $form;
2228
    }
2229
2230
    /**
2231
     * Does this page have any search results?
2232
     * If search was carried out without returns
2233
     * then it returns zero (false).
2234
     *
2235
     * @return int | false
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2236
     */
2237
    public function HasSearchResults()
2238
    {
2239
        $resultArray = $this->searchResultsArrayFromSession();
2240
        if ($resultArray) {
2241
            $count = count($resultArray) - 1;
2242
2243
            return $count ? $count : false;
2244
        }
2245
2246
        return false;
2247
    }
2248
2249
    /**
2250
     * Should the product search form be shown immediately?
2251
     *
2252
     * @return bool
2253
     */
2254
    public function ShowSearchFormImmediately()
2255
    {
2256
        if ($this->IsSearchResults()) {
2257
            return true;
2258
        }
2259
        if ((!$this->products) || ($this->products && $this->products->count())) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return !(!$this->product...is->products->count());.
Loading history...
2260
            return false;
2261
        }
2262
2263
        return true;
2264
    }
2265
2266
    /**
2267
     * Show a search form on this page?
2268
     *
2269
     * @return bool
2270
     */
2271
    public function ShowSearchFormAtAll()
2272
    {
2273
        return true;
2274
    }
2275
2276
    /**
2277
     * Is the current page a display of search results.
2278
     *
2279
     * This does not mean that something is actively being search for,
2280
     * it could also be just "showing the search results"
2281
     *
2282
     * @return bool
2283
     */
2284
    public function IsSearchResults()
2285
    {
2286
        return $this->isSearchResults;
2287
    }
2288
2289
    /**
2290
     * Is there something actively being searched for?
2291
     *
2292
     * This is different from IsSearchResults.
2293
     *
2294
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2295
     */
2296
    public function ActiveSearchTerm()
2297
    {
2298
        $data = Session::get(Config::inst()->get('ProductSearchForm', 'form_data_session_variable'));
2299
        if (!empty($data['Keyword'])) {
2300
            return $this->IsSearchResults();
2301
        }
2302
    }
2303
2304
    /****************************************************
2305
     *  Filter / Sort / Display related controllers
2306
    /****************************************************/
2307
2308
    /**
2309
     * Do we show all products on one page?
2310
     *
2311
     * @return bool
2312
     */
2313
    public function ShowFiltersAndDisplayLinks()
2314
    {
2315
        if ($this->TotalCountGreaterThanOne()) {
2316
            if ($this->HasFilters()) {
2317
                return true;
2318
            }
2319
            if ($this->DisplayLinks()) {
2320
                return true;
2321
            }
2322
        }
2323
2324
        return false;
2325
    }
2326
2327
    /**
2328
     * Do we show the sort links.
2329
     *
2330
     * A bit arbitrary to say three,
2331
     * but there is not much point to sort three or less products
2332
     *
2333
     * @return bool
2334
     */
2335
    public function ShowSortLinks($minimumCount = 3)
2336
    {
2337
        if ($this->TotalCountGreaterThanOne($minimumCount)) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return $this->TotalCount...ThanOne($minimumCount);.
Loading history...
2338
            return true;
2339
        }
2340
2341
        return false;
2342
    }
2343
2344
    /**
2345
     * Is there a special filter operating at the moment?
2346
     * Is the current filter the default one (return inverse!)?
2347
     *
2348
     * @return bool
2349
     */
2350
    public function HasFilter()
2351
    {
2352
        return
2353
            $this->getCurrentUserPreferences('FILTER') != $this->getMyUserPreferencesDefault('FILTER')
2354
            ||
2355
            $this->filterForGroupObject;
2356
    }
2357
2358
    /**
2359
     * Is there a special sort operating at the moment?
2360
     * Is the current sort the default one (return inverse!)?
2361
     *
2362
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2363
     */
2364
    public function HasSort()
2365
    {
2366
        $sort = $this->getCurrentUserPreferences('SORT');
2367
        if ($sort != $this->getMyUserPreferencesDefault('SORT')) {
2368
            return true;
2369
        }
2370
    }
2371
2372
    /**
2373
     * @return boolean
2374
     */
2375
    public function HasFilterOrSort()
2376
    {
2377
        return $this->HasFilter() || $this->HasSort();
2378
    }
2379
2380
    /**
2381
     * @return boolean
2382
     */
2383
    public function HasFilterOrSortFullList()
2384
    {
2385
        return $this->HasFilterOrSort() || $this->IsShowFullList();
2386
    }
2387
2388
    /**
2389
     * are filters available?
2390
     * we check one at the time so that we do the least
2391
     * amount of DB queries.
2392
     *
2393
     * @return bool
2394
     */
2395
    public function HasFilters()
2396
    {
2397
        $countFilters = $this->FilterLinks()->count();
2398
        if ($countFilters > 1) {
2399
            return true;
2400
        }
2401
        $countGroupFilters = $this->ProductGroupFilterLinks()->count();
2402
        if ($countGroupFilters > 1) {
2403
            return true;
2404
        }
2405
        if ($countFilters + $countGroupFilters > 1) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return $countFilters + $countGroupFilters > 1;.
Loading history...
2406
            return true;
2407
        }
2408
2409
        return false;
2410
    }
2411
2412
    /**
2413
     * Do we show all products on one page?
2414
     *
2415
     * @return bool
2416
     */
2417
    public function IsShowFullList()
2418
    {
2419
        return $this->showFullList;
2420
    }
2421
2422
    /**
2423
     * returns the current filter applied to the list
2424
     * in a human readable string.
2425
     *
2426
     * @return string
2427
     */
2428
    public function CurrentDisplayTitle()
2429
    {
2430
        $displayKey = $this->getCurrentUserPreferences('DISPLAY');
2431
        if ($displayKey != $this->getMyUserPreferencesDefault('DISPLAY')) {
2432
            return $this->getUserPreferencesTitle('DISPLAY', $displayKey);
2433
        }
2434
    }
2435
2436
    /**
2437
     * returns the current filter applied to the list
2438
     * in a human readable string.
2439
     *
2440
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2441
     */
2442
    public function CurrentFilterTitle()
2443
    {
2444
        $filterKey = $this->getCurrentUserPreferences('FILTER');
2445
        $filters = array();
2446
        if ($filterKey != $this->getMyUserPreferencesDefault('FILTER')) {
2447
            $filters[] = $this->getUserPreferencesTitle('FILTER', $filterKey);
2448
        }
2449
        if ($this->filterForGroupObject) {
2450
            $filters[] = $this->filterForGroupObject->MenuTitle;
2451
        }
2452
        if (count($filters)) {
2453
            return implode(', ', $filters);
2454
        }
2455
    }
2456
2457
    /**
2458
     * returns the current sort applied to the list
2459
     * in a human readable string.
2460
     *
2461
     * @return string
2462
     */
2463
    public function CurrentSortTitle()
2464
    {
2465
        $sortKey = $this->getCurrentUserPreferences('SORT');
2466
        if ($sortKey != $this->getMyUserPreferencesDefault('SORT')) {
2467
            return $this->getUserPreferencesTitle('SORT', $sortKey);
2468
        }
2469
    }
2470
2471
    /**
2472
     * short-cut for getMyUserPreferencesDefault("DISPLAY")
2473
     * for use in templtes.
2474
     *
2475
     * @return string - key
2476
     */
2477
    public function MyDefaultDisplayStyle()
2478
    {
2479
        return $this->getMyUserPreferencesDefault('DISPLAY');
2480
    }
2481
2482
    /**
2483
     * Number of entries per page limited by total number of pages available...
2484
     *
2485
     * @return int
2486
     */
2487
    public function MaxNumberOfProductsPerPage()
2488
    {
2489
        return $this->MyNumberOfProductsPerPage() > $this->TotalCount() ? $this->TotalCount() : $this->MyNumberOfProductsPerPage();
2490
    }
2491
2492
    /****************************************************
2493
     *  TEMPLATE METHODS FILTER LINK
2494
    /****************************************************/
2495
2496
    /**
2497
     * Provides a ArrayList of links for filters products.
2498
     *
2499
     * @return ArrayList( ArrayData(Name, Link, SelectKey, Current (boolean), LinkingMode))
0 ignored issues
show
Documentation introduced by
The doc-type ArrayList( could not be parsed: Expected "|" or "end of type", but got "(" at position 9. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
2500
     */
2501
    public function FilterLinks()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
2502
    {
2503
        $cacheKey = 'FilterLinks_'.($this->filterForGroupObject ? $this->filterForGroupObject->ID : 0);
2504
        if ($list = $this->retrieveObjectStore($cacheKey)) {
2505
            //do nothing
2506
        } else {
2507
            $list = $this->userPreferencesLinks('FILTER');
2508
            foreach ($list as $obj) {
0 ignored issues
show
Bug introduced by
The expression $list of type null|this<ProductGroup_Controller> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
2509
                $key = $obj->SelectKey;
2510
                if ($key != $this->getMyUserPreferencesDefault('FILTER')) {
2511
                    $count = count($this->currentInitialProductsAsCachedArray($key));
2512
                    if ($count == 0) {
2513
                        $list->remove($obj);
2514
                    } else {
2515
                        $obj->Count = $count;
2516
                    }
2517
                }
2518
            }
2519
            $this->saveObjectStore($list, $cacheKey);
2520
        }
2521
        $selectedItem = $this->getCurrentUserPreferences('FILTER');
2522
        foreach ($list as $obj) {
2523
            $canHaveCurrent = true;
2524
            if ($this->filterForGroupObject) {
2525
                $canHaveCurrent = false;
2526
            }
2527
            $obj->Current = $selectedItem == $obj->SelectKey && $canHaveCurrent ? true : false;
2528
            $obj->LinkingMode = $obj->Current ? 'current' : 'link';
2529
            $obj->Ajaxify = true;
2530
        }
2531
2532
        return $list;
2533
    }
2534
2535
    /**
2536
     * returns a list of items (with links).
2537
     *
2538
     * @return ArrayList( ArrayData(Name, FilterLink,  SelectKey, Current (boolean), LinkingMode))
0 ignored issues
show
Documentation introduced by
The doc-type ArrayList( could not be parsed: Expected "|" or "end of type", but got "(" at position 9. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
2539
     */
2540
    public function ProductGroupFilterLinks()
2541
    {
2542
        if ($array = $this->retrieveObjectStore('ProductGroupFilterLinks')) {
2543
            //do nothing
2544
        } else {
2545
            $arrayOfItems = array();
2546
2547
            $baseArray = $this->currentInitialProductsAsCachedArray($this->getMyUserPreferencesDefault('FILTER'));
2548
2549
            //also show
2550
            $items = $this->ProductGroupsFromAlsoShowProducts();
2551
            $arrayOfItems = array_merge($arrayOfItems, $this->productGroupFilterLinksCount($items, $baseArray, true));
2552
            //also show inverse
2553
            $items = $this->ProductGroupsFromAlsoShowProductsInverse();
2554
            $arrayOfItems = array_merge($arrayOfItems, $this->productGroupFilterLinksCount($items, $baseArray, true));
2555
2556
            //parent groups
2557
            $items = $this->ProductGroupsParentGroups();
2558
            $arrayOfItems = array_merge($arrayOfItems, $this->productGroupFilterLinksCount($items, $baseArray, true));
2559
2560
            //child groups
2561
            $items = $this->MenuChildGroups();
2562
            $arrayOfItems = array_merge($arrayOfItems, $this->productGroupFilterLinksCount($items, $baseArray, true));
2563
2564
            ksort($arrayOfItems);
2565
            $array = array();
2566
            foreach ($arrayOfItems as $arrayOfItem) {
2567
                $array[] = $this->makeArrayItem($arrayOfItem);
2568
            }
2569
            $this->saveObjectStore($array, 'ProductGroupFilterLinks');
2570
        }
2571
        $arrayList = ArrayList::create();
2572
        foreach ($array as $item) {
2573
            $arrayList->push(ArrayData::create($item));
2574
        }
2575
2576
        return $arrayList;
2577
    }
2578
2579
2580
    /**
2581
     * @see ProductGroupFilterLinks
2582
     * same as ProductGroupFilterLinks, but with originating Object...
2583
     *
2584
     * @return ArrayList
2585
     */
2586
    public function ProductGroupFilterOriginalObjects()
2587
    {
2588
        $links = $this->ProductGroupFilterLinks();
2589
        // /print_r($links);
2590
        foreach ($links as $linkItem) {
2591
            $className = $linkItem->ClassName;
2592
            $id = $linkItem->ID;
2593
            if ($className && $id) {
2594
                $object = $className::get()->byID($id);
2595
                $linkItem->Object = $object;
2596
            }
2597
        }
2598
2599
2600
        return $links;
2601
    }
2602
2603
    /**
2604
     * counts the total number in the combination....
2605
     *
2606
     * @param DataList $items     - list of
2607
     * @param Arary    $baseArray - list of products on the current page
2608
     *
2609
     * @return array
2610
     */
2611
    protected function productGroupFilterLinksCount($items, $baseArray, $ajaxify = true)
2612
    {
2613
        $array = array();
2614
        if ($items && $items->count()) {
2615
            foreach ($items as $item) {
2616
                $arrayOfIDs = $item->currentInitialProductsAsCachedArray($this->getMyUserPreferencesDefault('FILTER'));
2617
                $newArray = array_intersect_key(
2618
                    $arrayOfIDs,
2619
                    $baseArray
2620
                );
2621
                $count = count($newArray);
2622
                if ($count) {
2623
                    $array[$item->Title] = array(
2624
                        'Item' => $item,
2625
                        'Count' => $count,
2626
                        'Ajaxify' => $ajaxify,
2627
                    );
2628
                }
2629
            }
2630
        }
2631
2632
        return $array;
2633
    }
2634
2635
    /**
2636
     * @param array itemInArray (Item, Count, UserFilterAction)
2637
     *
2638
     * @return ArrayData
0 ignored issues
show
Documentation introduced by
Should the return type not be array?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2639
     */
2640
    protected function makeArrayItem($itemInArray)
2641
    {
2642
        $item = $itemInArray['Item'];
2643
        $count = $itemInArray['Count'];
2644
        $ajaxify = $itemInArray['Ajaxify'];
2645
        $filterForGroupObjectID = $this->filterForGroupObject ? $this->filterForGroupObject->ID : 0;
2646
        $isCurrent = ($item->ID == $filterForGroupObjectID ? true : false);
2647
        if ($ajaxify) {
2648
            $link = $this->Link($item->FilterForGroupLinkSegment());
2649
        } else {
2650
            $link = $item->Link();
2651
        }
2652
        return array(
2653
            'ID' => $item->ID,
2654
            'ClassName' => $item->ClassName,
2655
            'Title' => $item->Title,
2656
            'Count' => $count,
2657
            'SelectKey' => $item->URLSegment,
2658
            'Current' => $isCurrent ? true : false,
2659
            'MyLinkingMode' => $isCurrent ? 'current' : 'link',
2660
            'FilterLink' => $link,
2661
            'Ajaxify' => $ajaxify ? true : false,
2662
        );
2663
    }
2664
2665
    /**
2666
     * Provides a ArrayList of links for sorting products.
2667
     */
2668
    public function SortLinks()
2669
    {
2670
2671
        $list = $this->userPreferencesLinks('SORT');
2672
        $selectedItem = $this->getCurrentUserPreferences('SORT');
2673
        if ($list) {
2674
            foreach ($list as $obj) {
2675
                $obj->Current = $selectedItem == $obj->SelectKey ? true : false;
2676
                $obj->LinkingMode = $obj->Current ? 'current' : 'link';
2677
                $obj->Ajaxify = true;
2678
            }
2679
2680
            return $list;
2681
        }
2682
    }
2683
2684
    /**
2685
     * Provides a ArrayList for displaying display links.
2686
     */
2687
    public function DisplayLinks()
2688
    {
2689
        $list = $this->userPreferencesLinks('DISPLAY');
2690
        $selectedItem = $this->getCurrentUserPreferences('DISPLAY');
2691
        if ($list) {
2692
            foreach ($list as $obj) {
2693
                $obj->Current = $selectedItem == $obj->SelectKey ? true : false;
2694
                $obj->LinkingMode = $obj->Current ? 'current' : 'link';
2695
                $obj->Ajaxify = true;
2696
            }
2697
2698
            return $list;
2699
        }
2700
    }
2701
2702
    /**
2703
     * The link that Google et al. need to index.
2704
     * @return string
2705
     */
2706
    public function CanonicalLink()
2707
    {
2708
        $link = $this->ListAllLink();
2709
        $this->extend('UpdateCanonicalLink', $link);
2710
2711
        return $link;
2712
    }
2713
2714
2715
    /**
2716
     * Link that returns a list of all the products
2717
     * for this product group as a simple list.
2718
     *
2719
     * @return string
2720
     */
2721
    public function ListAllLink()
2722
    {
2723
        if ($this->filterForGroupObject) {
2724
            return $this->Link('filterforgroup/'.$this->filterForGroupObject->URLSegment).'?showfulllist=1';
2725
        } else {
2726
            return $this->Link().'?showfulllist=1';
2727
        }
2728
    }
2729
2730
    /**
2731
     * Link that returns a list of all the products
2732
     * for this product group as a simple list.
2733
     *
2734
     * @return string
2735
     */
2736
    public function ListAFewLink()
2737
    {
2738
        return str_replace('?showfulllist=1', '', $this->ListAllLink());
2739
    }
2740
2741
    /**
2742
     * Link that returns a list of all the products
2743
     * for this product group as a simple list.
2744
     *
2745
     * It resets everything - not just filter....
2746
     *
2747
     * @return string
2748
     */
2749
    public function ResetPreferencesLink($escapedAmpersands = true)
2750
    {
2751
        $ampersand = '&';
2752
        if ($escapedAmpersands) {
2753
            $ampersand = '&amp;';
2754
        }
2755
        $getVariableNameFilter = $this->getSortFilterDisplayNames('FILTER', 'getVariable');
2756
        $getVariableNameSort = $this->getSortFilterDisplayNames('SORT', 'getVariable');
2757
2758
        return $this->Link().'?'.
2759
            $getVariableNameFilter.'='.$this->getMyUserPreferencesDefault('FILTER').$ampersand.
2760
            $getVariableNameSort.'='.$this->getMyUserPreferencesDefault('SORT').$ampersand.
2761
            'reload=1';
2762
    }
2763
2764
    /**
2765
     * Link to the search results.
2766
     *
2767
     * @return string
2768
     */
2769
    public function SearchResultLink()
2770
    {
2771
        if ($this->HasSearchResults() && !$this->isSearchResults) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->HasSearchResults() of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2772
            return $this->Link('searchresults');
2773
        }
2774
    }
2775
2776
    /****************************************************
2777
     *  INTERNAL PROCESSING: PRODUCT LIST
2778
    /****************************************************/
2779
2780
    /**
2781
     * turns full list into paginated list.
2782
     *
2783
     * @param SS_List
2784
     *
2785
     * @return PaginatedList
0 ignored issues
show
Documentation introduced by
Should the return type not be ProductGroup_Controller|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2786
     */
2787
    protected function paginateList(SS_List $list)
2788
    {
2789
        if ($list && $list->count()) {
2790
            if ($this->IsShowFullList()) {
2791
                $obj = PaginatedList::create($list, $this->request);
2792
                $obj->setPageLength(EcommerceConfig::get('ProductGroup', 'maximum_number_of_products_to_list') + 1);
2793
2794
                return $obj;
2795
            } else {
2796
                $obj = PaginatedList::create($list, $this->request);
2797
                $obj->setPageLength($this->MyNumberOfProductsPerPage());
2798
2799
                return $obj;
2800
            }
2801
        }
2802
    }
2803
2804
    /****************************************************
2805
     *  INTERNAL PROCESSING: USER PREFERENCES
2806
    /****************************************************/
2807
2808
    /**
2809
     * Checks out a bunch of $_GET variables
2810
     * that are used to work out user preferences
2811
     * Some of these are saved to session.
2812
     *
2813
     * @param array $overrideArray - override $_GET variable settings
2814
     */
2815
    protected function saveUserPreferences($overrideArray = array())
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
2816
    {
2817
        //save sort - filter - display
2818
        $sortFilterDisplayNames = $this->getSortFilterDisplayNames();
2819
        foreach ($sortFilterDisplayNames as $type => $oneTypeArray) {
2820
            $getVariableName = $oneTypeArray['getVariable'];
2821
            $sessionName = $oneTypeArray['sessionName'];
2822
            if (isset($overrideArray[$getVariableName])) {
2823
                $newPreference = $overrideArray[$getVariableName];
2824
            } else {
2825
                $newPreference = $this->request->getVar($getVariableName);
2826
            }
2827
            if ($newPreference) {
2828
                $optionsVariableName = $oneTypeArray['configName'];
2829
                $options = EcommerceConfig::get($this->ClassName, $optionsVariableName);
2830
                if (isset($options[$newPreference])) {
2831
                    Session::set('ProductGroup_'.$sessionName, $newPreference);
2832
                    //save in model as well...
2833
                }
2834
            } else {
2835
                $newPreference = Session::get('ProductGroup_'.$sessionName);
2836
            }
2837
            //save data in model...
2838
            if($newPreference) {
2839
                $this->setCurrentUserPreference($type, $newPreference);
2840
            }
2841
        }
2842
        /* save URLSegments in model
2843
        $this->setCurrentUserPreference(
2844
            "URLSegments",
2845
            array(
2846
                "Action" => $this->request->param("Action"),
2847
                "ID" => $this->request->param("ID")
2848
            )
2849
        );
2850
        */
2851
2852
        //clearing data..
2853
        if ($this->request->getVar('reload')) {
2854
            //reset other session variables...
2855
            Session::set($this->SearchResultsSessionVariable(false), '');
2856
            Session::set($this->SearchResultsSessionVariable(true), '');
2857
2858
            return $this->redirect($this->Link());
2859
        }
2860
2861
        //full list ....
2862
        if ($this->request->getVar('showfulllist')) {
2863
            $this->showFullList = true;
2864
        }
2865
    }
2866
2867
    /**
2868
     * Checks for the most applicable user preferences for this user:
2869
     * 1. session value
2870
     * 2. getMyUserPreferencesDefault.
2871
     *
2872
     * @param string $type - FILTER | SORT | DISPLAY
2873
     *
2874
     * @return string
2875
     *
2876
     * @todo: move to controller?
2877
     */
2878
    protected function getCurrentUserPreferences($type)
2879
    {
2880
        $sessionName = $this->getSortFilterDisplayNames($type, 'sessionName');
2881
        if ($sessionValue = Session::get('ProductGroup_'.$sessionName)) {
2882
            $key = Convert::raw2sql($sessionValue);
2883
        } else {
2884
            $key = $this->getMyUserPreferencesDefault($type);
2885
        }
2886
        $key = $this->getBestKeyAndValidateKey($type, $key);
2887
2888
        return $key;
2889
    }
2890
2891
    /**
2892
     * Provides a dataset of links for a particular user preference.
2893
     *
2894
     * @param string $type SORT | FILTER | DISPLAY - e.g. sort_options
2895
     *
2896
     * @return ArrayList( ArrayData(Name, Link,  SelectKey, Current (boolean), LinkingMode))
0 ignored issues
show
Documentation introduced by
The doc-type ArrayList( could not be parsed: Expected "|" or "end of type", but got "(" at position 9. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
2897
     */
2898
    protected function userPreferencesLinks($type)
2899
    {
2900
        //get basics
2901
        $sortFilterDisplayNames = $this->getSortFilterDisplayNames();
2902
        $options = $this->getConfigOptions($type);
2903
2904
        //if there is only one option then do not bother
2905
        if (count($options) < 2) {
2906
            return;
2907
        }
2908
2909
        //get more config names
2910
        $translationCode = $sortFilterDisplayNames[$type]['translationCode'];
2911
        $getVariableName = $sortFilterDisplayNames[$type]['getVariable'];
2912
        $arrayList = ArrayList::create();
2913
        if (count($options)) {
2914
            foreach ($options as $key => $array) {
2915
                //$isCurrent = ($key == $selectedItem) ? true : false;
2916
2917
                $link = '?'.$getVariableName."=$key";
2918
                if ($type == 'FILTER') {
2919
                    $link = $this->Link().$link;
2920
                } else {
2921
                    $link = $this->request->getVar('url').$link;
2922
                }
2923
                $arrayList->push(ArrayData::create(array(
2924
                    'Name' => _t('ProductGroup.'.$translationCode.strtoupper(str_replace(' ', '', $array['Title'])), $array['Title']),
2925
                    'Link' => $link,
2926
                    'SelectKey' => $key,
2927
                    //we add current at runtime, so we can store the object without current set...
2928
                    //'Current' => $isCurrent,
2929
                    //'LinkingMode' => $isCurrent ? "current" : "link"
2930
                )));
2931
            }
2932
        }
2933
2934
        return $arrayList;
2935
    }
2936
2937
    /****************************************************
2938
     *  INTERNAL PROCESSING: TITLES
2939
    /****************************************************/
2940
2941
    /**
2942
     * variable to make sure secondary title only gets
2943
     * added once.
2944
     *
2945
     * @var bool
2946
     */
2947
    protected $secondaryTitleHasBeenAdded = false;
2948
2949
    /**
2950
     * add a secondary title to the main title
2951
     * in case there is, for example, a filter applied
2952
     * e.g. Socks | MyBrand.
2953
     *
2954
     * @param string
2955
     */
2956
    protected function addSecondaryTitle($secondaryTitle = '')
2957
    {
2958
        $pipe = _t('ProductGroup.TITLE_SEPARATOR', ' | ');
2959
        if (! $this->secondaryTitleHasBeenAdded) {
2960
            if (trim($secondaryTitle)) {
2961
                $secondaryTitle = $pipe.$secondaryTitle;
2962
            }
2963
            if ($this->IsSearchResults()) {
2964
                $array = $this->searchResultsArrayFromSession();
2965
                $count = count($array);
2966
                if ($count > 4) {
2967
                    if($count < EcommerceConfig::get('ProductGroup', 'maximum_number_of_products_to_list_for_search')) {
2968
                        $toAdd = $count. ' '._t('ProductGroup.PRODUCTS_FOUND', 'Products Found');
2969
                        $secondaryTitle .= $this->cleanSecondaryTitleForAddition($pipe, $toAdd);
2970
                    }
2971
                } else {
2972
                    $toAdd = _t('ProductGroup.SEARCH_RESULTS', 'Search Results');
2973
                    $secondaryTitle .= $this->cleanSecondaryTitleForAddition($pipe, $toAdd);
2974
                }
2975
            }
2976
            if (is_object($this->filterForGroupObject)) {
2977
                $toAdd = $this->filterForGroupObject->Title;
2978
                $secondaryTitle .= $this->cleanSecondaryTitleForAddition($pipe, $toAdd);
2979
            }
2980
            $pagination = true;
2981
            if ($this->IsShowFullList()) {
2982
                $toAdd = _t('ProductGroup.LIST_VIEW', 'List View');
2983
                $secondaryTitle .= $this->cleanSecondaryTitleForAddition($pipe, $toAdd);
2984
                $pagination = false;
2985
            }
2986
            $filter = $this->getCurrentUserPreferences('FILTER');
2987
            if ($filter != $this->getMyUserPreferencesDefault('FILTER')) {
2988
                $toAdd = $this->getUserPreferencesTitle('FILTER', $this->getCurrentUserPreferences('FILTER'));
2989
                $secondaryTitle .= $this->cleanSecondaryTitleForAddition($pipe, $toAdd);
2990
            }
2991
            if ($this->HasSort()) {
2992
                $toAdd = $this->getUserPreferencesTitle('SORT', $this->getCurrentUserPreferences('SORT'));
2993
                $secondaryTitle .= $this->cleanSecondaryTitleForAddition($pipe, $toAdd);
2994
            }
2995
            if ($pagination) {
2996
                if ($pageStart = intval($this->request->getVar('start'))) {
2997
                    if ($pageStart > 0) {
2998
                        $page = ($pageStart / $this->MyNumberOfProductsPerPage()) + 1;
2999
                        $toAdd = _t('ProductGroup.PAGE', 'Page') . ' '.$page;
3000
                        $secondaryTitle .= $this->cleanSecondaryTitleForAddition($pipe, $toAdd);
3001
                    }
3002
                }
3003
            }
3004
            if ($secondaryTitle) {
3005
                $this->Title .= $secondaryTitle;
3006
                if (isset($this->MetaTitle)) {
3007
                    $this->MetaTitle .= $secondaryTitle;
3008
                }
3009
                if (isset($this->MetaDescription)) {
3010
                    $this->MetaDescription .= $secondaryTitle;
3011
                }
3012
            }
3013
            //dont update menu title, because the entry in the menu
3014
            //should stay the same as it links back to the unfiltered
3015
            //page (in some cases).
3016
3017
            $this->secondaryTitleHasBeenAdded = true;
3018
        }
3019
    }
3020
3021
    /**
3022
     * removes any spaces from the 'toAdd' bit and adds the pipe if there is
3023
     * anything to add at all.  Through the lang files, you can change the pipe
3024
     * symbol to anything you like.
3025
     *
3026
     * @param  string $pipe
3027
     * @param  string $toAdd
3028
     * @return string
3029
     */
3030
    protected function cleanSecondaryTitleForAddition($pipe, $toAdd)
3031
    {
3032
        $toAdd = trim($toAdd);
3033
        $length = strlen($toAdd);
3034
        if ($length > 0) {
3035
            $toAdd = $pipe.$toAdd;
3036
        }
3037
        return $toAdd;
3038
    }
3039
3040
    /****************************************************
3041
     *  DEBUG
3042
    /****************************************************/
3043
3044
    public function debug()
3045
    {
3046
        $member = Member::currentUser();
3047
        if (!$member || !$member->IsShopAdmin()) {
3048
            $messages = array(
3049
                'default' => 'You must login as an admin to use debug functions.',
3050
            );
3051
            Security::permissionFailure($this, $messages);
3052
        }
3053
        $this->ProductsShowable();
3054
        $html = EcommerceTaskDebugCart::debug_object($this->dataRecord);
3055
        $html .= '<ul>';
3056
3057
        $html .= '<li><hr /><h3>Available options</h3><hr /></li>';
3058
        $html .= '<li><b>Sort Options for Dropdown:</b><pre> '.print_r($this->getUserPreferencesOptionsForDropdown('SORT'), 1).'</pre> </li>';
3059
        $html .= '<li><b>Filter Options for Dropdown:</b><pre> '.print_r($this->getUserPreferencesOptionsForDropdown('FILTER'), 1).'</pre></li>';
3060
        $html .= '<li><b>Display Styles for Dropdown:</b><pre> '.print_r($this->getUserPreferencesOptionsForDropdown('DISPLAY'), 1).'</pre> </li>';
3061
3062
        $html .= '<li><hr /><h3>Selection Setting (what is set as default for this page)</h3><hr /></li>';
3063
        $html .= '<li><b>MyDefaultFilter:</b> '.$this->getMyUserPreferencesDefault('FILTER').' </li>';
3064
        $html .= '<li><b>MyDefaultSortOrder:</b> '.$this->getMyUserPreferencesDefault('SORT').' </li>';
3065
        $html .= '<li><b>MyDefaultDisplayStyle:</b> '.$this->getMyUserPreferencesDefault('DISPLAY').' </li>';
3066
        $html .= '<li><b>MyNumberOfProductsPerPage:</b> '.$this->MyNumberOfProductsPerPage().' </li>';
3067
        $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>';
3068
3069
        $html .= '<li><hr /><h3>Current Settings</h3><hr /></li>';
3070
        $html .= '<li><b>Current Sort Order:</b> '.$this->getCurrentUserPreferences('SORT').' </li>';
3071
        $html .= '<li><b>Current Filter:</b> '.$this->getCurrentUserPreferences('FILTER').' </li>';
3072
        $html .= '<li><b>Current display style:</b> '.$this->getCurrentUserPreferences('DISPLAY').' </li>';
3073
3074
        $html .= '<li><hr /><h3>DATALIST: totals, numbers per page etc</h3><hr /></li>';
3075
        $html .= '<li><b>Total number of products:</b> '.$this->TotalCount().' </li>';
3076
        $html .= '<li><b>Is there more than one product:</b> '.($this->TotalCountGreaterThanOne() ? 'YES' : 'NO').' </li>';
3077
        $html .= '<li><b>Number of products per page:</b> '.$this->MyNumberOfProductsPerPage().' </li>';
3078
3079
        $html .= '<li><hr /><h3>SQL Factors</h3><hr /></li>';
3080
        $html .= '<li><b>Default sort SQL:</b> '.print_r($this->getUserSettingsOptionSQL('SORT'), 1).' </li>';
3081
        $html .= '<li><b>User sort SQL:</b> '.print_r($this->getUserSettingsOptionSQL('SORT', $this->getCurrentUserPreferences('SORT')), 1).' </li>';
3082
        $html .= '<li><b>Default Filter SQL:</b> <pre>'.print_r($this->getUserSettingsOptionSQL('FILTER'), 1).'</pre> </li>';
3083
        $html .= '<li><b>User Filter SQL:</b> <pre>'.print_r($this->getUserSettingsOptionSQL('FILTER', $this->getCurrentUserPreferences('FILTER')), 1).'</pre> </li>';
3084
        $html .= '<li><b>Buyable Class name:</b> '.$this->getBuyableClassName().' </li>';
3085
        $html .= '<li><b>allProducts:</b> '.print_r(str_replace('"', '`', $this->allProducts->sql()), 1).' </li>';
3086
3087
        $html .= '<li><hr /><h3>Search</h3><hr /></li>';
3088
        $resultArray = $this->searchResultsArrayFromSession();
3089
        $productGroupArray = explode(',', Session::get($this->SearchResultsSessionVariable(true)));
3090
        $html .= '<li><b>Is Search Results:</b> '.($this->IsSearchResults() ? 'YES' : 'NO').' </li>';
3091
        $html .= '<li><b>Products In Search (session variable : '.$this->SearchResultsSessionVariable(false).'):</b> '.print_r($resultArray, 1).' </li>';
3092
        $html .= '<li><b>Product Groups In Search (session variable : '.$this->SearchResultsSessionVariable(true).'):</b> '.print_r($productGroupArray, 1).' </li>';
3093
3094
        $html .= '<li><hr /><h3>Other</h3><hr /></li>';
3095
        if ($image = $this->BestAvailableImage()) {
3096
            $html .= '<li><b>Best Available Image:</b> <img src="'.$image->Link.'" /> </li>';
3097
        }
3098
        $html .= '<li><b>BestAvailableImage:</b> '.($this->BestAvailableImage() ? $this->BestAvailableImage()->Link : 'no image available').' </li>';
3099
        $html .= '<li><b>Is this an ecommerce page:</b> '.($this->IsEcommercePage() ? 'YES' : 'NO').' </li>';
3100
        $html .= '<li><hr /><h3>Related Groups</h3><hr /></li>';
3101
        $html .= '<li><b>Parent product group:</b> '.($this->ParentGroup() ? $this->ParentGroup()->Title : '[NO PARENT GROUP]').'</li>';
3102
3103
        $childGroups = $this->ChildGroups(99);
3104
        if ($childGroups->count()) {
3105
            $childGroups = $childGroups->map('ID', 'MenuTitle');
3106
            $html .= '<li><b>Child Groups (all):</b><pre> '.print_r($childGroups, 1).' </pre></li>';
3107
        } else {
3108
            $html .= '<li><b>Child Groups (full tree): </b>NONE</li>';
3109
        }
3110
        $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>';
3111
        $html .= '<li><b>the inverse of ProductGroupsFromAlsoShowProducts:</b><pre> '.print_r($this->ProductGroupsFromAlsoShowProductsInverse()->map('ID', 'Title')->toArray(), 1).' </pre></li>';
3112
        $html .= '<li><b>all product parent groups:</b><pre> '.print_r($this->ProductGroupsParentGroups()->map('ID', 'Title')->toArray(), 1).' </pre></li>';
3113
3114
        $html .= '<li><hr /><h3>Product Example and Links</h3><hr /></li>';
3115
        $product = DataObject::get_one(
3116
            'Product',
3117
            array('ParentID' => $this->ID)
3118
        );
3119
        if ($product) {
3120
            $html .= '<li><b>Product View:</b> <a href="'.$product->Link().'">'.$product->Title.'</a> </li>';
3121
            $html .= '<li><b>Product Debug:</b> <a href="'.$product->Link('debug').'">'.$product->Title.'</a> </li>';
3122
            $html .= '<li><b>Product Admin Page:</b> <a href="'.'/admin/pages/edit/show/'.$product->ID.'">'.$product->Title.'</a> </li>';
3123
            $html .= '<li><b>ProductGroup Admin Page:</b> <a href="'.'/admin/pages/edit/show/'.$this->ID.'">'.$this->Title.'</a> </li>';
3124
        } else {
3125
            $html .= '<li>this page has no products of its own</li>';
3126
        }
3127
        $html .= '</ul>';
3128
3129
        return $html;
3130
    }
3131
}
3132