turnValueIntoHumanReadableValue()   B
last analyzed

Complexity

Conditions 7
Paths 16

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
dl 0
loc 17
rs 8.8333
c 0
b 0
f 0
nc 16
nop 1
1
<?php
2
3
/**
4
 * This class reviews all of the static configurations in e-commerce for review
5
 * (a) which configs are set, but not required
6
 * (b) which configs are required, but not set
7
 * (c) review of set configs.
8
 *
9
 * @TODO: compare to default
10
 *
11
 * shows you the link to remove the current cart
12
 *
13
 * @authors: Nicolaas [at] Sunny Side Up .co.nz
14
 * @package: ecommerce
15
 * @sub-package: tasks
16
 * @inspiration: Silverstripe Ltd, Jeremy
17
 **/
18
class EcommerceTaskCheckConfiguration extends BuildTask
19
{
20
    /**
21
     * Default Location for Configuration File.
22
     *
23
     * @var string
24
     */
25
    protected $defaultLocation = 'ecommerce/_config/ecommerce.yml';
26
27
    /**
28
     * Standard (required) SS variable for BuildTasks.
29
     *
30
     * @var string
31
     */
32
    protected $title = 'Check Configuration';
33
34
    /**
35
     * Standard (required) SS variable for BuildTasks.
36
     *
37
     * @var string
38
     */
39
    protected $description = 'Runs through all static configuration for review.';
40
41
    /**
42
     * Array of definitions - set like this:
43
     * ClassName
44
     * 		VariableName: Description.
45
     *
46
     * @var array
47
     */
48
    protected $definitions = array();
49
50
    /**
51
     * Array of definitions Header - set like this:
52
     * HEADER TITLE
53
     * 		ClassName.
54
     *
55
     * @var array
56
     */
57
    protected $definitionsHeaders = array();
58
59
    /**
60
     * Array of defaults - set like this:
61
     * ClassName
62
     * 		VariableName: Default Variable Value.
63
     *
64
     * @var array
65
     */
66
    protected $defaults = array();
67
68
    /**
69
     * Array of configs - set like this:
70
     * ClassName
71
     * 		VariableName: VariableValue.
72
     *
73
     * @var array
74
     */
75
    protected $configs = array();
76
77
    /**
78
     * which values are derived from DB
79
     * ClassName
80
     * 		VariableName: TRUE | FALSE.
81
     *
82
     * @var array
83
     */
84
    protected $databaseValues = array();
85
86
    /**
87
     * set in default yml, but not customised.
88
     * ClassName
89
     * 		VariableName: TRUE | FALSE.
90
     *
91
     * @var array
92
     */
93
    protected $customisedValues = array();
94
95
    /**
96
     * Other configs
97
     * ClassName
98
     * 		VariableName: TRUE | FLASE.
99
     *
100
     * @var array
101
     */
102
    protected $otherConfigs = array();
103
104
    /**
105
     * Array of classes (partially) missing in configs.
106
     * VariableName: VariableName.
107
     *
108
     *  @var array
109
     */
110
    protected $missingClasses = array();
111
112
    /**
113
     * Standard (required) SS method, runs buildtask.
114
     */
115
    public function run($request)
116
    {
117
        $definitionsObject = EcommerceConfigDefinitions::create();
118
        $this->definitions = $definitionsObject->Definitions();
119
        $this->definitionsHeaders = $definitionsObject->GroupDefinitions();
120
        $configsObject = EcommerceConfig::create();
121
        $this->configs = $configsObject->getCompleteDataSet();
122
        $this->defaults = $this->getDefaultValues();
123
        if ($this->definitions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->definitions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
124
            if ($this->configs) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->configs of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
125
                if ($this->defaults) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->defaults of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
126
                    $this->checkFiles();
127
                    $this->configsNotSet();
128
                    $this->classesThatDoNotExist();
129
                    $this->definitionsNotSet();
130
                    $this->addEcommerceDBConfigToConfigs();
131
                    $this->addOtherValuesToConfigs();
132
                    $this->addPages();
133
                    $this->orderSteps();
134
                    $this->checkoutAndModifierDetails();
135
                    $this->getAjaxDefinitions();
136
                    $this->definedConfigs();
137
                    $this->checkGEOIP();
138
                } else {
139
                    DB::alteration_message('ERROR: could not find any defaults', 'deleted');
140
                }
141
            } else {
142
                DB::alteration_message('ERROR: could not find any configs', 'deleted');
143
            }
144
        } else {
145
            DB::alteration_message('ERROR: could not find any definitions', 'deleted');
146
        }
147
    }
148
149
    /**
150
     * Check what files is being used.
151
     */
152
    protected function checkFiles()
