Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.00% covered (success)
95.00%
76 / 80
84.21% covered (warning)
84.21%
16 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
parser
95.00% covered (success)
95.00%
76 / 80
84.21% covered (warning)
84.21%
16 / 19
41
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 parse
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 disable_bbcode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 disable_bbcodes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 disable_censor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 disable_magic_url
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 disable_smilies
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 enable_bbcode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 enable_bbcodes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 enable_censor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 enable_magic_url
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 enable_smilies
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 get_errors
92.86% covered (success)
92.86%
26 / 28
0.00% covered (danger)
0.00%
0 / 1
11.04
 get_parser
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 set_var
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 set_vars
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 filter_font_size
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 filter_img_url
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 is_a_bbcode
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3*
4* This file is part of the phpBB Forum Software package.
5*
6* @copyright (c) phpBB Limited <https://www.phpbb.com>
7* @license GNU General Public License, version 2 (GPL-2.0)
8*
9* For full copyright and license information, please see
10* the docs/CREDITS.txt file.
11*
12*/
13
14namespace phpbb\textformatter\s9e;
15
16use s9e\TextFormatter\Parser\AttributeFilters\UrlFilter;
17use s9e\TextFormatter\Parser\Logger;
18use s9e\TextFormatter\Parser\Tag;
19
20/**
21* s9e\TextFormatter\Parser adapter
22*/
23class parser implements \phpbb\textformatter\parser_interface
24{
25    /**
26    * @var \phpbb\event\dispatcher_interface
27    */
28    protected $dispatcher;
29
30    /**
31    * @var \s9e\TextFormatter\Parser
32    */
33    protected $parser;
34
35    /**
36    * Constructor
37    *
38    * @param \phpbb\cache\driver\driver_interface $cache
39    * @param string $key Cache key
40    * @param factory $factory
41    * @param \phpbb\event\dispatcher_interface $dispatcher
42    */
43    public function __construct(\phpbb\cache\driver\driver_interface $cache, $key, factory $factory, \phpbb\event\dispatcher_interface $dispatcher)
44    {
45        $parser = $cache->get($key);
46        if (!$parser)
47        {
48            $objects = $factory->regenerate();
49            $parser  = $objects['parser'];
50        }
51
52        $this->dispatcher = $dispatcher;
53        $this->parser = $parser;
54
55        $parser = $this;
56
57        /**
58        * Configure the parser service
59        *
60        * Can be used to:
61        *  - toggle features or BBCodes
62        *  - register variables or custom parsers in the s9e\TextFormatter parser
63        *  - configure the s9e\TextFormatter parser's runtime settings
64        *
65        * @event core.text_formatter_s9e_parser_setup
66        * @var \phpbb\textformatter\s9e\parser parser This parser service
67        * @since 3.2.0-a1
68        * @psalm-ignore-var
69        */
70        $vars = ['parser'];
71        extract($dispatcher->trigger_event('core.text_formatter_s9e_parser_setup', compact($vars)));
72    }
73
74    /**
75    * {@inheritdoc}
76    */
77    public function parse($text)
78    {
79        $parser = $this;
80
81        /**
82        * Modify a text before it is parsed
83        *
84        * @event core.text_formatter_s9e_parse_before
85        * @var \phpbb\textformatter\s9e\parser parser This parser service
86        * @var string text The original text
87        * @since 3.2.0-a1
88        */
89        $vars = array('parser', 'text');
90        extract($this->dispatcher->trigger_event('core.text_formatter_s9e_parse_before', compact($vars)));
91
92        $xml = $this->parser->parse($text);
93
94        /**
95        * Modify a parsed text in its XML form
96        *
97        * @event core.text_formatter_s9e_parse_after
98        * @var \phpbb\textformatter\s9e\parser parser This parser service
99        * @var string xml The parsed text, in XML
100        * @since 3.2.0-a1
101        */
102        $vars = array('parser', 'xml');
103        extract($this->dispatcher->trigger_event('core.text_formatter_s9e_parse_after', compact($vars)));
104
105        return $xml;
106    }
107
108    /**
109    * {@inheritdoc}
110    */
111    public function disable_bbcode($name)
112    {
113        $this->parser->disableTag(strtoupper($name));
114    }
115
116    /**
117    * {@inheritdoc}
118    */
119    public function disable_bbcodes()
120    {
121        $this->parser->disablePlugin('BBCodes');
122    }
123
124    /**
125    * {@inheritdoc}
126    */
127    public function disable_censor()
128    {
129        $this->parser->disablePlugin('Censor');
130    }
131
132    /**
133    * {@inheritdoc}
134    */
135    public function disable_magic_url()
136    {
137        $this->parser->disablePlugin('Autoemail');
138        $this->parser->disablePlugin('Autolink');
139    }
140
141    /**
142    * {@inheritdoc}
143    */
144    public function disable_smilies()
145    {
146        $this->parser->disablePlugin('Emoticons');
147        $this->parser->disablePlugin('Emoji');
148    }
149
150    /**
151    * {@inheritdoc}
152    */
153    public function enable_bbcode($name)
154    {
155        $this->parser->enableTag(strtoupper($name));
156    }
157
158    /**
159    * {@inheritdoc}
160    */
161    public function enable_bbcodes()
162    {
163        $this->parser->enablePlugin('BBCodes');
164    }
165
166    /**
167    * {@inheritdoc}
168    */
169    public function enable_censor()
170    {
171        $this->parser->enablePlugin('Censor');
172    }
173
174    /**
175    * {@inheritdoc}
176    */
177    public function enable_magic_url()
178    {
179        $this->parser->enablePlugin('Autoemail');
180        $this->parser->enablePlugin('Autolink');
181    }
182
183    /**
184    * {@inheritdoc}
185    */
186    public function enable_smilies()
187    {
188        $this->parser->enablePlugin('Emoticons');
189        $this->parser->enablePlugin('Emoji');
190    }
191
192    /**
193    * {@inheritdoc}
194    *
195    * This will convert the log entries found in s9e\TextFormatter's logger into phpBB error
196    * messages
197    */
198    public function get_errors()
199    {
200        $errors = array();
201        $entries = $this->parser->getLogger()->getLogs();
202
203        foreach ($entries as $entry)
204        {
205            list(, $msg, $context) = $entry;
206
207            if ($msg === 'Tag limit exceeded')
208            {
209                if ($context['tagName'] === 'E')
210                {
211                    $errors[] = array('TOO_MANY_SMILIES', $context['tagLimit']);
212                }
213                else if ($context['tagName'] === 'URL')
214                {
215                    $errors[] = array('TOO_MANY_URLS', $context['tagLimit']);
216                }
217            }
218            else if ($msg === 'MAX_FONT_SIZE_EXCEEDED')
219            {
220                $errors[] = array($msg, $context['max_size']);
221            }
222            else if (preg_match('/^MAX_IMG_(HEIGHT|WIDTH)_EXCEEDED$/D', $msg, $m))
223            {
224                $errors[] = array($msg, $context['max_' . strtolower($m[1])]);
225            }
226            else if ($msg === 'Tag is disabled' && $this->is_a_bbcode($context['tag']))
227            {
228                $name = strtolower($context['tag']->getName());
229                $errors[] = array('UNAUTHORISED_BBCODE', '[' . $name . ']');
230            }
231            else if ($msg === 'UNABLE_GET_IMAGE_SIZE')
232            {
233                $errors[] = array($msg);
234            }
235        }
236
237        // Deduplicate error messages. array_unique() only works on strings so we have to serialize
238        if (!empty($errors))
239        {
240            $errors = array_map('unserialize', array_unique(array_map('serialize', $errors)));
241        }
242
243        $parser = $this;
244
245        /**
246        * Modify error messages generated by the s9e\TextFormatter's logger
247        *
248        * @event core.text_formatter_s9e_get_errors
249        * @var parser    parser        This parser service
250        * @var array    entries        s9e\TextFormatter's logger entries
251        * @var array    errors        Error arrays with language key and optional arguments
252        * @since 3.2.10-RC1
253        * @since 3.3.1-RC1
254        */
255        $vars = [
256            'parser',
257            'entries',
258            'errors',
259        ];
260        extract($this->dispatcher->trigger_event('core.text_formatter_s9e_get_errors', compact($vars)));
261
262        return $errors;
263    }
264
265    /**
266    * Return the instance of s9e\TextFormatter\Parser used by this object
267    *
268    * @return \s9e\TextFormatter\Parser
269    */
270    public function get_parser()
271    {
272        return $this->parser;
273    }
274
275    /**
276    * {@inheritdoc}
277    */
278    public function set_var($name, $value)
279    {
280        if ($name === 'max_smilies')
281        {
282            $this->parser->setTagLimit('E', $value ?: PHP_INT_MAX);
283        }
284        else if ($name === 'max_urls')
285        {
286            $this->parser->setTagLimit('URL', $value ?: PHP_INT_MAX);
287        }
288        else
289        {
290            $this->parser->registeredVars[$name] = $value;
291        }
292    }
293
294    /**
295    * {@inheritdoc}
296    */
297    public function set_vars(array $vars)
298    {
299        foreach ($vars as $name => $value)
300        {
301            $this->set_var($name, $value);
302        }
303    }
304
305    /**
306    * Filter the value used in a [size] BBCode
307    *
308    * @see bbcode_firstpass::bbcode_size()
309    *
310    * @param  string  $size     Original size
311    * @param  integer $max_size Maximum allowed size
312    * @param  Logger  $logger
313    * @return mixed             Original value if valid, FALSE otherwise
314    */
315    public static function filter_font_size($size, $max_size, Logger $logger)
316    {
317        if ($max_size && $size > $max_size)
318        {
319            $logger->err('MAX_FONT_SIZE_EXCEEDED', array('max_size' => $max_size));
320
321            return false;
322        }
323
324        if ($size < 1 || !is_numeric($size))
325        {
326            return false;
327        }
328
329        return $size;
330    }
331
332    /**
333    * Filter an image's URL to enforce restrictions on its dimensions
334    *
335    * @see bbcode_firstpass::bbcode_img()
336    *
337    * @param  string  $url        Original URL
338    * @param  array   $url_config Config used by the URL filter
339    * @param  Logger  $logger
340    *
341    * @return string|bool         Original value if valid, FALSE otherwise
342    */
343    public static function filter_img_url($url, array $url_config, Logger $logger)
344    {
345        // Validate the URL
346        $url = UrlFilter::filter($url, $url_config, $logger);
347        if ($url === false)
348        {
349            return false;
350        }
351
352        return $url;
353    }
354
355    /**
356    * Test whether given tag consumes text that looks like BBCode-styled markup
357    *
358    * @param  Tag  $tag Original tag
359    * @return bool
360    */
361    protected function is_a_bbcode(Tag $tag)
362    {
363        if ($tag->getLen() < 3)
364        {
365            return false;
366        }
367        $markup = substr($this->parser->getText(), $tag->getPos(), $tag->getLen());
368
369        return (bool) preg_match('(^\\[\\w++.*?\\]$)s', $markup);
370    }
371}