Passed
Push — master ( 7f745f...f5786a )
by Thomas
02:48
created

code/DebugBar.php (1 issue)

assigning incompatible types to properties.

Bug Documentation Minor
1
<?php
2
3
namespace LeKoala\DebugBar;
4
5
use DebugBar\JavascriptRenderer;
6
use Exception;
7
use LeKoala\DebugBar\Collector\PhpInfoCollector;
8
use Monolog\Logger;
9
use ReflectionObject;
10
use SilverStripe\Core\Config\ConfigLoader;
11
use SilverStripe\ORM\DB;
12
use Psr\Log\LoggerInterface;
13
use SilverStripe\Core\Kernel;
14
use DebugBar\Storage\FileStorage;
15
use SilverStripe\Control\Director;
16
use SilverStripe\Core\Environment;
17
use SilverStripe\Admin\LeftAndMain;
18
use SilverStripe\View\Requirements;
19
use SilverStripe\Control\Controller;
20
use DebugBar\Bridge\MonologCollector;
21
use SilverStripe\Control\HTTPRequest;
22
use DebugBar\DebugBar as BaseDebugBar;
23
use SilverStripe\Control\Email\Mailer;
24
use SilverStripe\Core\Injector\Injector;
25
use SilverStripe\Core\Config\Configurable;
26
use SilverStripe\Core\Injector\Injectable;
27
use SilverStripe\ORM\Connect\PDOConnector;
28
use DebugBar\DataCollector\MemoryCollector;
29
use LeKoala\DebugBar\Messages\LogFormatter;
30
use SilverStripe\Admin\AdminRootController;
31
use SilverStripe\Control\Email\SwiftMailer;
32
use DebugBar\DataCollector\PDO\PDOCollector;
33
use DebugBar\DataCollector\PDO\TraceablePDO;
34
use SilverStripe\Core\Manifest\ModuleLoader;
35
use DebugBar\DataCollector\MessagesCollector;
36
use SilverStripe\Core\Manifest\ModuleResource;
37
use LeKoala\DebugBar\Collector\ConfigCollector;
38
use LeKoala\DebugBar\Proxy\ConfigManifestProxy;
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 SilverStripe\Config\Collections\CachedConfigCollection;
46
47
/**
48
 * A simple helper
49
 */
