Passed
Push — master ( b4f6c4...81b9da )
by Thomas
02:51 queued 10s
created

DebugBar::setProtectedValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
c 0
b 0
f 0
nc 1
nop 3
dl 0
loc 6
rs 10
1
<?php
2
3
namespace LeKoala\DebugBar;
4
5
use Exception;
6
use Monolog\Logger;
7
use ReflectionObject;
8
use SilverStripe\ORM\DB;
9
use Psr\Log\LoggerInterface;
10
use SilverStripe\Core\Kernel;
11
use DebugBar\JavascriptRenderer;
12
use DebugBar\Storage\FileStorage;
13
use SilverStripe\Control\Director;
14
use SilverStripe\Core\Environment;
15
use SilverStripe\Admin\LeftAndMain;
16
use SilverStripe\View\Requirements;
17
use SilverStripe\Control\Controller;
18
use DebugBar\Bridge\MonologCollector;
19
use SilverStripe\Control\HTTPRequest;
20
use DebugBar\DebugBar as BaseDebugBar;
21
use SilverStripe\Control\Email\Mailer;
22
use SilverStripe\Core\Injector\Injector;
23
use SilverStripe\Core\Config\ConfigLoader;
24
use SilverStripe\Core\Config\Configurable;
25
use SilverStripe\Core\Injector\Injectable;
26
use SilverStripe\ORM\Connect\PDOConnector;
27
use DebugBar\DataCollector\MemoryCollector;
28
use LeKoala\DebugBar\Messages\LogFormatter;
29
use SilverStripe\Admin\AdminRootController;
30
use SilverStripe\Control\Email\SwiftMailer;
31
use DebugBar\DataCollector\PDO\PDOCollector;
32
use DebugBar\DataCollector\PDO\TraceablePDO;
33
use SilverStripe\Core\Manifest\ModuleLoader;
34
use DebugBar\DataCollector\MessagesCollector;
35
use SilverStripe\Core\Manifest\ModuleResource;
36
use LeKoala\DebugBar\Collector\ConfigCollector;
37
use LeKoala\DebugBar\Proxy\ConfigManifestProxy;
38
use LeKoala\DebugBar\Collector\PhpInfoCollector;
39
use LeKoala\DebugBar\Collector\DatabaseCollector;
40
use LeKoala\DebugBar\Collector\TimeDataCollector;
41
use DebugBar\Bridge\SwiftMailer\SwiftLogCollector;
42
use DebugBar\Bridge\SwiftMailer\SwiftMailCollector;
43
use LeKoala\DebugBar\Collector\PartialCacheCollector;
44
use LeKoala\DebugBar\Collector\SilverStripeCollector;
45
use LeKoala\DebugBar\Middleware\DebugBarConfigMiddleware;
0 ignored issues
show
Bug introduced by
The type LeKoala\DebugBar\Middlew...ebugBarConfigMiddleware was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
46
use LeKoala\DebugBar\Proxy\DeltaConfigManifestProxy;
47
use SilverStripe\Config\Collections\CachedConfigCollection;
48
use SilverStripe\Config\Collections\DeltaConfigCollection;
49
50
/**
51
 * A simple helper
52
 */
