Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
80.28% covered (warning)
80.28%
57 / 71
63.64% covered (warning)
63.64%
7 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
base
80.28% covered (warning)
80.28%
57 / 71
63.64% covered (warning)
63.64%
7 / 11
50.66
0.00% covered (danger)
0.00%
0 / 1
 get_max_id
n/a
0 / 0
n/a
0 / 0
0
 get_records_by_range
n/a
0 / 0
n/a
0 / 0
0
 save_record
n/a
0 / 0
n/a
0 / 0
0
 add_missing_fields
73.68% covered (warning)
73.68%
14 / 19
0.00% covered (danger)
0.00%
0 / 1
5.46
 get_name
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 set_name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 disable_save
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 enable_save
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 guess_bbcode
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 guess_bbcodes
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 guess_magic_url
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 guess_smilies
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 reparse_range
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 reparse_record
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
15
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\textreparser;
15
16abstract class base implements reparser_interface
17{
18    /**
19     * @var string The reparser name
20     */
21    protected $name;
22
23    /**
24    * @var bool Whether to save changes to the database
25    */
26    protected $save_changes = true;
27
28    /**
29    * {@inheritdoc}
30    */
31    abstract public function get_max_id();
32
33    /**
34    * Return all records in given range
35    *
36    * @param  integer $min_id Lower bound
37    * @param  integer $max_id Upper bound
38    * @return array           Array of records
39    */
40    abstract protected function get_records_by_range($min_id, $max_id);
41
42    /**
43     * Save record
44     *
45     * @param array $record
46     * @return void
47     */
48    abstract protected function save_record(array $record);
49
50    /**
51    * Add fields to given record, if applicable
52    *
53    * The enable_* fields are not always saved to the database. Sometimes we need to guess their
54    * original value based on the text content or possibly other fields
55    *
56    * @param  array $record Original record
57    * @return array         Complete record
58    */
59    protected function add_missing_fields(array $record)
60    {
61        if (!isset($record['enable_bbcode'], $record['enable_smilies'], $record['enable_magic_url']))
62        {
63            if (isset($record['options']))
64            {
65                $record += array(
66                    'enable_bbcode'    => (bool) ($record['options'] & OPTION_FLAG_BBCODE),
67                    'enable_smilies'   => (bool) ($record['options'] & OPTION_FLAG_SMILIES),
68                    'enable_magic_url' => (bool) ($record['options'] & OPTION_FLAG_LINKS),
69                );
70            }
71            else
72            {
73                $record += array(
74                    'enable_bbcode'    => $this->guess_bbcodes($record),
75                    'enable_smilies'   => $this->guess_smilies($record),
76                    'enable_magic_url' => $this->guess_magic_url($record),
77                );
78            }
79        }
80
81        // Those BBCodes are disabled based on context and user permissions and that value is never
82        // stored in the database. Here we test whether they were used in the original text.
83        $bbcodes = array('img', 'quote', 'url');
84        foreach ($bbcodes as $bbcode)
85        {
86            $field_name = 'enable_' . $bbcode . '_bbcode';
87            $record[$field_name] = $this->guess_bbcode($record, $bbcode);
88        }
89
90        // Magic URLs are tied to the URL BBCode, that's why if magic URLs are enabled we make sure
91        // that the URL BBCode is also enabled
92        if ($record['enable_magic_url'])
93        {
94            $record['enable_url_bbcode'] = true;
95        }
96
97        return $record;
98    }
99
100    /**
101     * Returns the name of the reparser
102     *
103     * @return string Name of reparser
104     */
105    public function get_name()
106    {
107        return $this->name;
108    }
109
110    /**
111     * Sets the name of the reparser
112     *
113     * @param string $name The reparser name
114     */
115    public function set_name($name)
116    {
117        $this->name = $name;
118    }
119
120    /**
121    * Disable saving changes to the database
122    */
123    public function disable_save()
124    {
125        $this->save_changes = false;
126    }
127
128    /**
129    * Enable saving changes to the database
130    */
131    public function enable_save()
132    {
133        $this->save_changes = true;
134    }
135
136    /**
137    * Guess whether given BBCode is in use in given record
138    *
139    * @param  array  $record
140    * @param  string $bbcode
141    * @return bool
142    */
143    protected function guess_bbcode(array $record, $bbcode)
144    {
145        if (!empty($record['bbcode_uid']))
146        {
147            // Look for the closing tag, e.g. [/url]
148            $match = '[/' . $bbcode . ':' . $record['bbcode_uid'];
149            if (strpos($record['text'], $match) !== false)
150            {
151                return true;
152            }
153        }
154
155        if (substr($record['text'], 0, 2) === '<r')
156        {
157            // Look for the closing tag inside of a e element, in an element of the same name, e.g.
158            // <e>[/url]</e></URL>
159            $match = '<e>[/' . $bbcode . ']</e></' . $bbcode . '>';
160            if (stripos($record['text'], $match) !== false)
161            {
162                return true;
163            }
164        }
165
166        return false;
167    }
168
169    /**
170    * Guess whether any BBCode is in use in given record
171    *
172    * @param  array $record
173    * @return bool
174    */
175    protected function guess_bbcodes(array $record)
176    {
177        if (!empty($record['bbcode_uid']))
178        {
179            // Test whether the bbcode_uid is in use
180            $match = ':' . $record['bbcode_uid'];
181            if (strpos($record['text'], $match) !== false)
182            {
183                return true;
184            }
185        }
186
187        if (substr($record['text'], 0, 2) === '<r')
188        {
189            // Look for a closing tag inside of an e element
190            return (bool) preg_match('(<e>\\[/\\w+\\]</e>)', $match);
191        }
192
193        return false;
194    }
195
196    /**
197    * Guess whether magic URLs are in use in given record
198    *
199    * @param  array $record
200    * @return bool
201    */
202    protected function guess_magic_url(array $record)
203    {
204        // Look for magic URL markers or for a URL tag that's not immediately followed by <s>
205        return preg_match('#<!-- ([lmwe]) -->.*?<!-- \1 -->#', $record['text']) || preg_match('(<URL [^>]++>(?!<s>))', $record['text']);
206    }
207
208    /**
209    * Guess whether smilies are in use in given record
210    *
211    * @param  array $record
212    * @return bool
213    */
214    protected function guess_smilies(array $record)
215    {
216        return (strpos($record['text'], '<!-- s') !== false || strpos($record['text'], '<E>') !== false);
217    }
218
219    /**
220    * {@inheritdoc}
221    */
222    public function reparse_range($min_id, $max_id, bool $force_bbcode_reparsing = false)
223    {
224        foreach ($this->get_records_by_range($min_id, $max_id) as $record)
225        {
226            $this->reparse_record($record, $force_bbcode_reparsing);
227        }
228    }
229
230    /**
231    * Reparse given record
232    *
233    * @param array $record Associative array containing the record's data
234    * @param bool $force_bbcode_reparsing Flag indicating if BBCode should be reparsed unconditionally
235    */
236    protected function reparse_record(array $record, bool $force_bbcode_reparsing = false)
237    {
238        // Guess magic URL state based on actual record content before adding fields
239        $record['enable_magic_url'] = $this->guess_magic_url($record);
240        $record = $this->add_missing_fields($record);
241
242        $flags = ($record['enable_bbcode'] || $force_bbcode_reparsing) ? OPTION_FLAG_BBCODE : 0;
243        $flags |= ($record['enable_smilies'] || $force_bbcode_reparsing) ? OPTION_FLAG_SMILIES : 0;
244        $flags |= ($record['enable_magic_url'] || $force_bbcode_reparsing) ? OPTION_FLAG_LINKS : 0;
245        $unparsed = array_merge(
246            $record,
247            generate_text_for_edit($record['text'], $record['bbcode_uid'], $flags)
248        );
249
250        // generate_text_for_edit() and decode_message() actually return the text as HTML. It has to
251        // be decoded to plain text before it can be reparsed
252        $text = html_entity_decode($unparsed['text'], ENT_QUOTES, 'UTF-8');
253        $bitfield = '';
254        $flags = 0;
255        generate_text_for_storage(
256            $text,
257            $unparsed['bbcode_uid'],
258            $bitfield,
259            $flags,
260            $unparsed['enable_bbcode'] || $force_bbcode_reparsing,
261            $unparsed['enable_magic_url'] || $force_bbcode_reparsing,
262            $unparsed['enable_smilies'] || $force_bbcode_reparsing,
263            $unparsed['enable_img_bbcode'] || $force_bbcode_reparsing,
264            $unparsed['enable_quote_bbcode'] || $force_bbcode_reparsing,
265            $unparsed['enable_url_bbcode'] || $force_bbcode_reparsing,
266            'text_reparser.' . $this->get_name()
267        );
268
269        // Save the new text if it has changed and it's not a dry run
270        if ($text !== $record['text'] && $this->save_changes)
271        {
272            $record['text'] = $text;
273            $this->save_record($record);
274        }
275    }
276}