153
    {
154
        $configsObject = EcommerceConfig::create();
155
        DB::alteration_message('<h2>Files Used</h2>');
156
        $files = implode(', ', $configsObject->fileLocations());
157
        global $project;
158
        $baseFolder = Director::baseFolder();
159
        $projectFolder = $project.'/_config';
160
        $baseAndProjectFolder = $baseFolder.'/'.$projectFolder;
161
        $file = 'ecommerce.yml';
162
        $projectFolderAndFile = $projectFolder.'/'.$file;
163
        $fullFilePath = $baseFolder.'/'.$projectFolderAndFile;
164
        $defaultFileFullPath = Director::baseFolder().'/'.$this->defaultLocation;
165
        DB::alteration_message(
166
            '
167
            Current files used: <strong style="color: darkRed">'.$files.'</strong>,
168
            unless stated otherwise, all settings can be edited in these file(s).',
169
            'created'
170
        );
171
        if (!file_exists($baseAndProjectFolder)) {
172
            mkdir($baseAndProjectFolder);
173
        }
174
        if (!file_exists($fullFilePath)) {
175
            copy($defaultFileFullPath, $fullFilePath);
176
            DB::alteration_message('We have created a new configuration file for you.', 'created');
177
        }
178
        if ($files == $this->defaultLocation) {
179
            if (file_exists($fullFilePath)) {
180
                DB::alteration_message("A customisable configuration file exists here: $projectFolderAndFile, you should add the following to your config.yml file:
181
<pre>
182
EcommerceConfig:
183
  folder_and_file_locations:
184
    - \"$projectFolderAndFile\"
185
</pre>", 'created');
186
            }
187
        }
188
    }
189
190
    /**
191
     * Work out items set in the configuration but not set in the config file.
192
     */
193
    protected function definitionsNotSet()
194
    {
195
        echo '<h2>Set in configs but not defined</h2>';
196
        $allOK = true;
197
        foreach ($this->configs as $className => $setting) {
198
            if (!isset($this->definitions[$className])) {
199
                $allOK = false;
200
                $this->missingClasses[$className] = $className;
201
                DB::alteration_message("$className", 'deleted');
202
            } else {
203
                $classConfigs = $this->configs[$className];
204
                foreach ($classConfigs as $key => $classConfig) {
205
                    if (!isset($this->definitions[$className][$key])) {
206
                        $allOK = false;
207
                        DB::alteration_message("$className.$key", 'deleted');
208
                    }
209
                }
210
            }
211
        }
212
        if ($allOK) {
213
            DB::alteration_message('Perfect match, nothing to report', 'created');
214
        } else {
215
            DB::alteration_message('Recommended course of action: remove from your config as these are superfluous!', 'edited');
216
        }
217
    }
218
219
    /**
220
     * Work out items set in the configuration but not set in the config file.
221
     */
222
    protected function classesThatDoNotExist()
223
    {
224
        echo '<h2>Classes that do not exist</h2>';
225
        $allOK = true;
226
        foreach ($this->configs as $className => $setting) {
227
            if (!class_exists($className)) {
228
                $allOK = false;
229
                DB::alteration_message("$className", 'deleted');
230
            }
231
        }
232
        if ($allOK) {
233
            DB::alteration_message('Perfect match, nothing to report', 'created');
234
        } else {
235
            DB::alteration_message('Recommended course of action: remove from your config file and review if any other action needs to be taken.', 'edited');
236
        }
237
    }
238
239
    /**
240
     * Work out items set in the definitions but not set in the config file.
241
     */
242
    protected function configsNotSet()
243
    {
244
        echo '<h2>Defined variables not set in configs ...</h2>';
245
        $allOK = true;
246
        //print_r($this->configs["EcommercePayment"]);
247
        foreach ($this->definitions as $className => $setting) {
248
            if (!isset($this->configs[$className])) {
249
                DB::alteration_message("No settings found for $className in /ecommerce/_config/config.yml", 'deleted');
250
            } else {
251
                $classConfigs = $this->definitions[$className];
252
                foreach ($classConfigs as $key => $classConfig) {
253
                    if (!isset($this->configs[$className][$key])) {
254
                        $this->customisedValues[$className][$key] = false;
255
                    //fallback to Configs...
256
                    } else {
257
                        $this->customisedValues[$className][$key] = false;
258
                    }
259
                    if (!isset($this->configs[$className][$key])) {
260
                        DB::alteration_message(" - $className.$key NOT SET in /ecommerce/_config/config.yml", 'deleted');
261
                        $allOK = false;
262
                    } else {
263
                        //$this->configs[$className][$key] = EcommerceConfig::get($className, $key);
264
                        //if(!$this->configs[$className][$key]) {
265
                            //DB::alteration_message(" - $className.$key exists, set to FALSE / [EMPTRY STRING]", "edited");
266
                        //}
267
                    }
268
                }
269
            }
270
        }
271
        if ($allOK) {
272
            DB::alteration_message('Perfect match, nothing to report', 'created');
273
        } else {
274
            DB::alteration_message('Recommended course of action: add the above configs to your mysite/_config/ecommerce.yml file if you required them.', 'edited');
275
        }
276
    }
277
278
    /**
279
     * Work out items set in the definitions but not set in the config file.
280
     */
281
    protected function definedConfigs()
282
    {
283
        $htmlHeader = "
284
        <style>
285
            body {margin-left: 300px!important;}
286
            h2 {padding-top: 2em;margin-bottom: 0; padding-bottom: 0;}
287
            th[scope='col'] {text-align: left; border-bottom: 3px solid #ccdef3;padding-top: 40px;}
288
            td {vertical-align: top; border-left: 1px solid #d7d7d7; border-bottom: 1px solid #d7d7d7; padding: 10px; width: 47%;}
289
            /** headings **/
290
            td span.spanTitle {color: #002137; font-weight: 900; display: block; padding-left: 10px; padding-bottom: 5px;}
291
            .ecommerceConfigHeadings th, h2 {
292
                font-size: 1.2em;
293
                padding-bottom: 5px;
294
                color: #002137;
295
            }
296
            td span {color: #000; font-size: 0.8em; display: block; padding-left: 10px; }
297
            .sameConfig {color: #000;}
298
            .newConfig pre:first-of-type{color: #000; background-color: yellow;}
299
            .newConfig pre:first-of-type { }
300
            .newConfig pre:nth-of-type(2) { }
301
            #TOC {
302
                position: fixed;
303
                top: -15px;
304
                bottom: -20px;
305
                color: #fff;
306
                background-color: #000;
307
                width: 270px;
308
                left: 0px;
309
                padding-top: 15px;
310
                z-index: 10000;
311
                overflow: auto;
312
                padding-bottom: 20px;
313
            }
314
            #TOC ul {
315
                list-style-type: none;
316
            }
317
            #TOC li {
318
                line-height: 1.3;
319
                font-size: 80%;
320
                font-weight: 900;
321
                height: auto;
322
                list-style-type: none;
323
            }
324
            #TOC a {
325
                color: #fff;
326
                text-decoration: none;
327
                font-size: 85%;
328
                font-weight: 900;
329
                margin-left: -10px;
330
            }
331
            #TOC a:hover {
332
                color: #7da4be;
333
            }
334
            /* not sure why we needed this ...
335
            #TaskHolder, #EcommerceDatabaseAdmin, .info h1, .info h3, .info a:first-of-type  {
336
                margin-left: 280px !important;
337
            }
338
            */
339
            .info h1, .info h3, .info a {
340
                padding-left: 30px;
341
            }
342
            a.backToTop {display: block; font-size: 0.7em; float: right;}
343
            td.newConfig {}
344
            table td pre, table td sub {white-space:pre-wrap; font-size: 1em; font-weight: bold;margin: 3px; padding: 3px;}
345
            table td sub {font-weight: normal; font-size: 77%;}
346
347
            li pre {width: auto;}
348
        </style>
349
        ";
350
        $htmlTable = '
351
        <table summary="list of configs">
352
        ';
353
        $oldClassName = '';
354
        $htmlTOC = '<div id="TOC"><ul>';
355
        $count = 0;
356
        $oldHeaderOfGroup = '';
357
        $newHeader = '';
358
        $completedListOfClasses = array();
359
        foreach ($this->definitionsHeaders as $headerOfGroup => $classesArray) {
360
            if ($headerOfGroup == 'OTHER') {
361
                $classesArray = array_keys(array_diff_key($this->configs, $completedListOfClasses));
362
            }
363
            foreach ($classesArray as $className) {
364
                $completedListOfClasses[$className] = $className;
365
                if (!isset($this->configs[$className])) {
366
                    $this->configs[$className] = array();
367
                }
368
                $settings = $this->configs[$className];
369
                ++$count;
370
                if (in_array($className, $classesArray)) {
371
                    $newHeader = $headerOfGroup;
372
                }
373
                if ($oldHeaderOfGroup != $newHeader) {
374
                    $oldHeaderOfGroup = $headerOfGroup;
375
                    $htmlTOC .= "</ul><li class=\"header\">$headerOfGroup</li><ul>";
376
                }
377
378
                $htmlTOC .= "<li><a href=\"#$className\">$count. $className</a></li>";
379
                if ($className != $oldClassName) {
380
                    $htmlTable .= "<tr  class='ecommerceConfigHeadings' id=\"$className\"><th colspan=\"2\" scope=\"col\">
381
                    $count. $className ($newHeader)
382
                    <a class=\"backToTop\" href=\"#TaskHolder\">top</a>
383
                    </th></tr>";
384
                    $oldClassName = $className;
385
                }
386
                if (is_array($settings)) {
387
                    foreach ($settings as $key => $classConfig) {
388
                        $configError = '';
389
                        $class = '';
390
                        $hasDefaultvalue = false;
391
                        $showActualValue = true;
392
                        $isDatabaseValues = isset($this->databaseValues[$className][$key]) ? $this->databaseValues[$className][$key] : false;
393
                        $isOtherConfigs = isset($this->otherConfigs[$className][$key]) ? $this->otherConfigs[$className][$key] : false;
394
                        $isCustomisedValues = isset($this->customisedValues[$className][$key]) ? $this->customisedValues[$className][$key] : false;
395
                        if (!isset($this->defaults[$className][$key])) {
396
                            $defaultValueRaw = false;
397
                        //DB::alteration_message("Could not retrieve default value for: $className $key", "deleted");
398
                        } else {
399
                            $defaultValueRaw = $this->defaults[$className][$key];
400
                            $hasDefaultvalue = true;
401
                        }
402
                        $defaultValue = print_r($defaultValueRaw, 1);
403
                        $manuallyAddedValue = print_r($this->configs[$className][$key], 1);
404
                        if ($isDatabaseValues || $isOtherConfigs) {
405
                            $actualValueRaw = $this->configs[$className][$key];
406
                        } else {
407
                            $actualValueRaw = EcommerceConfig::get($className, $key);
408
                        }
409
                        //if(!$actualValueRaw && $manuallyAddedValue) {
410
                        //	$actualValueRaw = $manuallyAddedValue;
411
                        //}
412
413
                        $actualValue = print_r($actualValueRaw, 1);
414
                        if ($defaultValue === $manuallyAddedValue && $isCustomisedValues) {
415
                            $configError .= 'This is a superfluous entry in your custom config as the default value is the same.';
416
                        }
417
                        if (($defaultValueRaw == $actualValueRaw) || (! $hasDefaultvalue)) {
418
                            $class .= 'sameConfig';
419
                            if ($defaultValueRaw == $actualValueRaw) {
420
                                $showActualValue = false;
421
                            }
422
                        } else {
423
                            $class .= ' newConfig';
424
                        }
425
                        $actualValue = $this->turnValueIntoHumanReadableValue($actualValue);
426
                        if ($hasDefaultvalue) {
427
                            $defaultValue = $this->turnValueIntoHumanReadableValue($defaultValue);
428
                        }
429
430
                        if (!isset($this->definitions[$className][$key])) {
431
                            $description = '<span style="color: red; font-weight: bold">ERROR: no longer required in configs!</span>';
432
                        } else {
433
                            $description = $this->definitions[$className][$key];
434
                            $description .= $this->specialCases($className, $key, $actualValue);
435
                        }
436
                        $defaultValueHTML = '';
437
                        if ($defaultValue && !$isOtherConfigs && $showActualValue) {
438
                            $defaultValueHTML = "<sub>default:</sub><pre>$defaultValue</pre>";
439
                        }
440
                        if ($configError) {
441
                            $configError = "<span style=\"color: red; font-size: 10px;\">$configError</span>";
442
                        }
443
                        $sourceNote = '';
444
                        if ($isDatabaseValues) {
445
                            $sourceNote = '<span>Values are set in the database using the CMS.</span>';
446
                        }
447
                        $htmlTable .= "<tr>
448
                <td>
449
                    <span class='spanTitle'>$key</span>
450
                    <span>$description</span>
451
                    $sourceNote
452
                </td>
453
                <td class=\"$class\">
454
                    <pre>$actualValue</pre>
455
                    $defaultValueHTML
456
                    $configError
457
                </td>
458
            </tr>";
459
                    }
460
                }
461
            }
462
        }
463
        $htmlEnd = '
464
        </table>
465
        <h2>--- THE END ---</h2>
466
        ';
467
        $htmlTOC .= '</ul></div>';
468
        echo $htmlHeader.$htmlTOC.$htmlTable.$htmlEnd;
469
    }
470
471
    protected function getDefaultValues()
472
    {
473
        require_once Director::baseFolder().'/vendor/mustangostang/spyc/Spyc.php';
474
        $fixtureFolderAndFile = Director::baseFolder().'/'.$this->defaultLocation;
475
        $parser = new Spyc();
476
477
        return $parser->loadFile($fixtureFolderAndFile);
478
    }
479
480
    /**
481
     * Adding EcommerceDBConfig values.
482
     */
483
    protected function addEcommerceDBConfigToConfigs()
484
    {
485
        $ecommerceDBConfig = EcommerceDBConfig::current_ecommerce_db_config();
486
        $fields = $ecommerceDBConfig->fieldLabels();
487
        if ($fields) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fields of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
488
            foreach ($fields as $field => $description) {
489
                if ($field != 'Title' && $field != 'UseThisOne') {
490
                    $defaultsDefaults = $ecommerceDBConfig->stat('defaults');
491
                    $this->definitions['EcommerceDBConfig'][$field] = "$description. <br />see: <a href=\"".$ecommerceDBConfig->CMSEditLink()."\">Ecommerce Configuration</a>";
492
                    $this->configs['EcommerceDBConfig'][$field] = $ecommerceDBConfig->$field;
493
                    $this->databaseValues['EcommerceDBConfig'][$field] = true;
494
                    $this->defaults['EcommerceDBConfig'][$field] = isset($defaultsDefaults[$field]) ? $defaultsDefaults[$field] : 'no default set';
495
                    $imageField = $field.'ID';
496
                    if (isset($ecommerceDBConfig->$imageField)) {
497
                        if ($image = $ecommerceDBConfig->$field()) {
498
                            if ($image->exists() && is_a($image, Object::getCustomClass('Image'))) {
499
                                $this->configs['EcommerceDBConfig'][$field] = '[Image]  --- <img src="'.$image->Link().'" />';
500
                                $this->databaseValues['EcommerceDBConfig'][$field] = true;
501
                            }
502
                        }
503
                    }
504
                }
505
            }
506
        }
507
    }
508
509
    protected function addOtherValuesToConfigs()
510
    {
511
        $this->definitions['Email']['admin_email_address'] = 'Default administrator email. <br />SET USING Email::$admin_email = "[email protected]" in the _config.php FILES';
512
        $this->configs['Email']['admin_email_address'] = Config::inst()->get('Email', 'admin_email');
513
        $this->defaults['Email']['admin_email_address'] = '[no default set]';
514
        $this->otherConfigs['Email']['admin_email_address'] = true;
515
516
        $siteConfig = SiteConfig::current_site_config();
517
        $this->definitions['SiteConfig']['website_title'] = 'The name of the website. <br />see: <a href="/admin/settings/">site configuration</a>.';
518
        $this->configs['SiteConfig']['website_title'] = $siteConfig->Title;
519
        $this->defaults['SiteConfig']['website_title'] = '[no default set]';
520
        $this->otherConfigs['SiteConfig']['website_title'] = true;
521
522
        $this->definitions['SiteConfig']['website_tagline'] = 'The subtitle or tagline of the website. <br />see: <a href="/admin/settings/">site configuration</a>.';
523
        $this->configs['SiteConfig']['website_tagline'] = $siteConfig->Tagline;
524
        $this->defaults['SiteConfig']['website_tagline'] = '[no default set]';
525
        $this->otherConfigs['SiteConfig']['website_tagline'] = true;
526
    }
527
528
    protected function addPages()
529
    {
530
        if ($checkoutPage = DataObject::get_one('CheckoutPage')) {
531
            $this->getPageDefinitions($checkoutPage);
0 ignored issues
show
Compatibility introduced by
$checkoutPage of type object<DataObject> is not a sub-type of object<SiteTree>. It seems like you assume a child class of the class DataObject to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
532
            $this->definitions['Pages']['CheckoutPage'] = 'Page where customers finalise (checkout) their order. This page is required.<br />'.($checkoutPage ? '<a href="/admin/pages/edit/show/'.$checkoutPage->ID.'/">edit</a>' : 'Create one in the <a href="/admin/pages/add/">CMS</a>');
533
            $this->configs['Pages']['CheckoutPage'] = $checkoutPage ? 'view: <a href="'.$checkoutPage->Link().'">'.$checkoutPage->Title.'</a><br />'.$checkoutPage->configArray : ' NOT CREATED!';
534
            $this->defaults['Pages']['CheckoutPage'] = $checkoutPage ? $checkoutPage->defaultsArray : '[add page first to see defaults]';
0 ignored issues
show
Bug introduced by
The property defaultsArray does not seem to exist. Did you mean defaults?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
535
            $this->databaseValues['Pages']['CheckoutPage'] = true;
536
        }
537
538
        if ($orderConfirmationPage = DataObject::get_one('OrderConfirmationPage')) {
539
            $this->getPageDefinitions($orderConfirmationPage);
0 ignored issues
show
Compatibility introduced by
$orderConfirmationPage of type object<DataObject> is not a sub-type of object<SiteTree>. It seems like you assume a child class of the class DataObject to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
540
            $this->definitions['Pages']['OrderConfirmationPage'] = 'Page where customers review their order after it has been placed. This page is required.<br />'.($orderConfirmationPage ? '<a href="/admin/pages/edit/show/'.$orderConfirmationPage->ID.'/">edit</a>' : 'Create one in the <a href="/admin/pages/add/">CMS</a>');
541
            $this->configs['Pages']['OrderConfirmationPage'] = $orderConfirmationPage ? 'view: <a href="'.$orderConfirmationPage->Link().'">'.$orderConfirmationPage->Title.'</a><br />'.$orderConfirmationPage->configArray : ' NOT CREATED!';
542
            $this->defaults['Pages']['OrderConfirmationPage'] = $orderConfirmationPage ? $orderConfirmationPage->defaultsArray : '[add page first to see defaults]';
0 ignored issues
show
Bug introduced by
The property defaultsArray does not seem to exist. Did you mean defaults?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
543
            $this->databaseValues['Pages']['OrderConfirmationPage'] = true;
544
        }
545
546
        if ($accountPage = DataObject::get_one('AccountPage')) {
547
            $this->getPageDefinitions($accountPage);
0 ignored issues
show
Compatibility introduced by
$accountPage of type object<DataObject> is not a sub-type of object<SiteTree>. It seems like you assume a child class of the class DataObject to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
548
            $this->definitions['Pages']['AccountPage'] = 'Page where customers can review their account. This page is required.<br />'.($accountPage ? '<a href="/admin/pages/edit/show/'.$accountPage->ID.'/">edit</a>' : 'Create one in the <a href="/admin/pages/add/">CMS</a>');
549
            $this->configs['Pages']['AccountPage'] = $accountPage ? 'view: <a href="'.$accountPage->Link().'">'.$accountPage->Title.'</a><br />'.$accountPage->configArray : ' NOT CREATED!';
550
            $this->defaults['Pages']['AccountPage'] = $accountPage ? $accountPage->defaultsArray : '[add page first to see defaults]';
0 ignored issues
show
Bug introduced by
The property defaultsArray does not seem to exist. Did you mean defaults?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
551
            $this->databaseValues['Pages']['AccountPage'] = true;
552
        }
553
554
        if (
555
            $cartPage = DataObject::get_one('CartPage', array('ClassName' => 'CartPage'))
556
        ) {
557
            $this->getPageDefinitions($cartPage);
0 ignored issues
show
Compatibility introduced by
$cartPage of type object<DataObject> is not a sub-type of object<SiteTree>. It seems like you assume a child class of the class DataObject to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
558
            $this->definitions['Pages']['CartPage'] = 'Page where customers review their cart while shopping. This page is optional.<br />'.($cartPage ? '<a href="/admin/pages/edit/show/'.$cartPage->ID.'/">edit</a>' : 'Create one in the <a href="/admin/pages/add/">CMS</a>');
559
            $this->configs['Pages']['CartPage'] = $cartPage ? 'view: <a href="'.$cartPage->Link().'">'.$cartPage->Title.'</a>, <a href="/admin/pages/edit/show/'.$cartPage->ID.'/">edit</a><br />'.$cartPage->configArray : ' NOT CREATED!';
560
            $this->defaults['Pages']['CartPage'] = $cartPage ? $cartPage->defaultsArray : '[add page first to see defaults]';
0 ignored issues
show
Bug introduced by
The property defaultsArray does not seem to exist. Did you mean defaults?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
561
            $this->defaults['Pages']['CartPage'] = $cartPage ? $cartPage->defaultsArray : '[add page first to see defaults]';
0 ignored issues
show
Bug introduced by
The property defaultsArray does not seem to exist. Did you mean defaults?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
562
            $this->databaseValues['Pages']['CartPage'] = true;
563
        }
564
    }
565
566
    private function getPageDefinitions(SiteTree $page)
567
    {
568
        if ($page) {
569
            $fields = Config::inst()->get($page->ClassName, 'db');
570
            $defaultsArray = $page->stat('defaults', true);
571
            $configArray = array();
572
            if ($fields) {
573
                foreach ($fields as $fieldKey => $fieldType) {
0 ignored issues
show
Bug introduced by
The expression $fields of type array|integer|double|string|boolean 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...
574
                    $configArray[$fieldKey] = $page->$fieldKey;
575
                    if (!isset($defaultsArray[$fieldKey])) {
576
                        $defaultsArray[$fieldKey] = '[default not set]';
577
                    }
578
                }
579
            }
580
            $page->defaultsArray = $defaultsArray;
0 ignored issues
show
Bug introduced by
The property defaultsArray does not seem to exist. Did you mean defaults?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
581
            $page->configArray = print_r($configArray, 1);
582
        }
583
    }
584
585
    public function orderSteps()
586
    {
587
        $steps = OrderStep::get();
588
        if ($steps->count()) {
589
            foreach ($steps as $step) {
590
                $fields = Config::inst()->get($step->ClassName, 'db');
591
                $defaultsArray = $step->stat('defaults', true);
592
                $configArray = array();
593
                foreach ($fields as $fieldKey => $fieldType) {
0 ignored issues
show
Bug introduced by
The expression $fields of type array|integer|double|string|boolean 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...
594
                    if ($fields) {
595
                        $configArray[$fieldKey] = $step->$fieldKey;
596
                        if (!isset($defaultsArray[$fieldKey])) {
597
                            $defaultsArray[$fieldKey] = '[default not set]';
598
                        }
599
                    }
600
                }
601
                $ecommerceDBConfig = EcommerceDBConfig::current_ecommerce_db_config();
0 ignored issues
show
Unused Code introduced by
$ecommerceDBConfig 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...
602
                $this->definitions['OrderStep'][$step->Code] = $step->Description.'<br />see: <a href="'.$step->CMSEditLink().'">Step Configuration</a>.';
603
                $this->configs['OrderStep'][$step->Code] = $configArray;
604
                $this->defaults['OrderStep'][$step->Code] = $defaultsArray;
605
                $this->databaseValues['OrderStep'][$step->Code] = true;
606
            }
607
        }
608
    }
609
610
    public function checkoutAndModifierDetails()
611
    {
612
        $checkoutPage = DataObject::get_one('CheckoutPage');
613
        if (!$checkoutPage) {
614
            $task = new EcommerceTaskDefaultRecords();
615
            $task->run(null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<SS_HTTPRequest>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
616
            $checkoutPage = DataObject::get_one('CheckoutPage');
617
            if (!$checkoutPage) {
618
                user_error('There is no checkout page available and it seems impossible to create one.');
619
            }
620
        }
621
        $steps = CheckoutPage_StepDescription::get();
622
        if ($steps->count()) {
623
            foreach ($steps as $key => $step) {
624
                $stepNumber = $key + 1;
625
                $fields = Config::inst()->get($step->ClassName, 'db');
626
                $defaultsArray = $step->stat('defaults', true);
627
                $configArray = array();
628
                foreach ($fields as $fieldKey => $fieldType) {
0 ignored issues
show
Bug introduced by
The expression $fields of type array|integer|double|string|boolean 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...
629
                    if ($fields) {
630
                        $configArray[$fieldKey] = $step->$fieldKey;
631
                        if (!isset($defaultsArray[$fieldKey])) {
632
                            $defaultsArray[$fieldKey] = '[default not set]';
633
                        }
634
                    }
635
                }
636
                $this->definitions['CheckoutPage_Controller']["STEP_$stepNumber".'_'.$step->Code] = $step->Description.'<br />see: <a href="/admin/pages/edit/show/'.$checkoutPage->ID.'/">checkout page</a>.';
637
                $this->configs['CheckoutPage_Controller']["STEP_$stepNumber".'_'.$step->Code] = $configArray;
638
                $this->defaults['CheckoutPage_Controller']["STEP_$stepNumber".'_'.$step->Code] = $defaultsArray;
639
                $this->databaseValues['CheckoutPage_Controller']["STEP_$stepNumber".'_'.$step->Code] = true;
640
            }
641
        }
642
        $steps = OrderModifier_Descriptor::get();
643
        if ($steps->count()) {
644
            foreach ($steps as $step) {
645
                $fields = Config::inst()->get($step->ClassName, 'db');
646
                $defaultsArray = $step->stat('defaults', true);
647
                $configArray = array();
648
                foreach ($fields as $fieldKey => $fieldType) {
0 ignored issues
show
Bug introduced by
The expression $fields of type array|integer|double|string|boolean 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...
649
                    if ($fields) {
650
                        $configArray[$fieldKey] = $step->$fieldKey;
651
                        if (!isset($defaultsArray[$fieldKey])) {
652
                            $defaultsArray[$fieldKey] = '[default not set]';
653
                        }
654
                    }
655
                }
656
                $this->definitions['CheckoutPage_Controller']['OrderModifier_Descriptor_'.$step->ModifierClassName] = $step->Description.'<br />see: <a href="/admin/pages/edit/show/'.$checkoutPage->ID.'/">checkout page</a>.';
657
                $this->configs['CheckoutPage_Controller']['OrderModifier_Descriptor_'.$step->ModifierClassName] = $configArray;
658
                $this->defaults['CheckoutPage_Controller']['OrderModifier_Descriptor_'.$step->ModifierClassName] = $defaultsArray;
659
                $this->databaseValues['CheckoutPage_Controller']['OrderModifier_Descriptor_'.$step->ModifierClassName] = true;
660
            }
661
        }
662
    }
663
664
    private function getAjaxDefinitions()
665
    {
666
        $definitionsObject = EcommerceConfigDefinitions::create();
667
        $methodArray = $definitionsObject->getAjaxMethods();
668
        $requestor = new ArrayData(
669
            array(
670
                'ID' => '[ID]',
671
                'ClassName' => '[CLASSNAME]',
672
            )
673
        );
674
        $obj = EcommerceConfigAjax::get_one($requestor);
0 ignored issues
show
Documentation introduced by
$requestor is of type object<ArrayData>, but the function expects a object<DataObject>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
675
        foreach ($methodArray as $method => $description) {
676
            if ($method != 'setRequestor') {
677
                if (strpos($method, 'lassName')) {
678
                    $selector = 'classname';
679
                } else {
680
                    $selector = 'id';
681
                }
682
                $note = "
683
                    This variable can be used like this: <pre>&lt;div $selector=\"\$AJAXDefinitions.".$method.'"&gt;&lt;/div&gt;</pre>
684
                    <a href="/shoppingcart/ajaxtest/?ajax=1">AJAX</a> will then use this selector to put the following content: ';
685
                $this->definitions['Templates']["AJAXDefinitions_$method"] = $note.'<br />'.$description;
686
                $this->configs['Templates']["AJAXDefinitions_$method"] = $obj->$method();
687
                $this->defaults['Templates']["AJAXDefinitions_$method"] = $obj->$method();
688
                $this->otherConfigs['Templates']["AJAXDefinitions_$method"] = true;
689
            }
690
        }
691
    }
692
693
    /**
694
     * check for any additional settings.
695
     */
696
    private function specialCases($className, $key, $actualValue)
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...
697
    {
698
        switch ($className.'.'.$key) {
699
            case 'Order_Email.css_file_location':
700
                if (!file_exists(Director::baseFolder()."/$actualValue")) {
701
                    return '<span style="color: red">ADDITIONAL CHECK: this file '.Director::baseFolder().'/'.$actualValue.' does not exist! For proper functioning of e-commerce, please make sure to create this file.</span>';
702
                } else {
703
                    return '<span style="color: #7da4be">ADDITIONAL CHECK: file exists.</span>';
704
                }
705
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
706
            case 'Order.modifiers':
707
                $classes = ClassInfo::subclassesFor('OrderModifier');
708
                unset($classes['OrderModifier']);
709
                $classesAsString = implode(', <br />', $classes);
710
711
                return "<br /><h4>Available Modifiers</h4>$classesAsString";
712
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
713
            case 'OrderStatusLog.available_log_classes_array':
714
                $classes = ClassInfo::subclassesFor('OrderStatusLog');
715
                unset($classes['OrderStatusLog']);
716
                $classesAsString = implode(', <br />', $classes);
717
718
                return "<br /><h4>Available Modifiers</h4>$classesAsString";
719
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
720
            case 'OrderStep.order_steps_to_include':
721
                $classes = ClassInfo::subclassesFor('OrderStep');
722
                unset($classes['OrderStep']);
723
                $classesAsString = implode('<br /> - ', $classes);
724
725
                return "<br /><h4>Available Order Steps</h4> - $classesAsString";
726
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
727
        }
728
    }
729
730
    private function turnValueIntoHumanReadableValue($actualValue)
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...
731
    {
732
        if ($actualValue === '') {
733
            $actualValue = '[FALSE] / [EMPTY STRING] ';
734
        }
735
        if ($actualValue === null) {
736
            $actualValue = '[NULL]';
737
        }
738
        if ($actualValue === '1' || $actualValue === 1) {
739
            $actualValue = '[TRUE] / 1';
740
        }
741
        if ($actualValue === '0' || $actualValue === false) {
742
            $actualValue = '[FALSE] / 0';
743
        }
744
745
        return $actualValue;
746
    }
747
748
    protected function checkGEOIP()
749
    {
750
        if (Config::inst()->get('EcommerceCountry', 'visitor_country_provider') == 'EcommerceCountry_VisitorCountryProvider' && !class_exists('Geoip')) {
751
            user_error(
752
                "
753
                You need to install Geoip module that has a method Geoip::visitor_country, returning the country code associated with the user's IP address.
754
                Alternatively you can set the following config EcommerceCountry.visitor_country_provider to something like MyGEOipProvider.
755
                You then create a class MyGEOipProvider with a method getCountry().",
756
                E_USER_NOTICE
757
            );
758
        } elseif (Director::isLive() && !EcommerceCountry::get_country_from_ip()) {
759
            user_error(
760
                "
761
                Please make sure that '".$this->Config()->get('visitor_country_provider')."' (visitor_country_provider) is working on your server (see the GEOIP module for details).",
762
                E_USER_NOTICE
763
            );
764
        }
765
    }
766
}
767