Completed
Pull Request — master (#41)
by Robbie
01:25
created

DebugBarDatabaseProxy::random()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
/**
4
 * A proxy database to log queries (compatible with 3.1)
5
 *
6
 * @author Koala
7
 * @deprecated 1.0.0 Please upgrade to at least SilverStripe 3.2. Use DebugBarDatabaseNewProxy.
8
 * @codeCoverageIgnore
9
 */
10
class DebugBarDatabaseProxy extends SS_Database
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
11
{
12
    /** @var MySQLDatabase */
13
    protected $realConn;
14
    protected $findSource;
15
16
    /** @var array */
17
    protected $queries;
18
    protected $connector;
19
    protected $schemaManager;
20
    protected $queryBuilder;
21
    protected $showQueries = false;
22
23
    /**
24
     * @param MySQLDatabase $realConn
25
     */
26
    public function __construct($realConn)
27
    {
28
        $this->realConn   = $realConn;
29
        $this->queries    = array();
30
        $this->findSource = DebugBar::config()->find_source;
31
    }
32
33
    public function getShowQueries()
34
    {
35
        return $this->showQueries;
36
    }
37
38
    public function setShowQueries($showQueries)
39
    {
40
        $this->showQueries = $showQueries;
41
        return $this;
42
    }
43
44
    /**
45
     * Get the current connector
46
     *
47
     * @return DBConnector
48
     */
49
    public function getConnector()
50
    {
51
        return $this->realConn->getConnector();
52
    }
53
54
    /**
55
     * Injector injection point for connector dependency
56
     *
57
     * @param DBConnector $connector
58
     */
59
    public function setConnector(DBConnector $connector)
60
    {
61
        parent::setConnector($connector);
62
        $this->realConn->setConnector($connector);
63
    }
64
65
    /**
66
     * Returns the current schema manager
67
     *
68
     * @return DBSchemaManager
69
     */
70
    public function getSchemaManager()
71
    {
72
        return $this->realConn->getSchemaManager();
73
    }
74
75
    /**
76
     * Injector injection point for schema manager
77
     *
78
     * @param DBSchemaManager $schemaManager
79
     */
80
    public function setSchemaManager(DBSchemaManager $schemaManager)
81
    {
82
        parent::setSchemaManager($schemaManager);
83
        $this->realConn->setSchemaManager($schemaManager);
84
    }
85
86
    /**
87
     * Returns the current query builder
88
     *
89
     * @return DBQueryBuilder
90
     */
91
    public function getQueryBuilder()
92
    {
93
        return $this->realConn->getQueryBuilder();
94
    }
95
96
    /**
97
     * Injector injection point for schema manager
98
     *
99
     * @param DBQueryBuilder $queryBuilder
100
     */
101
    public function setQueryBuilder(DBQueryBuilder $queryBuilder)
102
    {
103
        parent::setQueryBuilder($queryBuilder);
104
        $this->realConn->setQueryBuilder($queryBuilder);
105
    }
106
107
    /**
108
     * Determines if the query should be previewed, and thus interrupted silently.
109
     * If so, this function also displays the query via the debuging system.
110
     * Subclasess should respect the results of this call for each query, and not
111
     * execute any queries that generate a true response.
112
     *
113
     * @param string $sql The query to be executed
114
     * @return boolean Flag indicating that the query was previewed
115
     */
116 View Code Duplication
    protected function previewWrite($sql)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
previewWrite uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
117
    {
118
        // Only preview if previewWrite is set, we are in dev mode, and
119
        // the query is mutable
120
        if (isset($_REQUEST['previewwrite']) && Director::isDev() && $this->connector->isQueryMutable($sql)
121
        ) {
122
            // output preview message
123
            Debug::message("Will execute: $sql");
124
            return true;
125
        } else {
126
            return false;
127
        }
128
    }
129
130
    /**
131
     * Allows the display and benchmarking of queries as they are being run
132
     *
133
     * @param string $sql Query to run, and single parameter to callback
134
     * @param callable $callback Callback to execute code
135
     * @param array $parameters
136
     * @return mixed Result of query
137
     */
138
    protected function benchmarkQuery($sql, $callback, $parameters = array())
139
    {
140
        $starttime   = microtime(true);
141
        $startmemory = memory_get_usage(true);
142
143 View Code Duplication
        if ($this->showQueries && Director::isDev()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
144
            $starttime    = microtime(true);
145
            $result       = $callback($sql);
146
            $endtime      = round(microtime(true) - $starttime, 4);
147
148
            $formattedSql = JdornSqlFormatter::format($sql);
149
            $rows         = $result->numRecords();
150
            echo '<pre>The following query took <b>'.$endtime.'</b>s an returned <b>'.$rows."</b> row(s) \n";
151
            echo 'Triggered by: <i>'.$this->findSource().'</i></pre>';
152
            echo $formattedSql;
153
154
            $results = iterator_to_array($result);
155
            if ($rows > 0) {
156
                if ($rows == 1) {
157
                    dump($results[0]);
158
                } else {
159
                    $linearValues = count($results[0]);
160
                    if ($linearValues) {
161
                        dump(implode(',', (array_map(function ($item) {
162
                            return $item[key($item)];
163
                        }, $results))));
164
                    } else {
165
                        if ($rows < 20) {
166
                            dump($results);
167
                        } else {
168
                            dump("Too many results to display");
169
                        }
170
                    }
171
                }
172
            }
173
            echo '<hr/>';
174
175
            $handle = $result;
176
            $handle->rewind(); // Rewind the results
177
        } else {
178
            /* @var $handle MySQLQuery  */
179
            $handle = $callback($sql);
180
        }
181
182
        $endtime   = microtime(true);
183
        $endmemory = memory_get_usage(true);
184
185
186
        $rawsql = $sql;
187
188
        $select = null;
0 ignored issues
show
Unused Code introduced by
$select 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...
189
190
        // Sometimes, ugly spaces are there
191
        $sql = preg_replace('/[[:blank:]]+/', ' ', trim($sql));
192
193
        $shortsql = $sql;
194
195
        // Sometimes, the select statement can be very long and unreadable
196
        $matches = null;
197
        preg_match_all('/SELECT(.+?) FROM/is', $sql, $matches);
198
        $select  = empty($matches[1]) ? null : trim($matches[1][0]);
199 View Code Duplication
        if (strlen($select) > 100) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
200
            $shortsql = str_replace($select, '"ClickToShowFields"', $sql);
201
        } else {
202
            $select = null;
203
        }
204
205
        $this->queries[] = array(
206
            'raw_query' => $rawsql,
207
            'short_query' => $shortsql,
208
            'select' => $select,
209
            'query' => $sql,
210
            'start_time' => $starttime,
211
            'end_time' => $endtime,
212
            'duration' => $endtime - $starttime,
213
            'memory' => $endmemory - $startmemory,
214
            'rows' => $handle ? $handle->numRecords() : null,
215
            'success' => $handle ? true : false,
216
            'database' => $this->currentDatabase(),
0 ignored issues
show
Deprecated Code introduced by
The method SS_Database::currentDatabase() has been deprecated with message: since version 4.0 Use getSelectedDatabase instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
217
            'source' => $this->findSource ? $this->findSource() : null
218
        );
219
        return $handle;
220
    }
221
222 View Code Duplication
    protected function findSource()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
223
    {
224
        $traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT);
225
226
        // Not relevant to determine source
227
        $internalClasses = array(
228
            'DB', 'SQLExpression', 'DataList', 'DataObject',
229
            'DataQuery', 'SQLSelect', 'SQLQuery', 'SS_Map', 'SS_ListDecorator', 'Object'
230
        );
231
232
        $viewerClasses = array(
233
            'SSViewer_DataPresenter', 'SSViewer_Scope', 'SSViewer',
234
            'ViewableData'
235
        );
236
237
        $sources = array();
238
        foreach ($traces as $trace) {
239
            $class    = isset($trace['class']) ? $trace['class'] : null;
240
            $line     = isset($trace['line']) ? $trace['line'] : null;
241
            $function = isset($trace['function']) ? $trace['function'] : null;
242
            $type     = isset($trace['type']) ? $trace['type'] : '::';
243
244
            /* @var $object SSViewer */
245
            $object = isset($trace['object']) ? $trace['object'] : null;
246
247
            if (!$class) {
248
                continue;
249
            }
250
            if ($function && $function == '{closure}') {
251
                continue;
252
            }
253
            if (strpos($class, 'DebugBar') === 0) {
254
                continue;
255
            }
256
            if (in_array($class, $internalClasses)) {
257
                continue;
258
            }
259
            if (in_array($class, $viewerClasses)) {
260
                if ($function == 'includeGeneratedTemplate') {
261
                    $templates = $object->templates();
262
263
                    $template = null;
264
                    if (isset($templates['main'])) {
265
                        $template = basename($templates['main']);
266
                    } else {
267
                        $keys = array_keys($templates);
268
                        $key  = reset($keys);
269
                        if (isset($templates[$key])) {
270
                            $template = $key.':'.basename($templates[$key]);
271
                        }
272
                    }
273
                    if ($template) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $template of type string|null is loosely compared to true; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
274
                        $sources[] = $template;
275
                    }
276
                }
277
                continue;
278
            }
279
280
            $name = $class;
281
            if ($function) {
282
                $name .= $type.$function;
283
            }
284
            if ($line) {
285
                $name .= ':'.$line;
286
            }
287
288
            $sources[] = $name;
289
290
            if (count($sources) > 3) {
291
                break;
292
            }
293
294
295
            // We reached a Controller, exit loop
296
            if ($object && $object instanceof Controller) {
297
                break;
298
            }
299
        }
