Completed
Push — master ( 1be2e7...d38097 )
by Sam
23s
created

Debug::show()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 16
nc 7
nop 2
dl 0
loc 24
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Dev;
4
5
use SilverStripe\Control\Director;
6
use SilverStripe\Core\Convert;
7
use SilverStripe\Core\Injector\Injector;
8
use SilverStripe\ORM\DB;
9
use SilverStripe\Security\Permission;
10
use SilverStripe\Security\Security;
11
12
/**
13
 * Supports debugging and core error handling.
14
 *
15
 * Attaches custom methods to the default error handling hooks
16
 * in PHP. Currently, two levels of error are supported:
17
 *
18
 * - Notice
19
 * - Warning
20
 * - Error
21
 *
22
 * Uncaught exceptions are currently passed to the debug
23
 * reporter as standard PHP errors.
24
 *
25
 * Errors handled by this class are passed along to {@link SS_Log}.
26
 * For configuration information, see the {@link SS_Log}
27
 * class documentation.
28
 *
29
 * @todo add support for user defined config: Debug::die_on_notice(true | false)
30
 * @todo better way of figuring out the error context to display in highlighted source
31
 */
32
class Debug
33
{
34
35
    /**
36
     * Show the contents of val in a debug-friendly way.
37
     * Debug::show() is intended to be equivalent to dprintr()
38
     *
39
     * @param mixed $val
40
     * @param bool $showHeader
41
     */
42
    public static function show($val, $showHeader = true)
43
    {
44
        if (!Director::isLive()) {
45
            if ($showHeader) {
46
                $caller = Debug::caller();
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
47
                if (Director::is_ajax() || Director::is_cli()) {
48
                    echo "Debug ($caller[class]$caller[type]$caller[function]() in " . basename($caller['file'])
49
                        . ":$caller[line])\n";
50
                } else {
51
                    echo "<div style=\"background-color: white; text-align: left;\">\n<hr>\n"
52
                        . "<h3>Debug <span style=\"font-size: 65%\">($caller[class]$caller[type]$caller[function]()"
53
                        . " \nin " . basename($caller['file']) . ":$caller[line])</span>\n</h3>\n";
54
                }
55
            }
56
57
            echo Debug::text($val);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
58
59
            if (!Director::is_ajax() && !Director::is_cli()) {
60
                echo "</div>";
61
            } else {
62
                echo "\n\n";
63
            }
64
        }
65
    }
66
67
    /**
68
     * Returns the caller for a specific method
69
     *
70
     * @return array
71
     */
72
    public static function caller()
73
    {
74
        $bt = debug_backtrace();
75
        $caller = isset($bt[2]) ? $bt[2] : array();
76
        $caller['line'] = $bt[1]['line'];
77
        $caller['file'] = $bt[1]['file'];
78
        if (!isset($caller['class'])) {
79
            $caller['class'] = '';
80
        }
81
        if (!isset($caller['type'])) {
82
            $caller['type'] = '';
83
        }
84
        if (!isset($caller['function'])) {
85
            $caller['function'] = '';
86
        }
87
        return $caller;
88
    }
89
90
    /**
91
     * Close out the show dumper
92
     *
93
     * @param mixed $val
94
     */
95
    public static function endshow($val)
96
    {
97
        if (!Director::isLive()) {
98
            $caller = Debug::caller();
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
99
            echo "<hr>\n<h3>Debug \n<span style=\"font-size: 65%\">($caller[class]$caller[type]$caller[function]()"
100
                . " \nin " . basename($caller['file']) . ":$caller[line])</span>\n</h3>\n";
101
            echo Debug::text($val);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
102
            die();
0 ignored issues
show
Coding Style Compatibility introduced by
The method endshow() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
103
        }
104
    }
105
106
    /**
107
     * Quick dump of a variable.
108
     *
109
     * @param mixed $val
110
     */
111
    public static function dump($val)
112
    {
113
        echo self::create_debug_view()->renderVariable($val, self::caller());
114
    }
115
116
    /**
117
     * @param mixed $val
118
     * @return string
119
     */
120
    public static function text($val)
121
    {
122
        if (is_object($val)) {
123
            if (method_exists($val, 'hasMethod')) {
124
                $hasDebugMethod = $val->hasMethod('debug');
125
            } else {
126
                $hasDebugMethod = method_exists($val, 'debug');
127
            }
128
129
            if ($hasDebugMethod) {
130
                return $val->debug();
131
            }
132
        }
133
134
        if (is_array($val)) {
135
            $result = "<ul>\n";
136
            foreach ($val as $k => $v) {
137
                $result .= "<li>$k = " . Debug::text($v) . "</li>\n";
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
138
            }
139
            $val = $result . "</ul>\n";
140
        } elseif (is_object($val)) {
141
            $val = var_export($val, true);
142
        } elseif (is_bool($val)) {
143
            $val = $val ? 'true' : 'false';
144
            $val = '(bool) ' . $val;
145
        } else {
146
            if (!Director::is_cli() && !Director::is_ajax()) {
147
                $val = "<pre style=\"font-family: Courier new\">" . htmlentities($val, ENT_COMPAT, 'UTF-8')
148
                    . "</pre>\n";
149
            }
150
        }
151
152
        return $val;
153
    }
154
155
    /**
156
     * Show a debugging message
157
     *
158
     * @param string $message
159
     * @param bool $showHeader
160
     */
161
    public static function message($message, $showHeader = true)
162
    {
163
        if (!Director::isLive()) {
164
            $caller = Debug::caller();
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
165
            $file = basename($caller['file']);
166
            if (Director::is_cli()) {
167
                if ($showHeader) {
168
                    echo "Debug (line $caller[line] of $file):\n ";
169
                }
170
                echo $message . "\n";
171
            } else {
172
                echo "<p class=\"message warning\">\n";
173
                if ($showHeader) {
174
                    echo "<b>Debug (line $caller[line] of $file):</b>\n ";
175
                }
176
                echo Convert::raw2xml($message) . "</p>\n";
177
            }
178
        }
179
    }
180
181
    /**
182
     * Create an instance of an appropriate DebugView object.
183
     *
184
     * @return DebugView
185
     */
186
    public static function create_debug_view()
187
    {
188
        $service = Director::is_cli() || Director::is_ajax()
189
            ? 'SilverStripe\\Dev\\CliDebugView'
190
            : 'SilverStripe\\Dev\\DebugView';
191
        return Injector::inst()->get($service);
192
    }
193
194
    /**
195
     * Check if the user has permissions to run URL debug tools,
196
     * else redirect them to log in.
197
     */
198
    public static function require_developer_login()
0 ignored issues
show
Coding Style introduced by
require_developer_login uses the super-global variable $_SESSION 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
require_developer_login 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...
199
    {
200
        if (Director::isDev()) {
201
            return;
202
        }
203
        if (isset($_SESSION['loggedInAs'])) {
204
            // We have to do some raw SQL here, because this method is called in Object::defineMethods().
205
            // This means we have to be careful about what objects we create, as we don't want Object::defineMethods()
206
            // being called again.
207
            // This basically calls Permission::checkMember($_SESSION['loggedInAs'], 'ADMIN');
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
208
209
            // @TODO - Rewrite safely using DataList::filter
210
            $memberID = $_SESSION['loggedInAs'];
211
            $permission = DB::prepared_query(
212
                '
213
				SELECT "ID" FROM "Permission"
214
				INNER JOIN "Group_Members" ON "Permission"."GroupID" = "Group_Members"."GroupID"
215
				WHERE "Permission"."Code" = ?
216
				AND "Permission"."Type" = ?
217
				AND "Group_Members"."MemberID" = ?',
218
                array(
219
                    'ADMIN', // Code
220
                    Permission::GRANT_PERMISSION, // Type
221
                    $memberID // MemberID
222
                )
223
            )->value();
224
225
            if ($permission) {
226
                return;
227
            }
228
        }
229
230
        // This basically does the same as
231
        // Security::permissionFailure(null, "You need to login with developer access to make use of debugging tools.")
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% 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...
232
        // We have to do this because of how early this method is called in execution.
233
        $_SESSION['SilverStripe\\Security\\Security']['Message']['message']
234
            = "You need to login with developer access to make use of debugging tools.";
235
        $_SESSION['SilverStripe\\Security\\Security']['Message']['type'] =  'warning';
236
        $_SESSION['BackURL'] = $_SERVER['REQUEST_URI'];
237
        header($_SERVER['SERVER_PROTOCOL'] . " 302 Found");
238
        header("Location: " . Director::baseURL() . Security::login_url());
239
        die();
0 ignored issues
show
Coding Style Compatibility introduced by
The method require_developer_login() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
240
    }
241
}
242