<?php

/**
 * The main glossary class.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public License,
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
 * obtain one at http://mozilla.org/MPL/2.0/.
 *
 * @package   phpMyFAQ
 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
 * @copyright 2005-2020 phpMyFAQ Team
 * @license   http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
 * @link      https://www.phpmyfaq.de
 * @since     2005-09-15
 */

namespace phpMyFAQ;

/**
 * Class Glossary
 *
 * @package phpMyFAQ
 */
class Glossary
{
    /**
     * @var Configuration
     */
    private $config;

    /**
     * Item.
     *
     * @var array
     */
    private $item = [];

    /**
     * Definition of an item.
     *
     * @var string
     */
    private $definition = '';

    /**
     * Constructor.
     *
     * @param Configuration $config
     */
    public function __construct(Configuration $config)
    {
        $this->config = $config;
    }

    /**
     * Fill the passed string with the current Glossary items.
     *
     * @param string $content Content
     *
     * @return string
     */
    public function insertItemsIntoContent($content = '')
    {
        if ('' == $content) {
            return '';
        }

        $attributes = [
            'href',
            'src',
            'title',
            'alt',
            'class',
            'style',
            'id',
            'name',
            'face',
            'size',
            'dir',
            'rel',
            'rev',
            'onmouseenter',
            'onmouseleave',
            'onafterprint',
            'onbeforeprint',
            'onbeforeunload',
            'onhashchange',
            'onmessage',
            'onoffline',
            'ononline',
            'onpopstate',
            'onpagehide',
            'onpageshow',
            'onresize',
            'onunload',
            'ondevicemotion',
            'ondeviceorientation',
            'onabort',
            'onblur',
            'oncanplay',
            'oncanplaythrough',
            'onchange',
            'onclick',
            'oncontextmenu',
            'ondblclick',
            'ondrag',
            'ondragend',
            'ondragenter',
            'ondragleave',
            'ondragover',
            'ondragstart',
            'ondrop',
            'ondurationchange',
            'onemptied',
            'onended',
            'onerror',
            'onfocus',
            'oninput',
            'oninvalid',
            'onkeydown',
            'onkeypress',
            'onkeyup',
            'onload',
            'onloadeddata',
            'onloadedmetadata',
            'onloadstart',
            'onmousedown',
            'onmousemove',
            'onmouseout',
            'onmouseover',
            'onmouseup',
            'onpause',
            'onplay',
            'onplaying',
            'onprogress',
            'onratechange',
            'onreset',
            'onscroll',
            'onseeked',
            'onseeking',
            'onselect',
            'onshow',
            'onstalled',
            'onsubmit',
            'onsuspend',
            'ontimeupdate',
            'onvolumechange',
            'onwaiting',
            'oncopy',
            'oncut',
            'onpaste',
            'onbeforescriptexecute',
            'onafterscriptexecute',
        ];

        foreach ($this->getAllGlossaryItems() as $item) {
            $this->definition = $item['definition'];
            $item['item'] = preg_quote($item['item'], '/');
            $content = Strings::preg_replace_callback(
                '/'
                // a. the glossary item could be an attribute name
                . '(' . $item['item'] . '="[^"]*")|'
                // b. the glossary item could be inside an attribute value
                . '((' . implode('|', $attributes) . ')="[^"]*' . $item['item'] . '[^"]*")|'
                // c. the glossary item could be everywhere as a distinct word
                . '(\W+)(' . $item['item'] . ')(\W+)|'
                // d. the glossary item could be at the beginning of the string as a distinct word
                . '^(' . $item['item'] . ')(\W+)|'
                // e. the glossary item could be at the end of the string as a distinct word
                . '(\W+)(' . $item['item'] . ')$'
                . '/mis',
                [$this, 'setTooltip'],
                $content,
                1
            );
        }

        return $content;
    }

    /**
     * Gets all items and definitions from the database.
     *
     * @return array
     */
    public function getAllGlossaryItems()
    {
        $items = [];

        $query = sprintf(
            "
            SELECT
                id, item, definition
            FROM
                %sfaqglossary
            WHERE
                lang = '%s'
            ORDER BY item ASC",
            Database::getTablePrefix(),
            $this->config->getLanguage()->getLanguage()
        );

        $result = $this->config->getDb()->query($query);

        while ($row = $this->config->getDb()->fetchObject($result)) {
            $items[] = [
                'id' => $row->id,
                'item' => stripslashes($row->item),
                'definition' => stripslashes($row->definition),
            ];
        }

        return $items;
    }

