Completed
Push — master ( 055c36...a12289 )
by Thomas
17:02
created

DebugBar::moduleResource()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
dl 0
loc 4
rs 10
c 1
b 1
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
namespace LeKoala\DebugBar;
3
4
use Exception;
5
use Monolog\Logger;
6
use ReflectionObject;
7
use SilverStripe\ORM\DB;
8
use Psr\Log\LoggerInterface;
9
use SilverStripe\Core\Kernel;
10
use DebugBar\Storage\FileStorage;
11
use SilverStripe\Core\Environment;
12
use SilverStripe\Control\Director;
13
use SilverStripe\View\Requirements;
14
use SilverStripe\Admin\LeftAndMain;
15
use SilverStripe\Control\HTTPRequest;
16
use DebugBar\Bridge\MonologCollector;
17
use SilverStripe\Control\Email\Mailer;
18
use DebugBar\DebugBar as BaseDebugBar;
19
use SilverStripe\Core\Injector\Injector;
20
use LeKoala\DebugBar\Proxy\DatabaseProxy;
21
use SilverStripe\ORM\Connect\PDOConnector;
22
use SilverStripe\Core\Injector\Injectable;
23
use SilverStripe\Core\Config\Configurable;
24
use SilverStripe\Admin\AdminRootController;
25
use LeKoala\DebugBar\Messages\LogFormatter;
26
use SilverStripe\Control\Email\SwiftMailer;
27
use DebugBar\DataCollector\MemoryCollector;
28
use SilverStripe\Core\Manifest\ModuleLoader;
29
use DebugBar\DataCollector\PDO\PDOCollector;
30
use DebugBar\DataCollector\PDO\TraceablePDO;
31
use DebugBar\DataCollector\PhpInfoCollector;
32
use DebugBar\DataCollector\MessagesCollector;
33
use SilverStripe\Core\Manifest\ModuleResource;
34
use LeKoala\DebugBar\Proxy\ConfigManifestProxy;
35
use LeKoala\DebugBar\Collector\ConfigCollector;
36
use LeKoala\DebugBar\Collector\DatabaseCollector;
37
use LeKoala\DebugBar\Collector\TimeDataCollector;
38
use DebugBar\Bridge\SwiftMailer\SwiftLogCollector;
39
use DebugBar\Bridge\SwiftMailer\SwiftMailCollector;
40
use LeKoala\DebugBar\Collector\PartialCacheCollector;
41
use LeKoala\DebugBar\Collector\SilverStripeCollector;
42
use SilverStripe\Config\Collections\CachedConfigCollection;
43
44
/**
45
 * A simple helper
46
 */