300
301
        if (empty($sources)) {
302
            return 'Undefined source';
303
        }
304
        return implode(' > ', $sources);
305
    }
306
307
    /**
308
     * Execute the given SQL query.
309
     *
310
     * @param string $sql The SQL query to execute
311
     * @param int $errorLevel The level of error reporting to enable for the query
312
     * @return SS_Query
313
     */
314 View Code Duplication
    public function query($sql, $errorLevel = E_USER_ERROR)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
315
    {
316
        // Check if we should only preview this query
317
        if ($this->previewWrite($sql)) {
318
            return;
319
        }
320
321
        if (!$this->connector) {
322
            $self = $this;
323
            return $this->benchmarkQuery(
324
                $sql,
325
                function ($sql) use ($self, $errorLevel) {
326
                    return $self->oldQuery($sql, $errorLevel);
327
                }
328
            );
329
        }
330
331
        // Benchmark query
332
        $connector = $this->connector;
333
        return $this->benchmarkQuery(
334
            $sql,
335
            function ($sql) use ($connector, $errorLevel) {
336
                return $connector->query($sql, $errorLevel);
337
            }
338
        );
339
    }
340
341
    public function oldQuery($sql, $errorLevel = E_USER_ERROR)
342
    {
343
        return $this->realConn->query($sql, $errorLevel);
344
    }