    /**
     * Callback function for filtering HTML from URLs and images.
     *
     * @param array $matches Matches
     *
     * @return string
     */
    public function setTooltip(array $matches)
    {
        $prefix = $postfix = '';

        if (count($matches) > 9) {
            // if the word is at the end of the string
            $prefix = $matches[9];
            $item = $matches[10];
            $postfix = '';
        } elseif (count($matches) > 7) {
            // if the word is at the beginning of the string
            $prefix = '';
            $item = $matches[7];
            $postfix = $matches[8];
        } elseif (count($matches) > 4) {
            // if the word is else where in the string
            $prefix = $matches[4];
            $item = $matches[5];
            $postfix = $matches[6];
        }

        if (!empty($item)) {
            return sprintf(
                '%s<abbr data-toggle="tooltip" data-placement="bottom" title="%s">%s</abbr>%s',
                $prefix,
                $this->definition,
                $item,
                $postfix
            );
        }

        // Fallback: the original matched string
        return $matches[0];
    }

    /**
     * Gets one item and definition from the database.
     *
     * @param int $id Glossary ID
     *
     * @return array
     */
    public function getGlossaryItem($id)
    {
        $item = [];

        $query = sprintf(
            "
            SELECT
                id, item, definition
            FROM
                %sfaqglossary
            WHERE
                id = %d AND lang = '%s'",
            Database::getTablePrefix(),
            (int)$id,
            $this->config->getLanguage()->getLanguage()
        );

        $result = $this->config->getDb()->query($query);

        while ($row = $this->config->getDb()->fetchObject($result)) {
            $item = [
                'id' => $row->id,
                'item' => stripslashes($row->item),
                'definition' => stripslashes($row->definition),
            ];
        }

        return $item;
    }

    /**
     * Inserts an item and definition into the database.
     *
     * @param string $item       Item
     * @param string $definition Definition
     *
     * @return bool
     */
    public function addGlossaryItem($item, $definition)
    {
        $this->item = $this->config->getDb()->escape($item);
        $this->definition = $this->config->getDb()->escape($definition);

        $query = sprintf(
            "
            INSERT INTO
                %sfaqglossary
            (id, lang, item, definition)
                VALUES
            (%d, '%s', '%s', '%s')",
            Database::getTablePrefix(),
            $this->config->getDb()->nextId(Database::getTablePrefix() . 'faqglossary', 'id'),
            $this->config->getLanguage()->getLanguage(),
            Strings::htmlspecialchars($this->item),
            Strings::htmlspecialchars($this->definition)
        );

        if ($this->config->getDb()->query($query)) {
            return true;
        }

        return false;
    }

    /**
     * Updates an item and definition into the database.
     *
     * @param int    $id         Glossary ID
     * @param string $item       Item
     * @param string $definition Definition
     *
     * @return bool
     */
    public function updateGlossaryItem($id, $item, $definition)
    {
        $this->item = $this->config->getDb()->escape($item);
        $this->definition = $this->config->getDb()->escape($definition);

        $query = sprintf(
            "
            UPDATE
                %sfaqglossary
            SET
                item = '%s',
                definition = '%s'
            WHERE
                id = %d AND lang = '%s'",
            Database::getTablePrefix(),
            Strings::htmlspecialchars($this->item),
            Strings::htmlspecialchars($this->definition),
            (int)$id,
            $this->config->getLanguage()->getLanguage()
        );

        if ($this->config->getDb()->query($query)) {
            return true;
        }

        return false;
    }

    /**
     * Deletes an item and definition into the database.
     *
     * @param int $id Glossary ID
     *
     * @return bool
     */
    public function deleteGlossaryItem($id)
    {
        $query = sprintf(
            "
            DELETE FROM
                %sfaqglossary
            WHERE
                id = %d AND lang = '%s'",
            Database::getTablePrefix(),
            (int)$id,
            $this->config->getLanguage()->getLanguage()
        );

        if ($this->config->getDb()->query($query)) {
            return true;
        }

        return false;
    }
}