47
class DebugBar
48
{
49
    use Configurable;
50
    use Injectable;
51
52
    /**
53
     * @var DebugBar\DebugBar
54
     */
55
    protected static $debugbar;
56
57
    /**
58
     * @var bool
59
     */
60
    public static $bufferingEnabled = false;
61
62
    /**
63
     * @var DebugBar\JavascriptRenderer
64
     */
65
    protected static $renderer;
66
67
    /**
68
     * @var bool
69
     */
70
    protected static $showQueries = false;
71
72
    /**
73
     * @var HTTPRequest
74
     */
75
    protected static $request;
76
77
    /**
78
     * Get the Debug Bar instance
79
     * @return \DebugBar\StandardDebugBar
80
     * @throws Exception
81
     * @global array $databaseConfig
82
     */
83
    public static function getDebugBar()
84
    {
85
        if (self::$debugbar !== null) {
86
            return self::$debugbar;
87
        }
88
89
        if (!Director::isDev() || self::isDisabled() || self::vendorNotInstalled() ||
90
            self::notLocalIp() || Director::is_cli() || self::isDevUrl() || (self::isAdminUrl() && !self::config()->get('enabled_in_admin'))) {
91
            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 object<LeKoala\DebugBar\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...
92
            return;
93
        }
94
95
        self::initDebugBar();
96
97
        if (!self::$debugbar) {
98
            throw new Exception("Failed to initialize the DebugBar");
99
        }
100
101
        return self::$debugbar;
102
    }
103
104
    /**
105
     * Init the debugbar instance
106
     *
107
     * @global array $databaseConfig
108
     * @return DebugBar\StandardDebugBar
109
     */
110
    public static function initDebugBar()
0 ignored issues
show
Coding Style introduced by
initDebugBar 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...
111
    {
112
        // Prevent multiple inits
113
        if (self::$debugbar) {
114
            return self::$debugbar;
115
        }
116
117
        self::$debugbar = $debugbar = new BaseDebugBar;
0 ignored issues
show
Documentation Bug introduced by
It seems like $debugbar = new \DebugBar\DebugBar() of type object<DebugBar\DebugBar> is incompatible with the declared type object<LeKoala\DebugBar\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...
118
119
        if (isset($_REQUEST['showqueries'])) {
120
            self::setShowQueries(true);
121
            echo "The queries above have been run before we started DebugBar";
122
            echo '<hr>';
123
            unset($_REQUEST['showqueries']);
124
        }
125
126
        $debugbar->addCollector(new PhpInfoCollector);
127
        $debugbar->addCollector(new TimeDataCollector);
128
        $debugbar->addCollector(new MemoryCollector);
129
130
        // Add config proxy replacing the core config manifest
131
        /** @var SilverStripe\Core\Config\ConfigLoader $configLoader */
132
        $configLoader = Injector::inst()->get(Kernel::class)->getConfigLoader();
133
        $configManifest = false;
134
        while ($configLoader->hasManifest()) {
135
            $eachManifest = $configLoader->popManifest();
136
            if ($eachManifest instanceof CachedConfigCollection) {
137
                $configManifest = $eachManifest;
138
                break;
139
            }
140
        }
141
        if ($configManifest) {
142
            $configProxy = new ConfigManifestProxy($configManifest);
143
            $configLoader->pushManifest($configProxy);
144
        }
145
146
        $connector = DB::get_connector();
147
        if (!self::config()->get('force_proxy') && $connector instanceof PDOConnector) {
148
            // Use a little bit of magic to replace the pdo instance
149
            $refObject = new ReflectionObject($connector);
150
            $refProperty = $refObject->getProperty('pdoConnection');
151
            $refProperty->setAccessible(true);
152
            $traceablePdo = new TraceablePDO($refProperty->getValue($connector));
153
            $refProperty->setValue($connector, $traceablePdo);
154
155
            $debugbar->addCollector(new PDOCollector($traceablePdo));
156
        } else {
157
            DB::set_conn($db = new DatabaseProxy(DB::get_conn()));
158
            $db->setShowQueries(self::getShowQueries());
159
            $debugbar->addCollector(new DatabaseCollector);
160
        }
161
162
        // Add message collector last so other collectors can send messages to the console using it
163
        $debugbar->addCollector(new MessagesCollector());
164
165
        // Aggregate monolog into messages
166
        $logger = Injector::inst()->get(LoggerInterface::class);
167
        if ($logger instanceof Logger) {
168
            $logCollector = new MonologCollector($logger);
169
            $logCollector->setFormatter(new LogFormatter);
170
            $debugbar['messages']->aggregate($logCollector);
171
        }
172
173
        // Add some SilverStripe specific infos
174
        $debugbar->addCollector(new SilverStripeCollector);
175
176
        if (self::config()->get('enable_storage')) {
177
            $debugbar->setStorage(new FileStorage(TEMP_FOLDER . '/debugbar'));
178
        }
179
180
        if ($configManifest) {
181
            // Add the config collector
182
            $debugbar->addCollector(new ConfigCollector);
183
        }
184
        $debugbar->addCollector(new PartialCacheCollector);
185
186
        // Email logging
187
        $mailer = Injector::inst()->get(Mailer::class);
188
        if ($mailer instanceof SwiftMailer) {
189
            $swiftInst = $mailer->getSwiftMailer();
190
            $debugbar['messages']->aggregate(new SwiftLogCollector($swiftInst));
191
            $debugbar->addCollector(new SwiftMailCollector($swiftInst));
192
        }
193
194
        // Since we buffer everything, why not enable all dev options ?
195
        if (self::config()->get('auto_debug')) {
196
            $_REQUEST['debug'] = true;
197
            $_REQUEST['debug_request'] = true;
198
        }
199
200
        if (isset($_REQUEST['debug']) || isset($_REQUEST['debug_request'])) {
201
            self::$bufferingEnabled = true;
202
            ob_start(); // We buffer everything until we have called an action
203
        }
204
205
        return $debugbar;
206
    }
207
208
    public static function clearDebugBar()
209
    {
210
        self::$debugbar = null;
211
    }
212
213
    public static function getShowQueries()
214
    {
215
        return self::$showQueries;
216
    }
217
218
    public static function setShowQueries($showQueries)
219
    {
220
        self::$showQueries = $showQueries;
221
    }
222
223
    /**
224
     * Helper to access this module resources
225
     *
226
     * @param string $name
0 ignored issues
show
Bug introduced by
There is no parameter named $name. Was it maybe removed?

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

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

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

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

Loading history...
227
     * @return ModuleResource
228
     */
229
    public static function moduleResource($path)
230
    {
231
        return ModuleLoader::getModule('lekoala/silverstripe-debugbar')->getResource($path);
0 ignored issues
show
Bug introduced by
The method getResource() does not exist on SilverStripe\Core\Manifest\Module. Did you maybe mean getResourcePath()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
232
    }
233
234
    public static function includeRequirements()
235
    {
236
        $debugbar = self::getDebugBar();
237
238
        if (!$debugbar) {
239
            return;
240
        }
241
242
        // Already called
243
        if (self::$renderer) {
244
            return;
245
        }
246
247
        $renderer = $debugbar->getJavascriptRenderer();
248
249
        // We don't need the true path since we are going to use Requirements API that appends the BASE_PATH
250
        $assetsResource = self::moduleResource('assets');
251
        $renderer->setBasePath($assetsResource->getRelativePath());
252
        $renderer->setBaseUrl(Director::makeRelative($assetsResource->getURL()));
253
254
        $includeJquery = self::config()->get('include_jquery');
255
        // In CMS, jQuery is already included
256
        if (self::isAdminController()) {
257
            $includeJquery = false;
258
        }
259
        // If jQuery is already included, set to false
260
        $js = Requirements::backend()->getJavascript();
261
        foreach ($js as $url => $args) {
262
            $name = basename($url);
263
            if ($name == 'jquery.js' || $name == 'jquery.min.js') {
264
                $includeJquery = false;
265
                break;
266
            }
267
        }
268
269
        if ($includeJquery) {
270
            $renderer->setEnableJqueryNoConflict(true);
271
        } else {
272
            $renderer->disableVendor('jquery');
273
            $renderer->setEnableJqueryNoConflict(false);
274
        }
275
276
        if (DebugBar::config()->get('enable_storage')) {
277
            $renderer->setOpenHandlerUrl('__debugbar');
278
        }
279
280
        foreach ($renderer->getAssets('css') as $cssFile) {
281
            Requirements::css(Director::makeRelative(ltrim($cssFile, '/')));
282
        }
283
284
        foreach ($renderer->getAssets('js') as $jsFile) {
285
            Requirements::javascript(Director::makeRelative(ltrim($jsFile, '/')));
286
        }
287
288
        self::$renderer = $renderer;
289
    }
290
291
    public static function renderDebugBar()
292
    {
293
        if (!self::$renderer) {
294
            return;
295
        }
296
297
        // Requirements may have been cleared (CMS iframes...) or not set (Security...)
298
        $js = Requirements::backend()->getJavascript();
299
        $debugBarResource = self::moduleResource('assets/debugbar.js');
300
        $path = $debugBarResource->getRelativePath();
301
302
        // Url in getJavascript has a / slash, so fix if necessary
303
        $path = str_replace("assets\\debugbar.js", "assets/debugbar.js", $path);
304
        if (!array_key_exists($path, $js)) {
305
            return;
306
        }
307
        $initialize = true;
308
        if (Director::is_ajax()) {
309
            $initialize = false;
310
        }
311
312
        $script = self::$renderer->render($initialize);
313
        return $script;
314
    }
315
316
    /**
317
     * Determine why DebugBar is disabled
318
     *
319
     * @return string
320
     */
321
    public static function whyDisabled()
322
    {
323
        if (!Director::isDev()) {
324
            return 'Not in dev mode';
325
        }
326
        if (self::isDisabled()) {
327
            return 'Disabled by a constant or configuration';
328
        }
329
        if (self::vendorNotInstalled()) {
330
            return 'DebugBar is not installed in vendors';
331
        }
332
        if (self::notLocalIp()) {
333
            return 'Not a local ip';
334
        }
335
        if (Director::is_cli()) {
336
            return 'In CLI mode';
337
        }
338
        if (self::isDevUrl()) {
339
            return 'Dev tools';
340
        }
341
        if (self::isAdminUrl() && !self::config()->get('enabled_in_admin')) {
342
            return 'In admin';
343
        }
344
        return "I don't know why";
345
    }
346
347
    public static function vendorNotInstalled()
348
    {
349
        return !class_exists('DebugBar\\StandardDebugBar');
350
    }
351
352
    public static function notLocalIp()
0 ignored issues
show
Coding Style introduced by
notLocalIp uses the super-global variable $_SERVER 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...
353
    {
354
        if (!self::config()->get('check_local_ip')) {
355
            return false;
356
        }
357
        if (isset($_SERVER['REMOTE_ADDR'])) {
358
            return !in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1', '1'));
359
        }
360
        return false;
361
    }
362
363
    public static function isDisabled()
364
    {
365
        if (Environment::getEnv('DEBUGBAR_DISABLE') || static::config()->get('disabled')) {
0 ignored issues
show
Bug introduced by
The method getEnv() does not seem to exist on object<SilverStripe\Core\Environment>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
366
            return true;
367
        }
368
        return false;
369
    }
370
371
    public static function isDevUrl()
372
    {
373
        return strpos(self::getRequestUrl(), '/dev/') === 0;
374
    }
375
376
    public static function isAdminUrl()
377
    {
378
        $baseUrl = rtrim(BASE_URL, '/');
379
        $adminUrl = AdminRootController::config()->get('url_base');
380
381
        return strpos(self::getRequestUrl(), $baseUrl . '/' . $adminUrl . '/') === 0;
382
    }
383
384
    public static function isAdminController()
385
    {
386
        if (Controller::curr()) {
387
            return Controller::curr() instanceof LeftAndMain;
388
        }
389
        return self::isAdminUrl();
390
    }
391
392
    /**
393
     * Avoid triggering data collection for open handler
394
     *
395
     * @return boolean
396
     */
397
    public static function isDebugBarRequest()
398
    {
399
        if ($url = self::getRequestUrl()) {
400
            return strpos($url, '/__debugbar') === 0;
401
        }
402
        return true;
403
    }
404
405
    /**
406
     * Get request url
407
     *
408
     * @return string
409
     */
410
    public static function getRequestUrl()
0 ignored issues
show
Coding Style introduced by
getRequestUrl 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...
Coding Style introduced by
getRequestUrl uses the super-global variable $_SERVER 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...
411
    {
412
        if (isset($_REQUEST['url'])) {
413
            return $_REQUEST['url'];
414
        }
415
        if (isset($_SERVER['REQUEST_URI'])) {
416
            return $_SERVER['REQUEST_URI'];
417
        }
418
        return '';
419
    }
420
421
    /**
422
     * Helper to make code cleaner
423
     *
424
     * @param callable $callback
425
     */
426
    public static function withDebugBar($callback)
427
    {
428
        if (self::getDebugBar() && !self::isDebugBarRequest()) {
429
            $callback(self::getDebugBar());
430
        }
431
    }
432
433
    /**
434
     * Set the current request. Is provided by the DebugBarMiddleware.
435
     *
436
     * @param HTTPRequest $request
437
     */
438
    public static function setRequest(HTTPRequest $request)
439
    {
440
        self::$request = $request;
441
    }
442
443
    /**
444
     * Get the current request
445
     *
446
     * @return HTTPRequest
447
     */
448
    public static function getRequest()
449
    {
450
        if (self::$request) {
451
            return self::$request;
452
        }
453
        // Fall back to trying from the global state
454
        if (Controller::has_curr()) {
455
            return Controller::curr()->getRequest();
456
        }
457
    }
458
}
459