345
346
    /**
347
     * Execute the given SQL parameterised query with the specified arguments
348
     *
349
     * @param string $sql The SQL query to execute. The ? character will denote parameters.
350
     * @param array $parameters An ordered list of arguments.
351
     * @param int $errorLevel The level of error reporting to enable for the query
352
     * @return SS_Query
353
     */
354 View Code Duplication
    public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
355
    {
356
        // Check if we should only preview this query
357
        if ($this->previewWrite($sql)) {
358
            return;
359
        }
360
361
        // Benchmark query
362
        $connector = $this->connector;
363
        return $this->benchmarkQuery(
364
            $sql,
365
            function ($sql) use ($connector, $parameters, $errorLevel) {
366
                return $connector->preparedQuery($sql, $parameters, $errorLevel);
367
            }
368
        );
369
    }
370
371
    /**
372
     * @return array
373
     */
374
    public function getQueries()
375
    {
376
        return $this->queries;
377
    }
378
379
    /**
380
     * @param string $name
381
     * @param array $arguments
382
     * @return mixed
383
     */
384
    public function __call($name, $arguments)
385
    {
386
        //return call_user_func_array([$this->realConn, __FUNCTION__],            func_get_args());
0 ignored issues
show
Unused Code Comprehensibility introduced by
69% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
387
        return call_user_func_array(array($this->realConn, $name), $arguments);
388
    }
389
390
    public function addslashes($val)
391
    {
392
        return call_user_func_array(
393
            array($this->realConn, __FUNCTION__),
394
            func_get_args()
395
        );
396
    }
