Issues (3627)

CoreBundle/Templating/Helper/ExceptionHelper.php (1 issue)

1
<?php
2
3
/*
4
 * @copyright   2014 Mautic Contributors. All rights reserved
5
 * @author      Mautic
6
 *
7
 * @link        http://mautic.org
8
 *
9
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
10
 */
11
12
namespace Mautic\CoreBundle\Templating\Helper;
13
14
use Symfony\Component\Templating\Helper\Helper;
15
16
/**
17
 * Class ExceptionHelper.
18
 *
19
 * Code in this class is derived from \Symfony\Bridge\Twig\Extension\CodeExtension
20
 */
21
class ExceptionHelper extends Helper
22
{
23
    private $fileLinkFormat;
24
    private $rootDir;
25
26
    /**
27
     * Constructor.
28
     *
29
     * @param string $rootDir The project root directory
30
     */
31
    public function __construct($rootDir)
32
    {
33
        $this->fileLinkFormat = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
34
        $this->rootDir        = str_replace('\\', '/', dirname($rootDir)).'/';
35
    }
36
37
    /**
38
     * {@inheritdoc}
39
     */
40
    public function getName()
41
    {
42
        return 'exception';
43
    }
44
45
    /**
46
     * Returns an abbreviated class name with the full name as a tooltip.
47
     *
48
     * @param string $class
49
     *
50
     * @return string
51
     */
52
    public function abbrClass($class)
53
    {
54
        $parts = explode('\\', $class);
55
        $short = array_pop($parts);
56
57
        return sprintf('<abbr title="%s">%s</abbr>', $class, $short);
58
    }
59
60
    /**
61
     * Returns an excerpt of a code file around the given line number.
62
     *
63
     * @param string $file A file path
64
     * @param int    $line The selected line number
65
     *
66
     * @return string An HTML string
67
     */
68
    public function fileExcerpt($file, $line)
69
    {
70
        if (is_readable($file)) {
71
            // highlight_file could throw warnings
72
            // see https://bugs.php.net/bug.php?id=25725
73
            $code = @highlight_file($file, true);
74
            // remove main code/span tags
75
            $code    = preg_replace('#^<code.*?>\s*<span.*?>(.*)</span>\s*</code>#s', '\\1', $code);
76
            $content = preg_split('#<br />#', $code);
77
78
            $lines = [];
79
            for ($i = max($line - 3, 1), $max = min($line + 3, count($content)); $i <= $max; ++$i) {
80
                $lines[] = '<li'.($i == $line ? ' class="selected"' : '').'><code>'.$this->fixCodeMarkup($content[$i - 1]).'</code></li>';
81
            }
82
83
            return '<ol start="'.max($line - 3, 1).'">'.implode("\n", $lines).'</ol>';
84
        }
85
    }
86
87
    /**
88
     * Formats an array as a string.
89
     *
90
     * @param array $args The argument array
91
     *
92
     * @return string
93
     */
94
    public function formatArgs($args)
95
    {
96
        $result = [];
97
        foreach ($args as $key => $item) {
98
            if ('object' === $item[0]) {
99
                $parts          = explode('\\', $item[1]);
100
                $short          = array_pop($parts);
101
                $formattedValue = sprintf('<em>object</em>(<abbr title="%s">%s</abbr>)', $item[1], $short);
102
            } elseif ('array' === $item[0]) {
103
                $formattedValue = sprintf('<em>array</em>(%s)', is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
104
            } elseif ('string' === $item[0]) {
105
                $formattedValue = sprintf("'%s'", htmlspecialchars($item[1], ENT_QUOTES, $this->charset));
106
            } elseif ('null' === $item[0]) {
107
                $formattedValue = '<em>null</em>';
108
            } elseif ('boolean' === $item[0]) {
109
                $formattedValue = '<em>'.strtolower(var_export($item[1], true)).'</em>';
110
            } elseif ('resource' === $item[0]) {
111
                $formattedValue = '<em>resource</em>';
112
            } else {
113
                $formattedValue = str_replace("\n", '', var_export(htmlspecialchars((string) $item[1], ENT_QUOTES, $this->charset), true));
114
            }
115
116
            $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue);
117
        }
118
119
        return implode(', ', $result);
120
    }
121
122
    /**
123
     * Formats an array as a string.
124
     *
125
     * @param array $args The argument array
126
     *
127
     * @return string
128
     */
129
    public function formatArgsAsText($args)
130
    {
131
        return strip_tags($this->formatArgs($args));
132
    }
133
134
    /**
135
     * Formats a file path.
136
     *
137
     * @param string $file An absolute file path
138
     * @param int    $line The line number
139
     * @param string $text Use this text for the link rather than the file path
140
     *
141
     * @return string
142
     */
143
    public function formatFile($file, $line, $text = null)
144
    {
145
        if (null === $text) {
146
            $text = str_replace('\\', '/', $file);
147
            if (0 === strpos($text, $this->rootDir)) {
148
                $text = substr($text, strlen($this->rootDir));
149
                $text = explode('/', $text, 2);
150
                $text = sprintf('<abbr title="%s%2$s">%s</abbr>%s', $this->rootDir, $text[0], isset($text[1]) ? '/'.$text[1] : '');
151
            }
152
        }
153
154
        $text = "$text at line $line";
155
156
        if (false !== $link = $this->getFileLink($file, $line)) {
157
            $flags = ENT_QUOTES | ENT_SUBSTITUTE;
158
159
            return sprintf('<a href="%s" title="Click to open this file" class="file_link">%s</a>', htmlspecialchars($link, $flags, $this->charset), $text);
160
        }
161
162
        return $text;
163
    }
164
165
    /**
166
     * @param $text
167
     *
168
     * @return string
169
     */
170
    public function formatFileFromText($text)
171
    {
172
        $that = $this;
173
174
        return preg_replace_callback('/in ("|&quot;)?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) use ($that) {
175
            return 'in '.$that->formatFile($match[2], $match[3]);
176
        }, $text);
177
    }
178
179
    /**
180
     * Returns the link for a given file/line pair.
181
     *
182
     * @param string $file An absolute file path
183
     * @param int    $line The line number
184
     *
185
     * @return string A link of false
186
     */
187
    public function getFileLink($file, $line)
188
    {
189
        if ($this->fileLinkFormat && is_file($file)) {
190
            return strtr($this->fileLinkFormat, ['%f' => $file, '%l' => $line]);
191
        }
192
193
        return false;
194
    }
195
196
    /**
197
     * Corrects the markup for a line.
198
     *
199
     * @param string $line
200
     *
201
     * @return string
202
     */
203
    private function fixCodeMarkup($line)
204
    {
205
        // </span> ending tag from previous line
206
        $opening = strpos($line, '<span');
207
        $closing = strpos($line, '</span>');
208
        if (false !== $closing && (false === $opening || $closing < $opening)) {
209
            $line = substr_replace($line, '', $closing, 7);
210
        }
211
212
        // missing </span> tag at the end of line
213
        $opening = strpos($line, '<span');
0 ignored issues
show
It seems like $line can also be of type array; however, parameter $haystack of strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

213
        $opening = strpos(/** @scrutinizer ignore-type */ $line, '<span');
Loading history...
214
        $closing = strpos($line, '</span>');
215
        if (false !== $opening && (false === $closing || $closing > $opening)) {
216
            $line .= '</span>';
217
        }
218
219
        return $line;
220
    }
221
}
222