53
class DebugBar
54
{
55
    use Configurable;
56
    use Injectable;
57
58
    /**
59
     * @var BaseDebugBar
60
     */
61
    protected static $debugbar;
62
63
    /**
64
     * @var bool
65
     */
66
    public static $bufferingEnabled = false;
67
68
    /**
69
     * @var JavascriptRenderer
70
     */
71
    protected static $renderer;
72
73
    /**
74
     * @var bool
75
     */
76
    protected static $showQueries = false;
77
78
    /**
79
     * @var HTTPRequest
80
     */
81
    protected static $request;
82
83
    /**
84
     * Get the Debug Bar instance
85
     * @throws Exception
86
     * @global array $databaseConfig
87
     * @return BaseDebugBar
88
     */
89
    public static function getDebugBar()
90
    {
91
        if (self::$debugbar !== null) {
92
            return self::$debugbar;
93
        }
94
95
        $reasons = self::disabledCriteria();
96
        if (!empty($reasons)) {
97
            self::$debugbar = false; // no need to check again
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type DebugBar\DebugBar of property $debugbar.

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

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

Loading history...
98
            return;
99
        }
100
101
        self::initDebugBar();
102
103
        if (!self::$debugbar) {
104
            throw new Exception("Failed to initialize the DebugBar");
105
        }
106
107
        return self::$debugbar;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::debugbar returns the type void which is incompatible with the documented return type DebugBar\DebugBar.
Loading history...
108
    }
109
110
    /**
111
     * Init the debugbar instance
112
     *
113
     * @global array $databaseConfig
114
     * @return BaseDebugBar|null
115
     */
116
    public static function initDebugBar()
117
    {
118
        // Prevent multiple inits
119
        if (self::$debugbar) {
120
            return self::$debugbar;
121
        }
122
123
        self::$debugbar = $debugbar = new BaseDebugBar();
124
125
        if (isset($_REQUEST['showqueries']) && Director::isDev()) {
126
            self::setShowQueries(true);
127
            unset($_REQUEST['showqueries']);
128
        }
129
130
        $debugbar->addCollector(new PhpInfoCollector());
131
        $debugbar->addCollector(new TimeDataCollector());
132
        $debugbar->addCollector(new MemoryCollector());
133
134
        // Add config proxy replacing the core config manifest
135
        if (self::config()->config_collector) {
136
            /** @var ConfigLoader $configLoader */
137
            $configLoader = Injector::inst()->get(Kernel::class)->getConfigLoader();
138
            // There is no getManifests method on ConfigLoader
139
            $manifests = self::getProtectedValue($configLoader, 'manifests');
140
            foreach ($manifests as $manifestIdx => $manifest) {
141
                if ($manifest instanceof CachedConfigCollection) {
142
                    $manifest = new ConfigManifestProxy($manifest);
143
                    $manifests[$manifestIdx] = $manifest;
144
                }
145
                if ($manifest instanceof DeltaConfigCollection) {
146
                    $manifest = DeltaConfigManifestProxy::createFromOriginal($manifest);
147
                    $manifests[$manifestIdx] = $manifest;
148
                }
149
            }
150
            // Don't push as it may change stack order
151
            self::setProtectedValue($configLoader, 'manifests', $manifests);
152
        }
153
154
        if (self::config()->db_collector) {
155
            $connector = DB::get_connector();
156
            if (!self::config()->get('force_proxy') && $connector instanceof PDOConnector) {
157
                // Use a little bit of magic to replace the pdo instance
158
                $refObject = new ReflectionObject($connector);
159
                $refProperty = $refObject->getProperty('pdoConnection');
160
                $refProperty->setAccessible(true);
161
                $traceablePdo = new TraceablePDO($refProperty->getValue($connector));
162
                $refProperty->setValue($connector, $traceablePdo);
163
164
                $debugbar->addCollector(new PDOCollector($traceablePdo));
165
            } else {
166
                $debugbar->addCollector(new DatabaseCollector);
167
            }
168
        }
169
170
        // Add message collector last so other collectors can send messages to the console using it
171
        $debugbar->addCollector(new MessagesCollector());
172
173
        // Aggregate monolog into messages
174
        $logger = Injector::inst()->get(LoggerInterface::class);
175
        if ($logger instanceof Logger) {
176
            $logCollector = new MonologCollector($logger);
177
            $logCollector->setFormatter(new LogFormatter);
178
            $debugbar['messages']->aggregate($logCollector);
0 ignored issues
show
Bug introduced by
The method aggregate() does not exist on DebugBar\DataCollector\DataCollectorInterface. It seems like you code against a sub-type of DebugBar\DataCollector\DataCollectorInterface such as DebugBar\DataCollector\MessagesCollector. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

178
            $debugbar['messages']->/** @scrutinizer ignore-call */ 
179
                                   aggregate($logCollector);
Loading history...
179
        }
180
181
        // Add some SilverStripe specific infos
182
        $debugbar->addCollector(new SilverStripeCollector);
183
184
        if (self::config()->get('enable_storage')) {
185
            $debugBarTempFolder = TEMP_FOLDER . '/debugbar';
186
            $debugbar->setStorage($fileStorage = new FileStorage($debugBarTempFolder));
187
            if (isset($_GET['flush']) && is_dir($debugBarTempFolder)) {
188
                // FileStorage::clear() is implemented with \DirectoryIterator which throws UnexpectedValueException if dir can not be opened
189
                $fileStorage->clear();
190
            }
191
        }
192
193
        if (self::config()->config_collector) {
194
            // Add the config collector
195
            $debugbar->addCollector(new ConfigCollector);
196
        }
197
198
        // Partial cache
199
        if (self::config()->partial_cache_collector) {
200
            $debugbar->addCollector(new PartialCacheCollector);
201
        }
202
203
        // Email logging
204
        if (self::config()->email_collector) {
205
            $mailer = Injector::inst()->get(Mailer::class);
206
            if ($mailer instanceof SwiftMailer) {
207
                $swiftInst = $mailer->getSwiftMailer();
208
                $debugbar['messages']->aggregate(new SwiftLogCollector($swiftInst));
209
                $debugbar->addCollector(new SwiftMailCollector($swiftInst));
210
            }
211
        }
212
213
        // Since we buffer everything, why not enable all dev options ?
214
        if (self::config()->get('auto_debug')) {
215
            $_REQUEST['debug'] = true;
216
            $_REQUEST['debug_request'] = true;
217
        }
218
219
        if (isset($_REQUEST['debug']) || isset($_REQUEST['debug_request'])) {
220
            self::$bufferingEnabled = true;
221
            ob_start(); // We buffer everything until we have called an action
222
        }
223
224
        return $debugbar;
225
    }
226
227
    /**
228
     * Access a protected property when the api does not allow access
229
     *
230
     * @param object $object
231
     * @param string $property
232
     * @return mixed
233
     */
234
    protected static function getProtectedValue($object, $property)
235
    {
236
        $refObject = new ReflectionObject($object);
237
        $refProperty = $refObject->getProperty($property);
238
        $refProperty->setAccessible(true);
239
        return $refProperty->getValue($object);
240
    }
241
242
    /**
243
     * Set a protected property when the api does not allow access
244
     *
245
     * @param object $object
246
     * @param string $property
247
     * @param mixed $newValue
248
     * @return void
249
     */
250
    protected static function setProtectedValue($object, $property, $newValue)
251
    {
252
        $refObject = new ReflectionObject($object);
253
        $refProperty = $refObject->getProperty($property);
254
        $refProperty->setAccessible(true);
255
        return $refProperty->setValue($object, $newValue);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $refProperty->setValue($object, $newValue) targeting ReflectionProperty::setValue() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
256
    }
257
258
    /**
259
     * Clear the current instance of DebugBar
260
     *
261
     * @return void
262
     */
263
    public static function clearDebugBar()
264
    {
265
        self::$debugbar = null;
266
    }
267
268
    /**
269
     * @return boolean
270
     */
271
    public static function getShowQueries()
272
    {
273
        return self::$showQueries;
274
    }
275
276
    /**
277
     * Override default showQueries mode
278
     *
279
     * @param boolean $showQueries
280
     * @return void
281
     */
282
    public static function setShowQueries($showQueries)
283
    {
284
        self::$showQueries = $showQueries;
285
    }
286
287
    /**
288
     * Helper to access this module resources
289
     *
290
     * @param string $path
291
     * @return ModuleResource
292
     */
293
    public static function moduleResource($path)
294
    {
295
        return ModuleLoader::getModule('lekoala/silverstripe-debugbar')->getResource($path);
296
    }
297
298
    /**
299
     * Include DebugBar assets using Requirements API
300
     *
301
     * @return void
302
     */
303
    public static function includeRequirements()
304
    {
305
        $debugbar = self::getDebugBar();
306
307
        if (!$debugbar) {
0 ignored issues
show
introduced by
$debugbar is of type DebugBar\DebugBar, thus it always evaluated to true.
Loading history...
308
            return;
309
        }
310
311
        // Already called
312
        if (self::$renderer) {
313
            return;
314
        }
315
316
        $renderer = $debugbar->getJavascriptRenderer();
317
318
        // We don't need the true path since we are going to use Requirements API that appends the BASE_PATH
319
        $assetsResource = self::moduleResource('assets');
320
        $renderer->setBasePath($assetsResource->getRelativePath());
321
        $renderer->setBaseUrl(Director::makeRelative($assetsResource->getURL()));
322
323
        $includeJquery = self::config()->get('include_jquery');
324
        // In CMS, jQuery is already included
325
        if (self::isAdminController()) {
326
            $includeJquery = false;
327
        }
328
        // If jQuery is already included, set to false
329
        $js = Requirements::backend()->getJavascript();
330
        foreach ($js as $url => $args) {
331
            $name = basename($url);
332
            if ($name == 'jquery.js' || $name == 'jquery.min.js') {
333
                $includeJquery = false;
334
                break;
335
            }
336
        }
337
338
        if ($includeJquery) {
339
            $renderer->setEnableJqueryNoConflict(true);
340
        } else {
341
            $renderer->disableVendor('jquery');
342
            $renderer->setEnableJqueryNoConflict(false);
343
        }
344
345
        if (DebugBar::config()->get('enable_storage')) {
346
            $renderer->setOpenHandlerUrl('__debugbar');
347
        }
348
349
        foreach ($renderer->getAssets('css') as $cssFile) {
350
            Requirements::css(Director::makeRelative(ltrim($cssFile, '/')));
351
        }
352
353
        foreach ($renderer->getAssets('js') as $jsFile) {
354
            Requirements::javascript(Director::makeRelative(ltrim($jsFile, '/')));
355
        }
356
357
        self::$renderer = $renderer;
358
    }
359
360
    /**
361
     * Returns the script to display the DebugBar
362
     *
363
     * @return string
364
     */
365
    public static function renderDebugBar()
366
    {
367
        if (!self::$renderer) {
368
            return;
369
        }
370
371
        // Requirements may have been cleared (CMS iframes...) or not set (Security...)
372
        $js = Requirements::backend()->getJavascript();
373
        $debugBarResource = self::moduleResource('assets/debugbar.js');
374
        $path = $debugBarResource->getRelativePath();
375
376
        // Url in getJavascript has a / slash, so fix if necessary
377
        $path = str_replace("assets\\debugbar.js", "assets/debugbar.js", $path);
378
        if (!array_key_exists($path, $js)) {
379
            return;
380
        }
381
        $initialize = true;
382
        if (Director::is_ajax()) {
383
            $initialize = false;
384
        }
385
386
        $script = self::$renderer->render($initialize);
387
        return $script;
388
    }
389
390
    /**
391
     * Get all criteria why the DebugBar could be disabled
392
     *
393
     * @return array
394
     */
395
    public static function disabledCriteria()
396
    {
397
        $reasons = array();
398
        if (!Director::isDev()) {
399
            $reasons[] = 'Not in dev mode';
400
        }
401
        if (self::isDisabled()) {
402
            $reasons[] = 'Disabled by a constant or configuration';
403
        }
404
        if (self::vendorNotInstalled()) {
405
            $reasons[] = 'DebugBar is not installed in vendors';
406
        }
407
        if (self::notLocalIp()) {
408
            $reasons[] = 'Not a local ip';
409
        }
410
        if (Director::is_cli()) {
411
            $reasons[] = 'In CLI mode';
412
        }
413
        if (self::isDevUrl()) {
414
            $reasons[] = 'Dev tools';
415
        }
416
        if (self::isAdminUrl() && !self::config()->get('enabled_in_admin')) {
417
            $reasons[] = 'In admin';
418
        }
419
        if (isset($_GET['CMSPreview'])) {
420
            $reasons[] = 'CMS Preview';
421
        }
422
        return $reasons;
423
    }
424
425
    /**
426
     * Determine why DebugBar is disabled
427
     *
428
     * Deprecated in favor of disabledCriteria
429
     *
430
     * @return string
431
     */
432
    public static function whyDisabled()
433
    {
434
        $reasons = self::disabledCriteria();
435
        if (!empty($reasons)) {
436
            return $reasons[0];
437
        }
438
        return "I don't know why";
439
    }
440
441
    public static function vendorNotInstalled()
442
    {
443
        return !class_exists('DebugBar\\StandardDebugBar');
444
    }
445
446
    public static function notLocalIp()
447
    {
448
        if (!self::config()->get('check_local_ip')) {
449
            return false;
450
        }
451
        if (isset($_SERVER['REMOTE_ADDR'])) {
452
            return !in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1', '1'));
453
        }
454
        return false;
455
    }
456
457
    public static function isDisabled()
458
    {
459
        if (Environment::getEnv('DEBUGBAR_DISABLE') || static::config()->get('disabled')) {
460
            return true;
461
        }
462
        return false;
463
    }
464
465
    public static function isDevUrl()
466
    {
467
        return strpos(self::getRequestUrl(), '/dev/') === 0;
468
    }
469
470
    public static function isAdminUrl()
471
    {
472
        $baseUrl = rtrim(BASE_URL, '/');
473
        if (class_exists(AdminRootController::class)) {
474
            $adminUrl = AdminRootController::config()->get('url_base');
475
        } else {
476
            $adminUrl = 'admin';
477
        }
478
479
        return strpos(self::getRequestUrl(), $baseUrl . '/' . $adminUrl . '/') === 0;
480
    }
481
482
    public static function isAdminController()
483
    {
484
        if (Controller::curr()) {
485
            return Controller::curr() instanceof LeftAndMain;
486
        }
487
        return self::isAdminUrl();
488
    }
489
490
    /**
491
     * Avoid triggering data collection for open handler
492
     *
493
     * @return boolean
494
     */
495
    public static function isDebugBarRequest()
496
    {
497
        if ($url = self::getRequestUrl()) {
498
            return strpos($url, '/__debugbar') === 0;
499
        }
500
        return true;
501
    }
502
503
    /**
504
     * Get request url
505
     *
506
     * @return string
507
     */
508
    public static function getRequestUrl()
509
    {
510
        if (isset($_REQUEST['url'])) {
511
            return $_REQUEST['url'];
512
        }
513
        if (isset($_SERVER['REQUEST_URI'])) {
514
            return $_SERVER['REQUEST_URI'];
515
        }
516
        return '';
517
    }
518
519
    /**
520
     * Helper to make code cleaner
521
     *
522
     * @param callable $callback
523
     */
524
    public static function withDebugBar($callback)
525
    {
526
        if (self::getDebugBar() && !self::isDebugBarRequest()) {
527
            $callback(self::getDebugBar());
528
        }
529
    }
530
531
    /**
532
     * Set the current request. Is provided by the DebugBarMiddleware.
533
     *
534
     * @param HTTPRequest $request
535
     */
536
    public static function setRequest(HTTPRequest $request)
537
    {
538
        self::$request = $request;
539
    }
540
541
    /**
542
     * Get the current request
543
     *
544
     * @return HTTPRequest
545
     */
546
    public static function getRequest()
547
    {
548
        if (self::$request) {
549
            return self::$request;
550
        }
551
        // Fall back to trying from the global state
552
        if (Controller::has_curr()) {
553
            return Controller::curr()->getRequest();
554
        }
555
    }
556
}
557