50
class DebugBar
51
{
52
    use Configurable;
53
    use Injectable;
54
55
    /**
56
     * @var BaseDebugBar
57
     */
58
    protected static $debugbar;
59
60
    /**
61
     * @var bool
62
     */
63
    public static $bufferingEnabled = false;
64
65
    /**
66
     * @var JavascriptRenderer
67
     */
68
    protected static $renderer;
69
70
    /**
71
     * @var bool
72
     */
73
    protected static $showQueries = false;
74
75
    /**
76
     * @var HTTPRequest
77
     */
78
    protected static $request;
79
80
    /**
81
     * Get the Debug Bar instance
82
     * @throws Exception
83
     * @global array $databaseConfig
84
     * @return BaseDebugBar
85
     */
86
    public static function getDebugBar()
87
    {
88
        if (self::$debugbar !== null) {
89
            return self::$debugbar;
90
        }
91
92
        $reasons = self::disabledCriteria();
93
        if (!empty($reasons)) {
94
            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...
95
            return;
96
        }
97
98
        self::initDebugBar();
99
100
        if (!self::$debugbar) {
101
            throw new Exception("Failed to initialize the DebugBar");
102
        }
103
104
        return self::$debugbar;
105
    }
106
107
    /**
108
     * Init the debugbar instance
109
     *
110
     * @global array $databaseConfig
111
     * @return BaseDebugBar|null
112
     */
113
    public static function initDebugBar()
114
    {
115
        // Prevent multiple inits
116
        if (self::$debugbar) {
117
            return self::$debugbar;
118
        }
119
120
        self::$debugbar = $debugbar = new BaseDebugBar();
121
122
        if (isset($_REQUEST['showqueries']) && Director::isDev()) {
123
            self::setShowQueries(true);
124
            unset($_REQUEST['showqueries']);
125
        }
126
127
        $debugbar->addCollector(new PhpInfoCollector());
128
        $debugbar->addCollector(new TimeDataCollector());
129
        $debugbar->addCollector(new MemoryCollector());
130
131
        // Add config proxy replacing the core config manifest
132
        $configManifest = false;
133
134
        if (self::config()->config_collector) {
135
            /** @var ConfigLoader $configLoader */
136
            $configLoader = Injector::inst()->get(Kernel::class)->getConfigLoader();
137
            // Let's safely access the manifest value without popping things
138
            $manifests = self::getProtectedValue($configLoader, 'manifests');
139
            foreach ($manifests as $manifest) {
140
                if ($manifest instanceof CachedConfigCollection) {
141
                    $configManifest = $manifest;
142
                    break;
143
                }
144
            }
145
            // We can display a CachedConfigCollection
146
            if ($configManifest) {
147
                $configProxy = new ConfigManifestProxy($configManifest);
148
                $configLoader->pushManifest($configProxy);
149
            }
150
        }
151
152
        if (self::config()->db_collector) {
153
            $connector = DB::get_connector();
154
            if (!self::config()->get('force_proxy') && $connector instanceof PDOConnector) {
155
                // Use a little bit of magic to replace the pdo instance
156
                $refObject = new ReflectionObject($connector);
157
                $refProperty = $refObject->getProperty('pdoConnection');
158
                $refProperty->setAccessible(true);
159
                $traceablePdo = new TraceablePDO($refProperty->getValue($connector));
160
                $refProperty->setValue($connector, $traceablePdo);
161
162
                $debugbar->addCollector(new PDOCollector($traceablePdo));
163
            } else {
164
                $debugbar->addCollector(new DatabaseCollector);
165
            }
166
        }
167
168
        // Add message collector last so other collectors can send messages to the console using it
169
        $debugbar->addCollector(new MessagesCollector());
170
171
        // Aggregate monolog into messages
172
        $logger = Injector::inst()->get(LoggerInterface::class);
173
        if ($logger instanceof Logger) {
174
            $logCollector = new MonologCollector($logger);
175
            $logCollector->setFormatter(new LogFormatter);
176
            $debugbar['messages']->aggregate($logCollector);
177
        }
178
179
        // Add some SilverStripe specific infos
180
        $debugbar->addCollector(new SilverStripeCollector);
181
182
        if (self::config()->get('enable_storage')) {
183
            $debugBarTempFolder = TEMP_FOLDER . '/debugbar';
184
            $debugbar->setStorage($fileStorage = new FileStorage($debugBarTempFolder));
185
            if (isset($_GET['flush']) && is_dir($debugBarTempFolder)) {
186
                // FileStorage::clear() is implemented with \DirectoryIterator which throws UnexpectedValueException if dir can not be opened
187
                $fileStorage->clear();
188
            }
189
        }
190
191
        if ($configManifest) {
192
            // Add the config collector
193
            $debugbar->addCollector(new ConfigCollector);
194
        }
195
196
        // Partial cache
197
        if (self::config()->partial_cache_collector) {
198
            $debugbar->addCollector(new PartialCacheCollector);
199
        }
200
201
        // Email logging
202
        if (self::config()->email_collector) {
203
            $mailer = Injector::inst()->get(Mailer::class);
204
            if ($mailer instanceof SwiftMailer) {
205
                $swiftInst = $mailer->getSwiftMailer();
206
                $debugbar['messages']->aggregate(new SwiftLogCollector($swiftInst));
207
                $debugbar->addCollector(new SwiftMailCollector($swiftInst));
208
            }
209
        }
210
211
        // Since we buffer everything, why not enable all dev options ?
212
        if (self::config()->get('auto_debug')) {
213
            $_REQUEST['debug'] = true;
214
            $_REQUEST['debug_request'] = true;
215
        }
216
217
        if (isset($_REQUEST['debug']) || isset($_REQUEST['debug_request'])) {
218
            self::$bufferingEnabled = true;
219
            ob_start(); // We buffer everything until we have called an action
220
        }
221
222
        return $debugbar;
223
    }
224
225
    /**
226
     * Access a protected property when the api does not allow access
227
     *
228
     * @param object $object
229
     * @param string $property
230
     * @return mixed
231
     */
232
    protected static function getProtectedValue($object, $property)
233
    {
234
        $refObject = new ReflectionObject($object);
235
        $refProperty = $refObject->getProperty($property);
236
        $refProperty->setAccessible(true);
237
        return $refProperty->getValue($object);
238
    }
239
240
    /**
241
     * Clear the current instance of DebugBar
242
     *
243
     * @return void
244
     */
245
    public static function clearDebugBar()
246
    {
247
        self::$debugbar = null;
248
    }
249
250
    /**
251
     * @return boolean
252
     */
253
    public static function getShowQueries()
254
    {
255
        return self::$showQueries;
256
    }
257
258
    /**
259
     * Override default showQueries mode
260
     *
261
     * @param boolean $showQueries
262
     * @return void
263
     */
264
    public static function setShowQueries($showQueries)
265
    {
266
        self::$showQueries = $showQueries;
267
    }
268
269
    /**
270
     * Helper to access this module resources
271
     *
272
     * @param string $path
273
     * @return ModuleResource
274
     */
275
    public static function moduleResource($path)
276
    {
277
        return ModuleLoader::getModule('lekoala/silverstripe-debugbar')->getResource($path);
278
    }
279
280
    /**
281
     * Include DebugBar assets using Requirements API
282
     *
283
     * @return void
284
     */
285
    public static function includeRequirements()
286
    {
287
        $debugbar = self::getDebugBar();
288
289
        if (!$debugbar) {
290
            return;
291
        }
292
293
        // Already called
294
        if (self::$renderer) {
295
            return;
296
        }
297
298
        $renderer = $debugbar->getJavascriptRenderer();
299
300
        // We don't need the true path since we are going to use Requirements API that appends the BASE_PATH
301
        $assetsResource = self::moduleResource('assets');
302
        $renderer->setBasePath($assetsResource->getRelativePath());
303
        $renderer->setBaseUrl(Director::makeRelative($assetsResource->getURL()));
304
305
        $includeJquery = self::config()->get('include_jquery');
306
        // In CMS, jQuery is already included
307
        if (self::isAdminController()) {
308
            $includeJquery = false;
309
        }
310
        // If jQuery is already included, set to false
311
        $js = Requirements::backend()->getJavascript();
312
        foreach ($js as $url => $args) {
313
            $name = basename($url);
314
            if ($name == 'jquery.js' || $name == 'jquery.min.js') {
315
                $includeJquery = false;
316
                break;
317
            }
318
        }
319
320
        if ($includeJquery) {
321
            $renderer->setEnableJqueryNoConflict(true);
322
        } else {
323
            $renderer->disableVendor('jquery');
324
            $renderer->setEnableJqueryNoConflict(false);
325
        }
326
327
        if (DebugBar::config()->get('enable_storage')) {
328
            $renderer->setOpenHandlerUrl('__debugbar');
329
        }
330
331
        foreach ($renderer->getAssets('css') as $cssFile) {
332
            Requirements::css(Director::makeRelative(ltrim($cssFile, '/')));
333
        }
334
335
        foreach ($renderer->getAssets('js') as $jsFile) {
336
            Requirements::javascript(Director::makeRelative(ltrim($jsFile, '/')));
337
        }
338
339
        self::$renderer = $renderer;
340
    }
341
342
    /**
343
     * Returns the script to display the DebugBar
344
     *
345
     * @return string
346
     */
347
    public static function renderDebugBar()
348
    {
349
        if (!self::$renderer) {
350
            return;
351
        }
352
353
        // Requirements may have been cleared (CMS iframes...) or not set (Security...)
354
        $js = Requirements::backend()->getJavascript();
355
        $debugBarResource = self::moduleResource('assets/debugbar.js');
356
        $path = $debugBarResource->getRelativePath();
357
358
        // Url in getJavascript has a / slash, so fix if necessary
359
        $path = str_replace("assets\\debugbar.js", "assets/debugbar.js", $path);
360
        if (!array_key_exists($path, $js)) {
361
            return;
362
        }
363
        $initialize = true;
364
        if (Director::is_ajax()) {
365
            $initialize = false;
366
        }
367
368
        $script = self::$renderer->render($initialize);
369
        return $script;
370
    }
371
372
    /**
373
     * Get all criteria why the DebugBar could be disabled
374
     *
375
     * @return array
376
     */
377
    public static function disabledCriteria()
378
    {
379
        $reasons = array();
380
        if (!Director::isDev()) {
381
            $reasons[] = 'Not in dev mode';
382
        }
383
        if (self::isDisabled()) {
384
            $reasons[] = 'Disabled by a constant or configuration';
385
        }
386
        if (self::vendorNotInstalled()) {
387
            $reasons[] = 'DebugBar is not installed in vendors';
388
        }
389
        if (self::notLocalIp()) {
390
            $reasons[] = 'Not a local ip';
391
        }
392
        if (Director::is_cli()) {
393
            $reasons[] = 'In CLI mode';
394
        }
395
        if (self::isDevUrl()) {
396
            $reasons[] = 'Dev tools';
397
        }
398
        if (self::isAdminUrl() && !self::config()->get('enabled_in_admin')) {
399
            $reasons[] = 'In admin';
400
        }
401
        if (isset($_GET['CMSPreview'])) {
402
            $reasons[] = 'CMS Preview';
403
        }
404
        return $reasons;
405
    }
406
407
    /**
408
     * Determine why DebugBar is disabled
409
     *
410
     * Deprecated in favor of disabledCriteria
411
     *
412
     * @return string
413
     */
414
    public static function whyDisabled()
415
    {
416
        $reasons = self::disabledCriteria();
417
        if (!empty($reasons)) {
418
            return $reasons[0];
419
        }
420
        return "I don't know why";
421
    }
422
423
    public static function vendorNotInstalled()
424
    {
425
        return !class_exists('DebugBar\\StandardDebugBar');
426
    }
427
428
    public static function notLocalIp()
429
    {
430
        if (!self::config()->get('check_local_ip')) {
431
            return false;
432
        }
433
        if (isset($_SERVER['REMOTE_ADDR'])) {
434
            return !in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1', '1'));
435
        }
436
        return false;
437
    }
438
439
    public static function isDisabled()
440
    {
441
        if (Environment::getEnv('DEBUGBAR_DISABLE') || static::config()->get('disabled')) {
442
            return true;
443
        }
444
        return false;
445
    }
446
447
    public static function isDevUrl()
448
    {
449
        return strpos(self::getRequestUrl(), '/dev/') === 0;
450
    }
451
452
    public static function isAdminUrl()
453
    {
454
        $baseUrl = rtrim(BASE_URL, '/');
455
        if (class_exists(AdminRootController::class)) {
456
            $adminUrl = AdminRootController::config()->get('url_base');
457
        } else {
458
            $adminUrl = 'admin';
459
        }
460
461
        return strpos(self::getRequestUrl(), $baseUrl . '/' . $adminUrl . '/') === 0;
462
    }
463
464
    public static function isAdminController()
465
    {
466
        if (Controller::curr()) {
467
            return Controller::curr() instanceof LeftAndMain;
468
        }
469
        return self::isAdminUrl();
470
    }
471
472
    /**
473
     * Avoid triggering data collection for open handler
474
     *
475
     * @return boolean
476
     */
477
    public static function isDebugBarRequest()
478
    {
479
        if ($url = self::getRequestUrl()) {
480
            return strpos($url, '/__debugbar') === 0;
481
        }
482
        return true;
483
    }
484
485
    /**
486
     * Get request url
487
     *
488
     * @return string
489
     */
490
    public static function getRequestUrl()
491
    {
492
        if (isset($_REQUEST['url'])) {
493
            return $_REQUEST['url'];
494
        }
495
        if (isset($_SERVER['REQUEST_URI'])) {
496
            return $_SERVER['REQUEST_URI'];
497
        }
498
        return '';
499
    }
500
501
    /**
502
     * Helper to make code cleaner
503
     *
504
     * @param callable $callback
505
     */
506
    public static function withDebugBar($callback)
507
    {
508
        if (self::getDebugBar() && !self::isDebugBarRequest()) {
509
            $callback(self::getDebugBar());
510
        }
511
    }
512
513
    /**
514
     * Set the current request. Is provided by the DebugBarMiddleware.
515
     *
516
     * @param HTTPRequest $request
517
     */
518
    public static function setRequest(HTTPRequest $request)
519
    {
520
        self::$request = $request;
521
    }
522
523
    /**
524
     * Get the current request
525
     *
526
     * @return HTTPRequest
527
     */
528
    public static function getRequest()
529
    {
530
        if (self::$request) {
531
            return self::$request;
532
        }
533
        // Fall back to trying from the global state
534
        if (Controller::has_curr()) {
535
            return Controller::curr()->getRequest();
536
        }
537
    }
538
}
539