1<?php
2
3/**
4 * The main glossary class.
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2005-2020 phpMyFAQ Team
13 * @license   http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2005-09-15
16 */
17
18namespace phpMyFAQ;
19
20/**
21 * Class Glossary
22 *
23 * @package phpMyFAQ
24 */
25class Glossary
26{
27    /**
28     * @var Configuration
29     */
30    private $config;
31
32    /**
33     * Item.
34     *
35     * @var array
36     */
37    private $item = [];
38
39    /**
40     * Definition of an item.
41     *
42     * @var string
43     */
44    private $definition = '';
45
46    /**
47     * Constructor.
48     *
49     * @param Configuration $config
50     */
51    public function __construct(Configuration $config)
52    {
53        $this->config = $config;
54    }
55
56    /**
57     * Fill the passed string with the current Glossary items.
58     *
59     * @param string $content Content
60     *
61     * @return string
62     */
63    public function insertItemsIntoContent($content = '')
64    {
65        if ('' == $content) {
66            return '';
67        }
68
69        $attributes = [
70            'href',
71            'src',
72            'title',
73            'alt',
74            'class',
75            'style',
76            'id',
77            'name',
78            'face',
79            'size',
80            'dir',
81            'rel',
82            'rev',
83            'onmouseenter',
84            'onmouseleave',
85            'onafterprint',
86            'onbeforeprint',
87            'onbeforeunload',
88            'onhashchange',
89            'onmessage',
90            'onoffline',
91            'ononline',
92            'onpopstate',
93            'onpagehide',
94            'onpageshow',
95            'onresize',
96            'onunload',
97            'ondevicemotion',
98            'ondeviceorientation',
99            'onabort',
100            'onblur',
101            'oncanplay',
102            'oncanplaythrough',
103            'onchange',
104            'onclick',
105            'oncontextmenu',
106            'ondblclick',
107            'ondrag',
108            'ondragend',
109            'ondragenter',
110            'ondragleave',
111            'ondragover',
112            'ondragstart',
113            'ondrop',
114            'ondurationchange',
115            'onemptied',
116            'onended',
117            'onerror',
118            'onfocus',
119            'oninput',
120            'oninvalid',
121            'onkeydown',
122            'onkeypress',
123            'onkeyup',
124            'onload',
125            'onloadeddata',
126            'onloadedmetadata',
127            'onloadstart',
128            'onmousedown',
129            'onmousemove',
130            'onmouseout',
131            'onmouseover',
132            'onmouseup',
133            'onpause',
134            'onplay',
135            'onplaying',
136            'onprogress',
137            'onratechange',
138            'onreset',
139            'onscroll',
140            'onseeked',
141            'onseeking',
142            'onselect',
143            'onshow',
144            'onstalled',
145            'onsubmit',
146            'onsuspend',
147            'ontimeupdate',
148            'onvolumechange',
149            'onwaiting',
150            'oncopy',
151            'oncut',
152            'onpaste',
153            'onbeforescriptexecute',
154            'onafterscriptexecute',
155        ];
156
157        foreach ($this->getAllGlossaryItems() as $item) {
158            $this->definition = $item['definition'];
159            $item['item'] = preg_quote($item['item'], '/');
160            $content = Strings::preg_replace_callback(
161                '/'
162                // a. the glossary item could be an attribute name
163                . '(' . $item['item'] . '="[^"]*")|'
164                // b. the glossary item could be inside an attribute value
165                . '((' . implode('|', $attributes) . ')="[^"]*' . $item['item'] . '[^"]*")|'
166                // c. the glossary item could be everywhere as a distinct word
167                . '(\W+)(' . $item['item'] . ')(\W+)|'
168                // d. the glossary item could be at the beginning of the string as a distinct word
169                . '^(' . $item['item'] . ')(\W+)|'
170                // e. the glossary item could be at the end of the string as a distinct word
171                . '(\W+)(' . $item['item'] . ')$'
172                . '/mis',
173                [$this, 'setTooltip'],
174                $content,
175                1
176            );
177        }
178
179        return $content;
180    }
181
182    /**
183     * Gets all items and definitions from the database.
184     *
185     * @return array
186     */
187    public function getAllGlossaryItems()
188    {
189        $items = [];
190
191        $query = sprintf(
192            "
193            SELECT
194                id, item, definition
195            FROM
196                %sfaqglossary
197            WHERE
198                lang = '%s'
199            ORDER BY item ASC",
200            Database::getTablePrefix(),
201            $this->config->getLanguage()->getLanguage()
202        );
203
204        $result = $this->config->getDb()->query($query);
205
206        while ($row = $this->config->getDb()->fetchObject($result)) {
207            $items[] = [
208                'id' => $row->id,
209                'item' => stripslashes($row->item),
210                'definition' => stripslashes($row->definition),
211            ];
212        }
213
214        return $items;
215    }
216
217    /**
218     * Callback function for filtering HTML from URLs and images.
219     *
220     * @param array $matches Matches
221     *
222     * @return string
223     */
224    public function setTooltip(array $matches)
225    {
226        $prefix = $postfix = '';
227
228        if (count($matches) > 9) {
229            // if the word is at the end of the string
230            $prefix = $matches[9];
231            $item = $matches[10];
232            $postfix = '';
233        } elseif (count($matches) > 7) {
234            // if the word is at the beginning of the string
235            $prefix = '';
236            $item = $matches[7];
237            $postfix = $matches[8];
238        } elseif (count($matches) > 4) {
239            // if the word is else where in the string
240            $prefix = $matches[4];
241            $item = $matches[5];
242            $postfix = $matches[6];
243        }
244
245        if (!empty($item)) {
246            return sprintf(
247                '%s<abbr data-toggle="tooltip" data-placement="bottom" title="%s">%s</abbr>%s',
248                $prefix,
249                $this->definition,
250                $item,
251                $postfix
252            );
253        }
254
255        // Fallback: the original matched string
256        return $matches[0];
257    }
258
259    /**
260     * Gets one item and definition from the database.
261     *
262     * @param int $id Glossary ID
263     *
264     * @return array
265     */
266    public function getGlossaryItem($id)
267    {
268        $item = [];
269
270        $query = sprintf(
271            "
272            SELECT
273                id, item, definition
274            FROM
275                %sfaqglossary
276            WHERE
277                id = %d AND lang = '%s'",
278            Database::getTablePrefix(),
279            (int)$id,
280            $this->config->getLanguage()->getLanguage()
281        );
282
283        $result = $this->config->getDb()->query($query);
284
285        while ($row = $this->config->getDb()->fetchObject($result)) {
286            $item = [
287                'id' => $row->id,
288                'item' => stripslashes($row->item),
289                'definition' => stripslashes($row->definition),
290            ];
291        }
292
293        return $item;
294    }
295
296    /**
297     * Inserts an item and definition into the database.
298     *
299     * @param string $item       Item
300     * @param string $definition Definition
301     *
302     * @return bool
303     */
304    public function addGlossaryItem($item, $definition)
305    {
306        $this->item = $this->config->getDb()->escape($item);
307        $this->definition = $this->config->getDb()->escape($definition);
308
309        $query = sprintf(
310            "
311            INSERT INTO
312                %sfaqglossary
313            (id, lang, item, definition)
314                VALUES
315            (%d, '%s', '%s', '%s')",
316            Database::getTablePrefix(),
317            $this->config->getDb()->nextId(Database::getTablePrefix() . 'faqglossary', 'id'),
318            $this->config->getLanguage()->getLanguage(),
319            Strings::htmlspecialchars($this->item),
320            Strings::htmlspecialchars($this->definition)
321        );
322
323        if ($this->config->getDb()->query($query)) {
324            return true;
325        }
326
327        return false;
328    }
329
330    /**
331     * Updates an item and definition into the database.
332     *
333     * @param int    $id         Glossary ID
334     * @param string $item       Item
335     * @param string $definition Definition
336     *
337     * @return bool
338     */
339    public function updateGlossaryItem($id, $item, $definition)
340    {
341        $this->item = $this->config->getDb()->escape($item);
342        $this->definition = $this->config->getDb()->escape($definition);
343
344        $query = sprintf(
345            "
346            UPDATE
347                %sfaqglossary
348            SET
349                item = '%s',
350                definition = '%s'
351            WHERE
352                id = %d AND lang = '%s'",
353            Database::getTablePrefix(),
354            Strings::htmlspecialchars($this->item),
355            Strings::htmlspecialchars($this->definition),
356            (int)$id,
357            $this->config->getLanguage()->getLanguage()
358        );
359
360        if ($this->config->getDb()->query($query)) {
361            return true;
362        }
363
364        return false;
365    }
366
367    /**
368     * Deletes an item and definition into the database.
369     *
370     * @param int $id Glossary ID
371     *
372     * @return bool
373     */
374    public function deleteGlossaryItem($id)
375    {
376        $query = sprintf(
377            "
378            DELETE FROM
379                %sfaqglossary
380            WHERE
381                id = %d AND lang = '%s'",
382            Database::getTablePrefix(),
383            (int)$id,
384            $this->config->getLanguage()->getLanguage()
385        );
386
387        if ($this->config->getDb()->query($query)) {
388            return true;
389        }
390
391        return false;
392    }
393}
394