397
398 View Code Duplication
    public function alterTable(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
399
        $table,
400
        $newFields = null,
401
        $newIndexes = null,
402
        $alteredFields = null,
403
        $alteredIndexes = null,
404
        $alteredOptions = null,
405
        $advancedOptions = null
406
    ) {
407
408
        return call_user_func_array(
409
            array($this->realConn, __FUNCTION__),
410
            func_get_args()
411
        );
412
    }
413
414 View Code Duplication
    public function comparisonClause(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
415
        $field,
416
        $value,
417
        $exact = false,
418
        $negate = false,
419
        $caseSensitive = false,
420
        $parameterised = false
421
    ) {
422
423
        return call_user_func_array(
424
            array($this->realConn, __FUNCTION__),
425
            func_get_args()
426
        );
427
    }
428
429
    public function createDatabase()
430
    {
431
        return call_user_func_array(
432
            array($this->realConn, __FUNCTION__),
433
            func_get_args()
434
        );
435
    }
436
437
    public function createField($table, $field, $spec)
438
    {
439
        return call_user_func_array(
440
            array($this->realConn, __FUNCTION__),
441
            func_get_args()
442
        );
443
    }
444
445 View Code Duplication
    public function createTable(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
446
        $table,
447
        $fields = null,
448
        $indexes = null,
449
        $options = null,
450
        $advancedOptions = null
451
    ) {
452
453
        return call_user_func_array(
454
            array($this->realConn, __FUNCTION__),
455
            func_get_args()
456
        );
457
    }
458
459
    public function datetimeDifferenceClause($date1, $date2)
460
    {
461
        return call_user_func_array(
462
            array($this->realConn, __FUNCTION__),
463
            func_get_args()
464
        );
465
    }
466
467
    public function datetimeIntervalClause($date, $interval)
468
    {
469
        return call_user_func_array(
470
            array($this->realConn, __FUNCTION__),
471
            func_get_args()
472
        );
473
    }
474
475
    public function enumValuesForField($tableName, $fieldName)
476
    {
477
        return call_user_func_array(
478
            array($this->realConn, __FUNCTION__),
479
            func_get_args()
480
        );
481
    }
482
483
    public function fieldList($table)
484
    {
485
        return call_user_func_array(
486
            array($this->realConn, __FUNCTION__),
487
            func_get_args()
488
        );
489
    }
490
491
    public function formattedDatetimeClause($date, $format)
492
    {
493
        return call_user_func_array(
494
            array($this->realConn, __FUNCTION__),
495
            func_get_args()
496
        );
497
    }
498
499
    public function getConnect($parameters)
500
    {
501
        return call_user_func_array(
502
            array($this->realConn, __FUNCTION__),
503
            func_get_args()
504
        );
505
    }
506
507
    public function getGeneratedID($table)
508
    {
509
        return call_user_func_array(
510
            array($this->realConn, __FUNCTION__),
511
            func_get_args()
512
        );
513
    }
514
515
    public function hasTable($tableName)
516
    {
517
        return call_user_func_array(
518
            array($this->realConn, __FUNCTION__),
519
            func_get_args()
520
        );
521
    }
522
523
    public function isActive()
524
    {
525
        return call_user_func_array(
526
            array($this->realConn, __FUNCTION__),
527
            func_get_args()
528
        );
529
    }
530
531
    public function renameField($tableName, $oldName, $newName)
532
    {
533
        return call_user_func_array(
534
            array($this->realConn, __FUNCTION__),
535
            func_get_args()
536
        );
537
    }
538
539
    public function renameTable($oldTableName, $newTableName)
540
    {
541
        return call_user_func_array(
542
            array($this->realConn, __FUNCTION__),
543
            func_get_args()
544
        );
545
    }
546
547
    public function supportsTimezoneOverride()
548
    {
549
        return call_user_func_array(
550
            array($this->realConn, __FUNCTION__),
551
            func_get_args()
552
        );
553
    }
554
555
    public function supportsTransactions()
556
    {
557
        return call_user_func_array(
558
            array($this->realConn, __FUNCTION__),
559
            func_get_args()
560
        );
561
    }
562
563
    public function tableList()
564
    {
565
        return call_user_func_array(
566
            array($this->realConn, __FUNCTION__),
567
            func_get_args()
568
        );
569
    }
570
571
    public function transactionEnd($chain = false)
572
    {
573
        return call_user_func_array(
574
            array($this->realConn, __FUNCTION__),
575
            func_get_args()
576
        );
577
    }
578
579
    public function transactionRollback($savepoint = false)
580
    {
581
        return call_user_func_array(
582
            array($this->realConn, __FUNCTION__),
583
            func_get_args()
584
        );
585
    }
586
587
    public function transactionSavepoint($savepoint)
588
    {
589
        return call_user_func_array(
590
            array($this->realConn, __FUNCTION__),
591
            func_get_args()
592
        );
593
    }
594
595
    public function transactionStart(
596
        $transaction_mode = false,
597
        $session_characteristics = false
598
    ) {
599
600
        return call_user_func_array(
601
            array($this->realConn, __FUNCTION__),
602
            func_get_args()
603
        );
604
    }
605
606
    public function clearTable($table)
607
    {
608
        return call_user_func_array(
609
            array($this->realConn, __FUNCTION__),
610
            func_get_args()
611
        );
612
    }
613
614
    public function getDatabaseServer()
615
    {
616
        return "mysql";
617
    }
618
619
    public function now()
620
    {
621
        return 'NOW()';
622
    }
623
624
    public function random()
625
    {
626
        return 'RAND()';
627
    }
628
629
    public function searchEngine(
630
        $classesToSearch,
631
        $keywords,
632
        $start,
633
        $pageLength,
634
        $sortBy = "Relevance DESC",
635
        $extraFilter = "",
636
        $booleanSearch = false,
637
        $alternativeFileFilter = "",
638
        $invertedMatch = false
639
    ) {
640
641
        if (!class_exists('SiteTree')) {
642
            throw new Exception('MySQLDatabase->searchEngine() requires "SiteTree" class');
643
        }
644
        if (!class_exists('File')) {
645
            throw new Exception('MySQLDatabase->searchEngine() requires "File" class');
646
        }
647
648
        $keywords           = $this->escapeString($keywords);
649
        $htmlEntityKeywords = htmlentities($keywords, ENT_NOQUOTES, 'UTF-8');
650
651
        $extraFilters = array('SiteTree' => '', 'File' => '');
652
653
        if ($booleanSearch) {
654
            $boolean = "IN BOOLEAN MODE";
655
        }
656
657
        if ($extraFilter) {
658
            $extraFilters['SiteTree'] = " AND $extraFilter";
659
660
            if ($alternativeFileFilter) {
661
                $extraFilters['File'] = " AND $alternativeFileFilter";
662
            } else {
663
                $extraFilters['File'] = $extraFilters['SiteTree'];
664
            }
665
        }
666
667
        // Always ensure that only pages with ShowInSearch = 1 can be searched
668
        $extraFilters['SiteTree'] .= " AND ShowInSearch <> 0";
669
670
        // File.ShowInSearch was added later, keep the database driver backwards compatible
671
        // by checking for its existence first
672
        $fields = $this->fieldList('File');
673
        if (array_key_exists('ShowInSearch', $fields)) {
674
            $extraFilters['File'] .= " AND ShowInSearch <> 0";
675
        }
676
677
        $limit = $start.", ".(int) $pageLength;
678
679
        $notMatch = $invertedMatch ? "NOT " : "";
680
        if ($keywords) {
681
            $match['SiteTree'] = "
0 ignored issues
show
Coding Style Comprehensibility introduced by
$match was never initialized. Although not strictly required by PHP, it is generally a good practice to add $match = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
682
				MATCH (Title, MenuTitle, Content, MetaDescription) AGAINST ('$keywords' $boolean)
0 ignored issues
show
Bug introduced by
The variable $boolean does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
683
				+ MATCH (Title, MenuTitle, Content, MetaDescription) AGAINST ('$htmlEntityKeywords' $boolean)
684
			";
685
            $match['File'] = "MATCH (Filename, Title, Content) AGAINST ('$keywords' $boolean) AND ClassName = 'File'";
686
687
            // We make the relevance search by converting a boolean mode search into a normal one
688
            $relevanceKeywords = str_replace(array('*', '+', '-'), '', $keywords);
689
            $htmlEntityRelevanceKeywords = str_replace(array('*', '+', '-'), '', $htmlEntityKeywords);
690
            $relevance['SiteTree']       = "MATCH (Title, MenuTitle, Content, MetaDescription) "
0 ignored issues
show
Coding Style Comprehensibility introduced by
$relevance was never initialized. Although not strictly required by PHP, it is generally a good practice to add $relevance = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
691
                ."AGAINST ('$relevanceKeywords') "
692
                ."+ MATCH (Title, MenuTitle, Content, MetaDescription) AGAINST ('$htmlEntityRelevanceKeywords')";
693
            $relevance['File']           = "MATCH (Filename, Title, Content) AGAINST ('$relevanceKeywords')";
694
        } else {
695
            $relevance['SiteTree'] = $relevance['File']     = 1;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$relevance was never initialized. Although not strictly required by PHP, it is generally a good practice to add $relevance = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
696
            $match['SiteTree']     = $match['File']         = "1 = 1";
0 ignored issues
show
Coding Style Comprehensibility introduced by
$match was never initialized. Although not strictly required by PHP, it is generally a good practice to add $match = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
697
        }
698
699
        // Generate initial DataLists and base table names
700
        $lists       = array();
701
        $baseClasses = array('SiteTree' => '', 'File' => '');
702
        foreach ($classesToSearch as $class) {
703
            $lists[$class] = DataList::create($class)->where($notMatch . $match[$class] . $extraFilters[$class], "");
0 ignored issues
show
Unused Code introduced by
The call to DataList::where() has too many arguments starting with ''.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
704
            $baseClasses[$class] = '"' . $class . '"';
705
        }
706
707
        $charset = Config::inst()->get('MySQLDatabase', 'charset');
708
709
        // Make column selection lists
710
        $select = array(
711
            'SiteTree' => array(
712
                "ClassName", "$baseClasses[SiteTree].\"ID\"", "ParentID",
713
                "Title", "MenuTitle", "URLSegment", "Content",
714
                "LastEdited", "Created",
715
                "Filename" => "_{$charset}''", "Name" => "_{$charset}''",
716
                "Relevance" => $relevance['SiteTree'], "CanViewType"
717
            ),
718
            'File' => array(
719
                "ClassName", "$baseClasses[File].\"ID\"", "ParentID",
720
                "Title", "MenuTitle" => "_{$charset}''", "URLSegment" => "_{$charset}''",
721
                "Content",
722
                "LastEdited", "Created",
723
                "Filename", "Name",
724
                "Relevance" => $relevance['File'], "CanViewType" => "NULL"
725
            ),
726
        );
727
728
        // Process and combine queries
729
        $querySQLs       = array();
730
        $queryParameters = array();
731
        $totalCount      = 0;
732
        foreach ($lists as $class => $list) {
733
            $query = $list->dataQuery()->query();
734
735
            // There's no need to do all that joining
736
            $replacedString = str_replace(array('"', '`'), '', $baseClasses[$class]);
737
            $query->setFrom(array($replacedString => $baseClasses[$class]));
738
            $query->setSelect($select[$class]);
739
            $query->setOrderBy(array());
740
741
            $querySQLs[]     = $query->sql($parameters);
0 ignored issues
show
Bug introduced by
The variable $parameters does not exist. Did you mean $queryParameters?

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...
742
            $queryParameters = array_merge($queryParameters, $parameters);
0 ignored issues
show
Bug introduced by
The variable $parameters does not exist. Did you mean $queryParameters?

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...
743
744
            $totalCount += $query->unlimitedRowCount();
745
        }
746
        $fullQuery = implode(" UNION ", $querySQLs)." ORDER BY $sortBy LIMIT $limit";
747
748
        // Get records
749
        $records = $this->preparedQuery($fullQuery, $queryParameters);
750
751
        $objects = array();
752
753
        foreach ($records as $record) {
754
            $objects[] = new $record['ClassName']($record);
755
        }
756
757
        $list = new PaginatedList(new ArrayList($objects));
758
        $list->setPageStart($start);
759
        $list->setPageLength($pageLength);
760
        $list->setTotalItems($totalCount);
761
762
        // The list has already been limited by the query above
763
        $list->setLimitItems(false);
764
765
        return $list;
766
    }
767
768
    public function supportsCollations()
769
    {
770
        return true;
771
    }
772
}
773