xref: /plugin/tagging/helper.php (revision 55d5df8c4e40f0eede3f71e725efe9b563b29dbd)
1f61105deSAdrian Lang<?php
2ec4796e4SAnna Dabrowska
3df43a7beSAndreas Gohruse dokuwiki\Extension\Plugin;
4df43a7beSAndreas Gohruse dokuwiki\Utf8\PhpString;
5ec4796e4SAnna Dabrowskause dokuwiki\Form\Form;
6df43a7beSAndreas Gohruse dokuwiki\Search\Indexer;
7*55d5df8cSAnna Dabrowskause dokuwiki\File\PageResolver;
8ec4796e4SAnna Dabrowska
9aa627deeSAndreas Gohr/**
10aa627deeSAndreas Gohr * Tagging Plugin (hlper component)
11aa627deeSAndreas Gohr *
12aa627deeSAndreas Gohr * @license GPL 2
13aa627deeSAndreas Gohr */
14df43a7beSAndreas Gohrclass helper_plugin_tagging extends Plugin
15df43a7beSAndreas Gohr{
16289f50bdSAndreas Gohr    /**
17b12334e1SAndreas Gohr     * Gives access to the database
18b12334e1SAndreas Gohr     *
19b12334e1SAndreas Gohr     * Initializes the SQLite helper and register the CLEANTAG function
20b12334e1SAndreas Gohr     *
21b12334e1SAndreas Gohr     * @return helper_plugin_sqlite|bool false if initialization fails
22289f50bdSAndreas Gohr     */
23df43a7beSAndreas Gohr    public function getDB()
24df43a7beSAndreas Gohr    {
25302a38efSAndreas Gohr        static $db = null;
26aa627deeSAndreas Gohr        if ($db !== null) {
27f61105deSAdrian Lang            return $db;
28f61105deSAdrian Lang        }
29f61105deSAdrian Lang
30302a38efSAndreas Gohr        /** @var helper_plugin_sqlite $db */
31f61105deSAdrian Lang        $db = plugin_load('helper', 'sqlite');
32aa627deeSAndreas Gohr        if ($db === null) {
33f61105deSAdrian Lang            msg('The tagging plugin needs the sqlite plugin', -1);
34ca455b8eSMichael Große
35f61105deSAdrian Lang            return false;
36f61105deSAdrian Lang        }
37aa627deeSAndreas Gohr        $db->init('tagging', __DIR__ . '/db/');
38df43a7beSAndreas Gohr        $db->create_function('CLEANTAG', [$this, 'cleanTag'], 1);
39df43a7beSAndreas Gohr        $db->create_function(
40df43a7beSAndreas Gohr            'GROUP_SORT',
417e05e623SSzymon Olewniczak            function ($group, $newDelimiter) {
4240b94b1aSAnna Dabrowska                $ex = array_filter(explode(',', $group));
437e05e623SSzymon Olewniczak                sort($ex);
44ca455b8eSMichael Große
457e05e623SSzymon Olewniczak                return implode($newDelimiter, $ex);
46df43a7beSAndreas Gohr            },
47df43a7beSAndreas Gohr            2
48df43a7beSAndreas Gohr        );
4940b94b1aSAnna Dabrowska        $db->create_function('GET_NS', 'getNS', 1);
50ca455b8eSMichael Große
51f61105deSAdrian Lang        return $db;
52f61105deSAdrian Lang    }
53f61105deSAdrian Lang
54302a38efSAndreas Gohr    /**
552ace74f4SAndreas Gohr     * Return the user to use for accessing tags
562ace74f4SAndreas Gohr     *
572ace74f4SAndreas Gohr     * Handles the singleuser mode by returning 'auto' as user. Returnes false when no user is logged in.
582ace74f4SAndreas Gohr     *
592ace74f4SAndreas Gohr     * @return bool|string
602ace74f4SAndreas Gohr     */
61df43a7beSAndreas Gohr    public function getUser()
62df43a7beSAndreas Gohr    {
630cfde7e9SMichael Große        if (!isset($_SERVER['REMOTE_USER'])) {
640cfde7e9SMichael Große            return false;
650cfde7e9SMichael Große        }
660cfde7e9SMichael Große        if ($this->getConf('singleusermode')) {
670cfde7e9SMichael Große            return 'auto';
680cfde7e9SMichael Große        }
69ca455b8eSMichael Große
702ace74f4SAndreas Gohr        return $_SERVER['REMOTE_USER'];
712ace74f4SAndreas Gohr    }
722ace74f4SAndreas Gohr
732ace74f4SAndreas Gohr    /**
74e4443e5cSAnna Dabrowska     * If plugin elasticsearch is installed, inform it that we have just made changes
75e4443e5cSAnna Dabrowska     * to some data relevant to a page. The page should be re-indexed.
76e4443e5cSAnna Dabrowska     *
77e4443e5cSAnna Dabrowska     * @param string $id
78e4443e5cSAnna Dabrowska     */
79e4443e5cSAnna Dabrowska    public function updateElasticState($id)
80e4443e5cSAnna Dabrowska    {
81e4443e5cSAnna Dabrowska        /** @var \helper_plugin_elasticsearch_plugins $elasticHelper */
82e4443e5cSAnna Dabrowska        $elasticHelper = plugin_load('helper', 'elasticsearch_plugins');
83e4443e5cSAnna Dabrowska        if ($elasticHelper) {
84e4443e5cSAnna Dabrowska            $elasticHelper->updateRefreshState($id);
85e4443e5cSAnna Dabrowska        }
86e4443e5cSAnna Dabrowska    }
87e4443e5cSAnna Dabrowska
88e4443e5cSAnna Dabrowska    /**
89*55d5df8cSAnna Dabrowska     * Resolve the ns filter
90*55d5df8cSAnna Dabrowska     *
91*55d5df8cSAnna Dabrowska     * @param array $data
92*55d5df8cSAnna Dabrowska     * @return string
93*55d5df8cSAnna Dabrowska     */
94*55d5df8cSAnna Dabrowska    public function resolveNs(array $data)
95*55d5df8cSAnna Dabrowska    {
96*55d5df8cSAnna Dabrowska        if (!isset($data['ns'])) {
97*55d5df8cSAnna Dabrowska            $data['ns'] = '.';
98*55d5df8cSAnna Dabrowska        }
99*55d5df8cSAnna Dabrowska        if ($data['ns'] === '*') {
100*55d5df8cSAnna Dabrowska            $data['ns'] = '';
101*55d5df8cSAnna Dabrowska        }
102*55d5df8cSAnna Dabrowska
103*55d5df8cSAnna Dabrowska        global $ID;
104*55d5df8cSAnna Dabrowska        $resolver = new PageResolver($ID);
105*55d5df8cSAnna Dabrowska        $data['ns'] = getNS($resolver->resolveId($data['ns'] . ':fakeIdNotResolvable')) ?: ':';
106*55d5df8cSAnna Dabrowska
107*55d5df8cSAnna Dabrowska        return $data['ns'];
108*55d5df8cSAnna Dabrowska    }
109*55d5df8cSAnna Dabrowska
110*55d5df8cSAnna Dabrowska    /**
111302a38efSAndreas Gohr     * Canonicalizes the tag to its lower case nospace form
112302a38efSAndreas Gohr     *
113302a38efSAndreas Gohr     * @param $tag
1140cfde7e9SMichael Große     *
115302a38efSAndreas Gohr     * @return string
116302a38efSAndreas Gohr     */
117df43a7beSAndreas Gohr    public function cleanTag($tag)
118df43a7beSAndreas Gohr    {
119df43a7beSAndreas Gohr        $tag = str_replace([' ', '-', '_', '#'], '', $tag);
120df43a7beSAndreas Gohr        return PhpString::strtolower($tag);
121302a38efSAndreas Gohr    }
122302a38efSAndreas Gohr
12356d82720SAndreas Gohr    /**
12431396860SSzymon Olewniczak     * Canonicalizes the namespace, remove the first colon and add glob
12531396860SSzymon Olewniczak     *
12631396860SSzymon Olewniczak     * @param $namespace
12731396860SSzymon Olewniczak     *
12831396860SSzymon Olewniczak     * @return string
12931396860SSzymon Olewniczak     */
130df43a7beSAndreas Gohr    public function globNamespace($namespace)
131df43a7beSAndreas Gohr    {
132de379874SAnna Dabrowska        return cleanId($namespace) . '*';
13331396860SSzymon Olewniczak    }
13431396860SSzymon Olewniczak
13531396860SSzymon Olewniczak    /**
13656d82720SAndreas Gohr     * Create or Update tags of a page
13756d82720SAndreas Gohr     *
13856d82720SAndreas Gohr     * Uses the translation plugin to store the language of a page (if available)
13956d82720SAndreas Gohr     *
14056d82720SAndreas Gohr     * @param string $id The page ID
14156d82720SAndreas Gohr     * @param string $user
14256d82720SAndreas Gohr     * @param array  $tags
1430cfde7e9SMichael Große     *
14456d82720SAndreas Gohr     * @return bool|SQLiteResult
14556d82720SAndreas Gohr     */
146df43a7beSAndreas Gohr    public function replaceTags($id, $user, $tags)
147df43a7beSAndreas Gohr    {
14856d82720SAndreas Gohr        global $conf;
14956d82720SAndreas Gohr        /** @var helper_plugin_translation $trans */
15056d82720SAndreas Gohr        $trans = plugin_load('helper', 'translation');
15156d82720SAndreas Gohr        if ($trans) {
15256d82720SAndreas Gohr            $lang = $trans->realLC($trans->getLangPart($id));
15356d82720SAndreas Gohr        } else {
15456d82720SAndreas Gohr            $lang = $conf['lang'];
15556d82720SAndreas Gohr        }
15656d82720SAndreas Gohr
157f61105deSAdrian Lang        $db = $this->getDB();
158f61105deSAdrian Lang        $db->query('BEGIN TRANSACTION');
159df43a7beSAndreas Gohr
160df43a7beSAndreas Gohr        $queries = [['DELETE FROM taggings WHERE pid = ? AND tagger = ?', $id, $user]];
161f61105deSAdrian Lang        foreach ($tags as $tag) {
162df43a7beSAndreas Gohr            $queries[] = ['INSERT INTO taggings (pid, tagger, tag, lang) VALUES(?, ?, ?, ?)', $id, $user, $tag, $lang];
163f61105deSAdrian Lang        }
164f61105deSAdrian Lang
165f61105deSAdrian Lang        foreach ($queries as $query) {
166df43a7beSAndreas Gohr            if (!call_user_func_array([$db, 'query'], $query)) {
167f61105deSAdrian Lang                $db->query('ROLLBACK TRANSACTION');
168ca455b8eSMichael Große
169f61105deSAdrian Lang                return false;
170f61105deSAdrian Lang            }
171f61105deSAdrian Lang        }
172ca455b8eSMichael Große
173f61105deSAdrian Lang        return $db->query('COMMIT TRANSACTION');
174f61105deSAdrian Lang    }
175f61105deSAdrian Lang
1760a518a11SAndreas Gohr    /**
177b12334e1SAndreas Gohr     * Get a list of Tags or Pages matching search criteria
1780a518a11SAndreas Gohr     *
179b12334e1SAndreas Gohr     * @param array  $filter What to search for array('field' => 'searchterm')
180b12334e1SAndreas Gohr     * @param string $type   What field to return 'tag'|'pid'
181077ff864SAndreas Gohr     * @param int    $limit  Limit to this many results, 0 for all
1820cfde7e9SMichael Große     *
1830a518a11SAndreas Gohr     * @return array associative array in form of value => count
1840a518a11SAndreas Gohr     */
185df43a7beSAndreas Gohr    public function findItems($filter, $type, $limit = 0)
186df43a7beSAndreas Gohr    {
187df43a7beSAndreas Gohr        $queryBuilder = new helper_plugin_tagging_querybuilder();
1881b4b4fa9SAnna Dabrowska
1894a7da0a5SAnna Dabrowska        $queryBuilder->setField($type);
1904a7da0a5SAnna Dabrowska        $queryBuilder->setLimit($limit);
191739c5360SAnna Dabrowska        $queryBuilder->setTags($this->extractFromQuery($filter));
1924a7da0a5SAnna Dabrowska        if (isset($filter['ns'])) $queryBuilder->includeNS($filter['ns']);
1934a7da0a5SAnna Dabrowska        if (isset($filter['notns'])) $queryBuilder->excludeNS($filter['notns']);
1944a7da0a5SAnna Dabrowska        if (isset($filter['tagger'])) $queryBuilder->setTagger($filter['tagger']);
1954a7da0a5SAnna Dabrowska        if (isset($filter['pid'])) $queryBuilder->setPid($filter['pid']);
196b12334e1SAndreas Gohr
1974a7da0a5SAnna Dabrowska        return $this->queryDb($queryBuilder->getQuery());
198f61105deSAdrian Lang    }
199f61105deSAdrian Lang
200b12334e1SAndreas Gohr    /**
201302a38efSAndreas Gohr     * Constructs the URL to search for a tag
202302a38efSAndreas Gohr     *
2035540f91dSAndreas Gohr     * @param string $tag
2045540f91dSAndreas Gohr     * @param string $ns
2050cfde7e9SMichael Große     *
206302a38efSAndreas Gohr     * @return string
207302a38efSAndreas Gohr     */
208df43a7beSAndreas Gohr    public function getTagSearchURL($tag, $ns = '')
209df43a7beSAndreas Gohr    {
210a99fe09cSAnna Dabrowska        $ret = '?do=search&sf=1&q=' . rawurlencode('#' . $this->cleanTag($tag));
211*55d5df8cSAnna Dabrowska        if ($ns && $ns !== ':') {
2120cfde7e9SMichael Große            $ret .= rawurlencode(' @' . $ns);
2130cfde7e9SMichael Große        }
2145540f91dSAndreas Gohr
2155540f91dSAndreas Gohr        return $ret;
216f61105deSAdrian Lang    }
217f61105deSAdrian Lang
2185540f91dSAndreas Gohr    /**
2195540f91dSAndreas Gohr     * Calculates the size levels for the given list of clouds
2205540f91dSAndreas Gohr     *
2215540f91dSAndreas Gohr     * Automatically determines sensible tresholds
2225540f91dSAndreas Gohr     *
2235540f91dSAndreas Gohr     * @param array $tags list of tags => count
2245540f91dSAndreas Gohr     * @param int   $levels
2250cfde7e9SMichael Große     *
226df43a7beSAndreas Gohr     * @return array
2275540f91dSAndreas Gohr     */
228df43a7beSAndreas Gohr    public function cloudData($tags, $levels = 10)
229df43a7beSAndreas Gohr    {
230f61105deSAdrian Lang        $min = min($tags);
231f61105deSAdrian Lang        $max = max($tags);
232f61105deSAdrian Lang
233f61105deSAdrian Lang        // calculate tresholds
234df43a7beSAndreas Gohr        $tresholds = [];
235f61105deSAdrian Lang        for ($i = 0; $i <= $levels; $i++) {
236df43a7beSAndreas Gohr            $tresholds[$i] = ($max - $min + 1) ** ($i / $levels) + $min - 1;
237f61105deSAdrian Lang        }
238f61105deSAdrian Lang
239f61105deSAdrian Lang        // assign weights
240f61105deSAdrian Lang        foreach ($tags as $tag => $cnt) {
241f61105deSAdrian Lang            foreach ($tresholds as $tresh => $val) {
242f61105deSAdrian Lang                if ($cnt <= $val) {
243f61105deSAdrian Lang                    $tags[$tag] = $tresh;
244f61105deSAdrian Lang                    break;
245f61105deSAdrian Lang                }
246f61105deSAdrian Lang                $tags[$tag] = $levels;
247f61105deSAdrian Lang            }
248f61105deSAdrian Lang        }
249ca455b8eSMichael Große
250f61105deSAdrian Lang        return $tags;
251f61105deSAdrian Lang    }
252f61105deSAdrian Lang
2535540f91dSAndreas Gohr    /**
2545540f91dSAndreas Gohr     * Display a tag cloud
2555540f91dSAndreas Gohr     *
2565540f91dSAndreas Gohr     * @param array    $tags   list of tags => count
2575540f91dSAndreas Gohr     * @param string   $type   'tag'
2585540f91dSAndreas Gohr     * @param Callable $func   The function to print the link (gets tag and ns)
2595540f91dSAndreas Gohr     * @param bool     $wrap   wrap cloud in UL tags?
2605540f91dSAndreas Gohr     * @param bool     $return returnn HTML instead of printing?
2615540f91dSAndreas Gohr     * @param string   $ns     Add this namespace to search links
2620cfde7e9SMichael Große     *
2635540f91dSAndreas Gohr     * @return string
2645540f91dSAndreas Gohr     */
265df43a7beSAndreas Gohr    public function html_cloud($tags, $type, $func, $wrap = true, $return = false, $ns = '')
266df43a7beSAndreas Gohr    {
267a66f6715SAndreas Gohr        global $INFO;
268a66f6715SAndreas Gohr
269a66f6715SAndreas Gohr        $hidden_str = $this->getConf('hiddenprefix');
270a66f6715SAndreas Gohr        $hidden_len = strlen($hidden_str);
271a66f6715SAndreas Gohr
272f61105deSAdrian Lang        $ret = '';
2730cfde7e9SMichael Große        if ($wrap) {
2740cfde7e9SMichael Große            $ret .= '<ul class="tagging_cloud clearfix">';
2750cfde7e9SMichael Große        }
276f61105deSAdrian Lang        if (count($tags) === 0) {
277f61105deSAdrian Lang            // Produce valid XHTML (ul needs a child)
278f61105deSAdrian Lang            $this->setupLocale();
279f61105deSAdrian Lang            $ret .= '<li><div class="li">' . $this->lang['js']['no' . $type . 's'] . '</div></li>';
280f61105deSAdrian Lang        } else {
281f61105deSAdrian Lang            $tags = $this->cloudData($tags);
282f61105deSAdrian Lang            foreach ($tags as $val => $size) {
283a66f6715SAndreas Gohr                // skip hidden tags for users that can't edit
284df43a7beSAndreas Gohr                if (
285df43a7beSAndreas Gohr                    $type === 'tag' &&
286df43a7beSAndreas Gohr                    $hidden_len &&
287df43a7beSAndreas Gohr                    substr($val, 0, $hidden_len) == $hidden_str &&
288a66f6715SAndreas Gohr                    !($this->getUser() && $INFO['writable'])
289a66f6715SAndreas Gohr                ) {
290a66f6715SAndreas Gohr                    continue;
291a66f6715SAndreas Gohr                }
292a66f6715SAndreas Gohr
293f61105deSAdrian Lang                $ret .= '<li class="t' . $size . '"><div class="li">';
2945540f91dSAndreas Gohr                $ret .= call_user_func($func, $val, $ns);
295f61105deSAdrian Lang                $ret .= '</div></li>';
296f61105deSAdrian Lang            }
297f61105deSAdrian Lang        }
2980cfde7e9SMichael Große        if ($wrap) {
2990cfde7e9SMichael Große            $ret .= '</ul>';
3000cfde7e9SMichael Große        }
3010cfde7e9SMichael Große        if ($return) {
3020cfde7e9SMichael Große            return $ret;
3030cfde7e9SMichael Große        }
304f61105deSAdrian Lang        echo $ret;
305ca455b8eSMichael Große
3065540f91dSAndreas Gohr        return '';
307f61105deSAdrian Lang    }
308f61105deSAdrian Lang
3095540f91dSAndreas Gohr    /**
3100b6fad27Ssandos187     * Display a List of Page Links
3110b6fad27Ssandos187     *
3120b6fad27Ssandos187     * @param array    $pids   list of pids => count
3130b6fad27Ssandos187     * @return string
3140b6fad27Ssandos187     */
315df43a7beSAndreas Gohr    public function html_page_list($pids)
316df43a7beSAndreas Gohr    {
3170b6fad27Ssandos187        $ret = '<div class="search_quickresult">';
3180b6fad27Ssandos187        $ret .= '<ul class="search_quickhits">';
3190b6fad27Ssandos187
3200b6fad27Ssandos187        if (count($pids) === 0) {
3210b6fad27Ssandos187            // Produce valid XHTML (ul needs a child)
3220b6fad27Ssandos187            $ret .= '<li><div class="li">' . $this->lang['js']['nopages'] . '</div></li>';
3230b6fad27Ssandos187        } else {
324bdf1ecf0SAnna Dabrowska            foreach (array_keys($pids) as $val) {
3250b6fad27Ssandos187                $ret .= '<li><div class="li">';
326db3ab356SAnna Dabrowska                $ret .= html_wikilink(":$val");
3270b6fad27Ssandos187                $ret .= '</div></li>';
3280b6fad27Ssandos187            }
3290b6fad27Ssandos187        }
3300b6fad27Ssandos187
3310b6fad27Ssandos187        $ret .= '</ul>';
3320b6fad27Ssandos187        $ret .= '</div>';
3330b6fad27Ssandos187        $ret .= '<div class="clearer"></div>';
3340b6fad27Ssandos187
3350b6fad27Ssandos187        return $ret;
3360b6fad27Ssandos187    }
3370b6fad27Ssandos187
3380b6fad27Ssandos187    /**
3395540f91dSAndreas Gohr     * Get the link to a search for the given tag
3405540f91dSAndreas Gohr     *
3415540f91dSAndreas Gohr     * @param string $tag search for this tag
3425540f91dSAndreas Gohr     * @param string $ns  limit search to this namespace
3430cfde7e9SMichael Große     *
3445540f91dSAndreas Gohr     * @return string
3455540f91dSAndreas Gohr     */
346df43a7beSAndreas Gohr    protected function linkToSearch($tag, $ns = '')
347df43a7beSAndreas Gohr    {
3485540f91dSAndreas Gohr        return '<a href="' . hsc($this->getTagSearchURL($tag, $ns)) . '">' . $tag . '</a>';
349f61105deSAdrian Lang    }
350f61105deSAdrian Lang
351fb1d0583SAndreas Gohr    /**
352fb1d0583SAndreas Gohr     * Display the Tags for the current page and prepare the tag editing form
3533496cc8aSAndreas Gohr     *
3543496cc8aSAndreas Gohr     * @param bool $print Should the HTML be printed or returned?
3550cfde7e9SMichael Große     *
3563496cc8aSAndreas Gohr     * @return string
357fb1d0583SAndreas Gohr     */
358df43a7beSAndreas Gohr    public function tpl_tags($print = true)
359df43a7beSAndreas Gohr    {
360f61105deSAdrian Lang        global $INFO;
361f61105deSAdrian Lang        global $lang;
3623bf0e2f1SMichael Große
363df43a7beSAndreas Gohr        $filter = ['pid' => $INFO['id']];
3643bf0e2f1SMichael Große        if ($this->getConf('singleusermode')) {
3653bf0e2f1SMichael Große            $filter['tagger'] = 'auto';
3663bf0e2f1SMichael Große        }
3673bf0e2f1SMichael Große
3683bf0e2f1SMichael Große        $tags = $this->findItems($filter, 'tag');
3693496cc8aSAndreas Gohr
370df43a7beSAndreas Gohr        $ret = '<div class="plugin_tagging_edit">';
371df43a7beSAndreas Gohr        $ret .= $this->html_cloud($tags, 'tag', [$this, 'linkToSearch'], true, true);
372f61105deSAdrian Lang
3732ace74f4SAndreas Gohr        if ($this->getUser() && $INFO['writable']) {
374f61105deSAdrian Lang            $lang['btn_tagging_edit'] = $lang['btn_secedit'];
375e5b42768SSzymon Olewniczak            $ret .= '<div id="tagging__edit_buttons_group">';
376df43a7beSAndreas Gohr            $ret .= html_btn('tagging_edit', $INFO['id'], '', []);
377dd52fd45SSzymon Olewniczak            if (auth_isadmin()) {
37826f61833SAnna Dabrowska                $ret .= '<label>'
37926f61833SAnna Dabrowska                    . $this->getLang('toggle admin mode')
38026f61833SAnna Dabrowska                    . '<input type="checkbox" id="tagging__edit_toggle_admin" /></label>';
381dd52fd45SSzymon Olewniczak            }
382e5b42768SSzymon Olewniczak            $ret .= '</div>';
383df43a7beSAndreas Gohr            $form = new Form();
3842819ffcdSSzymon Olewniczak            $form->id('tagging__edit');
3852819ffcdSSzymon Olewniczak            $form->setHiddenField('tagging[id]', $INFO['id']);
3862819ffcdSSzymon Olewniczak            $form->setHiddenField('call', 'plugin_tagging_save');
387df43a7beSAndreas Gohr            $tags = $this->findItems(['pid'    => $INFO['id'], 'tagger' => $this->getUser()], 'tag');
38826f61833SAnna Dabrowska            $form->addTextarea('tagging[tags]')
38926f61833SAnna Dabrowska                ->val(implode(', ', array_keys($tags)))
39026f61833SAnna Dabrowska                ->addClass('edit')
39126f61833SAnna Dabrowska                ->attr('rows', 4);
392cf52ec2dSSzymon Olewniczak            $form->addButton('', $lang['btn_save'])->id('tagging__edit_save');
393cf52ec2dSSzymon Olewniczak            $form->addButton('', $lang['btn_cancel'])->id('tagging__edit_cancel');
3942819ffcdSSzymon Olewniczak            $ret .= $form->toHTML();
395f61105deSAdrian Lang        }
3963496cc8aSAndreas Gohr        $ret .= '</div>';
3973496cc8aSAndreas Gohr
3980cfde7e9SMichael Große        if ($print) {
3990cfde7e9SMichael Große            echo $ret;
4000cfde7e9SMichael Große        }
401ca455b8eSMichael Große
4023496cc8aSAndreas Gohr        return $ret;
403f61105deSAdrian Lang    }
404872edc7cSRené Corinth
4058a1a3846SAndreas Gohr    /**
406a99b66c1SSzymon Olewniczak     * @param string $namespace empty for entire wiki
407a99b66c1SSzymon Olewniczak     *
40840b94b1aSAnna Dabrowska     * @param string $order_by
40940b94b1aSAnna Dabrowska     * @param bool $desc
41040b94b1aSAnna Dabrowska     * @param array $filters
4118a1a3846SAndreas Gohr     * @return array
4128a1a3846SAndreas Gohr     */
413df43a7beSAndreas Gohr    public function getAllTags($namespace = '', $order_by = 'tid', $desc = false, $filters = [])
414df43a7beSAndreas Gohr    {
415df43a7beSAndreas Gohr        $order_fields = ['pid', 'tid', 'taggers', 'ns', 'count'];
416f0084ee1SSzymon Olewniczak        if (!in_array($order_by, $order_fields)) {
417f0084ee1SSzymon Olewniczak            msg('cannot sort by ' . $order_by . ' field does not exists', -1);
418f0084ee1SSzymon Olewniczak            $order_by = 'tag';
419f0084ee1SSzymon Olewniczak        }
420872edc7cSRené Corinth
421df43a7beSAndreas Gohr        [$having, $params] = $this->getFilterSql($filters);
42240b94b1aSAnna Dabrowska
423a2246f69SAnna Dabrowska        $db = $this->getDB();
424872edc7cSRené Corinth
425f0084ee1SSzymon Olewniczak        $query = 'SELECT    "pid",
426ca455b8eSMichael Große                            CLEANTAG("tag") AS "tid",
427f0084ee1SSzymon Olewniczak                            GROUP_SORT(GROUP_CONCAT("tagger"), \', \') AS "taggers",
42840b94b1aSAnna Dabrowska                            GROUP_SORT(GROUP_CONCAT(GET_NS("pid")), \', \') AS "ns",
42989ed97adSAnna Dabrowska                            GROUP_SORT(GROUP_CONCAT("pid"), \', \') AS "pids",
430193a767dSSzymon Olewniczak                            COUNT(*) AS "count"
43157e45304SSzymon Olewniczak                        FROM "taggings"
4324227fca4SAnna Dabrowska                        WHERE "pid" GLOB ? AND GETACCESSLEVEL(pid) >= ' . AUTH_READ
4334227fca4SAnna Dabrowska                        . ' GROUP BY "tid"';
43440b94b1aSAnna Dabrowska        $query .= $having;
43540b94b1aSAnna Dabrowska        $query .=      'ORDER BY ' . $order_by;
436ca455b8eSMichael Große        if ($desc) {
437ca455b8eSMichael Große            $query .= ' DESC';
438ca455b8eSMichael Große        }
439cb469644SSzymon Olewniczak
44040b94b1aSAnna Dabrowska        array_unshift($params, $this->globNamespace($namespace));
44140b94b1aSAnna Dabrowska        $res = $db->query($query, $params);
442872edc7cSRené Corinth
4437e05e623SSzymon Olewniczak        return $db->res2arr($res);
444872edc7cSRené Corinth    }
445872edc7cSRené Corinth
4468a1a3846SAndreas Gohr    /**
44772431326SMichael Große     * Get all pages with tags and their tags
44872431326SMichael Große     *
449790ca788SAndreas Gohr     * @return array ['pid' => ['tag1','tag2','tag3']]
45072431326SMichael Große     */
451df43a7beSAndreas Gohr    public function getAllTagsByPage()
452df43a7beSAndreas Gohr    {
45372431326SMichael Große        $query = '
45472431326SMichael Große        SELECT pid, GROUP_CONCAT(tag) AS tags
45572431326SMichael Große        FROM taggings
45672431326SMichael Große        GROUP BY pid
45772431326SMichael Große        ';
45872431326SMichael Große        $db = $this->getDb();
45972431326SMichael Große        $res = $db->query($query);
460790ca788SAndreas Gohr        return array_map(
461df43a7beSAndreas Gohr            fn($i) => explode(',', $i),
462790ca788SAndreas Gohr            array_column($db->res2arr($res), 'tags', 'pid')
463790ca788SAndreas Gohr        );
46472431326SMichael Große    }
46572431326SMichael Große
46672431326SMichael Große    /**
4678a1a3846SAndreas Gohr     * Renames a tag
4688a1a3846SAndreas Gohr     *
4698a1a3846SAndreas Gohr     * @param string $formerTagName
4704227fca4SAnna Dabrowska     * @param string $newTagNames
4718a1a3846SAndreas Gohr     */
472df43a7beSAndreas Gohr    public function renameTag($formerTagName, $newTagNames)
473df43a7beSAndreas Gohr    {
474872edc7cSRené Corinth
4754227fca4SAnna Dabrowska        if (empty($formerTagName) || empty($newTagNames)) {
4768a1a3846SAndreas Gohr            msg($this->getLang("admin enter tag names"), -1);
4778a1a3846SAndreas Gohr            return;
478872edc7cSRené Corinth        }
479872edc7cSRené Corinth
480870d77ddSAnna Dabrowska        $keepFormerTag = false;
481870d77ddSAnna Dabrowska
4824227fca4SAnna Dabrowska        // enable splitting tags on rename
483df43a7beSAndreas Gohr        $newTagNames = array_map(fn($tag) => $this->cleanTag($tag), explode(',', $newTagNames));
4844227fca4SAnna Dabrowska
4854227fca4SAnna Dabrowska        $db = $this->getDB();
486872edc7cSRené Corinth
4874227fca4SAnna Dabrowska        // non-admins can rename only their own tags
4884227fca4SAnna Dabrowska        if (!auth_isadmin()) {
4894227fca4SAnna Dabrowska            $queryTagger = ' AND tagger = ?';
4904227fca4SAnna Dabrowska            $tagger = $this->getUser();
4914227fca4SAnna Dabrowska        } else {
4924227fca4SAnna Dabrowska            $queryTagger = '';
4934227fca4SAnna Dabrowska            $tagger = '';
4944227fca4SAnna Dabrowska        }
4954227fca4SAnna Dabrowska
4960ec63874SAnna Dabrowska        $insertQuery = 'INSERT INTO taggings ';
4970ec63874SAnna Dabrowska        $insertQuery .= 'SELECT pid, ?, tagger, lang FROM taggings';
4980ec63874SAnna Dabrowska        $where = ' WHERE CLEANTAG(tag) = ?';
4990ec63874SAnna Dabrowska        $where .= ' AND GETACCESSLEVEL(pid) >= ' . AUTH_EDIT;
5000ec63874SAnna Dabrowska        $where .= $queryTagger;
5010ec63874SAnna Dabrowska
5020ec63874SAnna Dabrowska        $db->query('BEGIN TRANSACTION');
5030ec63874SAnna Dabrowska
5040ec63874SAnna Dabrowska        // insert new tags first
5050ec63874SAnna Dabrowska        foreach ($newTagNames as $newTag) {
506870d77ddSAnna Dabrowska            if ($newTag === $this->cleanTag($formerTagName)) {
507870d77ddSAnna Dabrowska                $keepFormerTag = true;
508870d77ddSAnna Dabrowska                continue;
509870d77ddSAnna Dabrowska            }
510870d77ddSAnna Dabrowska            $params = [$newTag, $this->cleanTag($formerTagName)];
511df43a7beSAndreas Gohr            if ($tagger) $params[] = $tagger;
5120ec63874SAnna Dabrowska            $res = $db->query($insertQuery . $where, $params);
5130ec63874SAnna Dabrowska            if ($res === false) {
5140ec63874SAnna Dabrowska                $db->query('ROLLBACK TRANSACTION');
5150ec63874SAnna Dabrowska                return;
5164227fca4SAnna Dabrowska            }
5170ec63874SAnna Dabrowska            $db->res_close($res);
5180ec63874SAnna Dabrowska        }
5190ec63874SAnna Dabrowska
520870d77ddSAnna Dabrowska        // finally delete the renamed tags
521870d77ddSAnna Dabrowska        if (!$keepFormerTag) {
5220ec63874SAnna Dabrowska            $deleteQuery = 'DELETE FROM taggings';
5230ec63874SAnna Dabrowska            $params = [$this->cleanTag($formerTagName)];
524df43a7beSAndreas Gohr            if ($tagger) $params[] = $tagger;
5250ec63874SAnna Dabrowska            if ($db->query($deleteQuery . $where, $params) === false) {
5260ec63874SAnna Dabrowska                $db->query('ROLLBACK TRANSACTION');
5270ec63874SAnna Dabrowska                return;
5280ec63874SAnna Dabrowska            }
529870d77ddSAnna Dabrowska        }
5300ec63874SAnna Dabrowska
5310ec63874SAnna Dabrowska        $db->query('COMMIT TRANSACTION');
532872edc7cSRené Corinth
533fb1d0583SAndreas Gohr        msg($this->getLang("admin renamed"), 1);
534872edc7cSRené Corinth    }
535872edc7cSRené Corinth
5368f630140SSzymon Olewniczak    /**
537dd52fd45SSzymon Olewniczak     * Rename or delete a tag for all users
538dd52fd45SSzymon Olewniczak     *
539dd52fd45SSzymon Olewniczak     * @param string $pid
540dd52fd45SSzymon Olewniczak     * @param string $formerTagName
541dd52fd45SSzymon Olewniczak     * @param string $newTagName
542dd52fd45SSzymon Olewniczak     *
543dd52fd45SSzymon Olewniczak     * @return array
544dd52fd45SSzymon Olewniczak     */
545df43a7beSAndreas Gohr    public function modifyPageTag($pid, $formerTagName, $newTagName)
546df43a7beSAndreas Gohr    {
547dd52fd45SSzymon Olewniczak
548dd52fd45SSzymon Olewniczak        $db = $this->getDb();
549dd52fd45SSzymon Olewniczak
55026f61833SAnna Dabrowska        $res = $db->query(
55126f61833SAnna Dabrowska            'SELECT pid FROM taggings WHERE CLEANTAG(tag) = ? AND pid = ?',
55226f61833SAnna Dabrowska            $this->cleanTag($formerTagName),
55326f61833SAnna Dabrowska            $pid
55426f61833SAnna Dabrowska        );
555dd52fd45SSzymon Olewniczak        $check = $db->res2arr($res);
556dd52fd45SSzymon Olewniczak
557dd52fd45SSzymon Olewniczak        if (empty($check)) {
558df43a7beSAndreas Gohr            return [true, $this->getLang('admin tag does not exists')];
559dd52fd45SSzymon Olewniczak        }
560dd52fd45SSzymon Olewniczak
561dd52fd45SSzymon Olewniczak        if (empty($newTagName)) {
56226f61833SAnna Dabrowska            $res = $db->query(
56326f61833SAnna Dabrowska                'DELETE FROM taggings WHERE pid = ? AND CLEANTAG(tag) = ?',
56426f61833SAnna Dabrowska                $pid,
56526f61833SAnna Dabrowska                $this->cleanTag($formerTagName)
56626f61833SAnna Dabrowska            );
567dd52fd45SSzymon Olewniczak        } else {
56826f61833SAnna Dabrowska            $res = $db->query(
56926f61833SAnna Dabrowska                'UPDATE taggings SET tag = ? WHERE pid = ? AND CLEANTAG(tag) = ?',
57026f61833SAnna Dabrowska                $newTagName,
57126f61833SAnna Dabrowska                $pid,
57226f61833SAnna Dabrowska                $this->cleanTag($formerTagName)
57326f61833SAnna Dabrowska            );
574dd52fd45SSzymon Olewniczak        }
575dd52fd45SSzymon Olewniczak        $db->res2arr($res);
576dd52fd45SSzymon Olewniczak
577df43a7beSAndreas Gohr        return [false, $this->getLang('admin renamed')];
578dd52fd45SSzymon Olewniczak    }
579dd52fd45SSzymon Olewniczak
580dd52fd45SSzymon Olewniczak    /**
5818f630140SSzymon Olewniczak     * Deletes a tag
5828f630140SSzymon Olewniczak     *
5838f630140SSzymon Olewniczak     * @param array  $tags
58431396860SSzymon Olewniczak     * @param string $namespace current namespace context as in getAllTags()
5858f630140SSzymon Olewniczak     */
586df43a7beSAndreas Gohr    public function deleteTags($tags, $namespace = '')
587df43a7beSAndreas Gohr    {
588ca455b8eSMichael Große        if (empty($tags)) {
589ca455b8eSMichael Große            return;
590ca455b8eSMichael Große        }
5918f630140SSzymon Olewniczak
59231396860SSzymon Olewniczak        $namespace = cleanId($namespace);
59331396860SSzymon Olewniczak
5941f5991a7SMichael Große        $db = $this->getDB();
5958f630140SSzymon Olewniczak
596de379874SAnna Dabrowska        $queryBody = 'FROM taggings WHERE pid GLOB ? AND (' .
59731396860SSzymon Olewniczak            implode(' OR ', array_fill(0, count($tags), 'CLEANTAG(tag) = ?')) . ')';
598df43a7beSAndreas Gohr        $args = array_map([$this, 'cleanTag'], $tags);
59931396860SSzymon Olewniczak        array_unshift($args, $this->globNamespace($namespace));
6008f630140SSzymon Olewniczak
6014227fca4SAnna Dabrowska        // non-admins can delete only their own tags
6024227fca4SAnna Dabrowska        if (!auth_isadmin()) {
6034227fca4SAnna Dabrowska            $queryBody .= ' AND tagger = ?';
604df43a7beSAndreas Gohr            $args[] = $this->getUser();
6054227fca4SAnna Dabrowska        }
606ca455b8eSMichael Große
6071f5991a7SMichael Große        $affectedPagesQuery = 'SELECT DISTINCT pid ' . $queryBody;
6081f5991a7SMichael Große        $resAffectedPages = $db->query($affectedPagesQuery, $args);
6091f5991a7SMichael Große        $numAffectedPages = count($resAffectedPages->fetchAll());
6101f5991a7SMichael Große
6111f5991a7SMichael Große        $deleteQuery = 'DELETE ' . $queryBody;
6121f5991a7SMichael Große        $db->query($deleteQuery, $args);
6131f5991a7SMichael Große
6141f5991a7SMichael Große        msg(sprintf($this->getLang("admin deleted"), count($tags), $numAffectedPages), 1);
6158f630140SSzymon Olewniczak    }
616cd08599fSAnna Dabrowska
617cd08599fSAnna Dabrowska    /**
618ec4796e4SAnna Dabrowska     * Delete taggings of nonexistent pages
619ec4796e4SAnna Dabrowska     */
620ec4796e4SAnna Dabrowska    public function deleteInvalidTaggings()
621ec4796e4SAnna Dabrowska    {
622ec4796e4SAnna Dabrowska        $db = $this->getDB();
623ec4796e4SAnna Dabrowska        $query = 'DELETE    FROM "taggings"
6241815f49fSAnna Dabrowska                            WHERE NOT PAGEEXISTS(pid)
625ec4796e4SAnna Dabrowska                 ';
626ec4796e4SAnna Dabrowska        $res = $db->query($query);
627ec4796e4SAnna Dabrowska        $db->res_close($res);
628ec4796e4SAnna Dabrowska    }
629ec4796e4SAnna Dabrowska
630ec4796e4SAnna Dabrowska    /**
631cd08599fSAnna Dabrowska     * Updates tags with a new page name
632cd08599fSAnna Dabrowska     *
633cd08599fSAnna Dabrowska     * @param string $oldName
634cd08599fSAnna Dabrowska     * @param string $newName
635cd08599fSAnna Dabrowska     */
636df43a7beSAndreas Gohr    public function renamePage($oldName, $newName)
637df43a7beSAndreas Gohr    {
638f6568bcbSAnna Dabrowska        $db = $this->getDB();
639cd08599fSAnna Dabrowska        $db->query('UPDATE taggings SET pid = ? WHERE pid = ?', $newName, $oldName);
640cd08599fSAnna Dabrowska    }
641972f6adfSAnna Dabrowska
642972f6adfSAnna Dabrowska    /**
6431b4b4fa9SAnna Dabrowska     * Extracts tags from search query
6441b4b4fa9SAnna Dabrowska     *
6451b4b4fa9SAnna Dabrowska     * @param array $parsedQuery
6461b4b4fa9SAnna Dabrowska     * @return array
6471b4b4fa9SAnna Dabrowska     */
648739c5360SAnna Dabrowska    public function extractFromQuery($parsedQuery)
6491b4b4fa9SAnna Dabrowska    {
6501b4b4fa9SAnna Dabrowska        $tags = [];
6511b4b4fa9SAnna Dabrowska        if (isset($parsedQuery['phrases'][0])) {
6521b4b4fa9SAnna Dabrowska            $tags = $parsedQuery['phrases'];
6531b4b4fa9SAnna Dabrowska        } elseif (isset($parsedQuery['and'][0])) {
6541b4b4fa9SAnna Dabrowska            $tags = $parsedQuery['and'];
6551b4b4fa9SAnna Dabrowska        } elseif (isset($parsedQuery['tag'])) {
6561b4b4fa9SAnna Dabrowska            // handle autocomplete call
6571b4b4fa9SAnna Dabrowska            $tags[] = $parsedQuery['tag'];
6581b4b4fa9SAnna Dabrowska        }
6591b4b4fa9SAnna Dabrowska        return $tags;
6601b4b4fa9SAnna Dabrowska    }
6611b4b4fa9SAnna Dabrowska
6621b4b4fa9SAnna Dabrowska    /**
6634a7da0a5SAnna Dabrowska     * Search for tagged pages
664972f6adfSAnna Dabrowska     *
665739c5360SAnna Dabrowska     * @param array $tagFiler
6664a7da0a5SAnna Dabrowska     * @return array
667972f6adfSAnna Dabrowska     */
668739c5360SAnna Dabrowska    public function searchPages($tagFiler)
669972f6adfSAnna Dabrowska    {
6701b4b4fa9SAnna Dabrowska        global $INPUT;
6711b4b4fa9SAnna Dabrowska        global $QUERY;
672df43a7beSAndreas Gohr        $parsedQuery = ft_queryParser(new Indexer(), $QUERY);
673972f6adfSAnna Dabrowska
674df43a7beSAndreas Gohr        $queryBuilder = new helper_plugin_tagging_querybuilder();
675972f6adfSAnna Dabrowska
6764a7da0a5SAnna Dabrowska        $queryBuilder->setField('pid');
677cbe7b4baSAnna Dabrowska        $queryBuilder->setTags($tagFiler);
678f014dfc9SAnna Dabrowska        $queryBuilder->setLogicalAnd($INPUT->str('tagging-logic') === 'and');
6794a7da0a5SAnna Dabrowska        if (isset($parsedQuery['ns'])) $queryBuilder->includeNS($parsedQuery['ns']);
6804a7da0a5SAnna Dabrowska        if (isset($parsedQuery['notns'])) $queryBuilder->excludeNS($parsedQuery['notns']);
6814a7da0a5SAnna Dabrowska        if (isset($parsedQuery['tagger'])) $queryBuilder->setTagger($parsedQuery['tagger']);
6824a7da0a5SAnna Dabrowska        if (isset($parsedQuery['pid'])) $queryBuilder->setPid($parsedQuery['pid']);
683972f6adfSAnna Dabrowska
6844a7da0a5SAnna Dabrowska        return $this->queryDb($queryBuilder->getPages());
685972f6adfSAnna Dabrowska    }
686972f6adfSAnna Dabrowska
6871b4b4fa9SAnna Dabrowska    /**
6884227fca4SAnna Dabrowska     * Syntax to allow users to manage tags on regular pages, respects ACLs
6894227fca4SAnna Dabrowska     * @param string $ns
6904227fca4SAnna Dabrowska     * @return string
6914227fca4SAnna Dabrowska     */
6924227fca4SAnna Dabrowska    public function manageTags($ns)
6934227fca4SAnna Dabrowska    {
6944227fca4SAnna Dabrowska        global $INPUT;
6954227fca4SAnna Dabrowska
696f6568bcbSAnna Dabrowska        $this->setDefaultSort();
6974227fca4SAnna Dabrowska
6984227fca4SAnna Dabrowska        // initially set namespace filter to what is defined in syntax
6994227fca4SAnna Dabrowska        if ($ns && !$INPUT->has('tagging__filters')) {
7004227fca4SAnna Dabrowska            $INPUT->set('tagging__filters', ['ns' => $ns]);
7014227fca4SAnna Dabrowska        }
7024227fca4SAnna Dabrowska
7034227fca4SAnna Dabrowska        return $this->html_table();
7044227fca4SAnna Dabrowska    }
7054227fca4SAnna Dabrowska
7064227fca4SAnna Dabrowska    /**
707a2246f69SAnna Dabrowska     * HTML list of tagged pages
708a2246f69SAnna Dabrowska     *
709a2246f69SAnna Dabrowska     * @param string $tid
710a2246f69SAnna Dabrowska     * @return string
711a2246f69SAnna Dabrowska     */
712a2246f69SAnna Dabrowska    public function getPagesHtml($tid)
713a2246f69SAnna Dabrowska    {
714a2246f69SAnna Dabrowska        $html = '';
715a2246f69SAnna Dabrowska
716a2246f69SAnna Dabrowska        $db = $this->getDB();
71731bddc5fSAnna Dabrowska        $sql = 'SELECT pid from taggings where CLEANTAG(tag) = CLEANTAG(?)';
718a2246f69SAnna Dabrowska        $res =  $db->query($sql, $tid);
719a2246f69SAnna Dabrowska        $pages = $db->res2arr($res);
720a2246f69SAnna Dabrowska
721a2246f69SAnna Dabrowska        if ($pages) {
722a2246f69SAnna Dabrowska            $html .= '<ul>';
723a2246f69SAnna Dabrowska            foreach ($pages as $page) {
724a2246f69SAnna Dabrowska                $pid = $page['pid'];
725a2246f69SAnna Dabrowska                $html .= '<li><a href="' . wl($pid) . '" target="_blank">' . $pid . '</li>';
726a2246f69SAnna Dabrowska            }
727a2246f69SAnna Dabrowska            $html .= '</ul>';
728a2246f69SAnna Dabrowska        }
729a2246f69SAnna Dabrowska
730a2246f69SAnna Dabrowska        return $html;
731a2246f69SAnna Dabrowska    }
732a2246f69SAnna Dabrowska
733a2246f69SAnna Dabrowska    /**
73489ed97adSAnna Dabrowska     * Display tag management table
73589ed97adSAnna Dabrowska     */
736df43a7beSAndreas Gohr    public function html_table()
737df43a7beSAndreas Gohr    {
73889ed97adSAnna Dabrowska        global $ID, $INPUT;
73989ed97adSAnna Dabrowska
740df43a7beSAndreas Gohr        $headers = [
741df43a7beSAndreas Gohr            ['value' => $this->getLang('admin tag'), 'sort_by' => 'tid'],
742df43a7beSAndreas Gohr            ['value' => $this->getLang('admin occurrence'), 'sort_by' => 'count']
743df43a7beSAndreas Gohr        ];
74426c4179dSAnna Dabrowska
74526c4179dSAnna Dabrowska        if (!$this->conf['hidens']) {
746df43a7beSAndreas Gohr            $headers[] = ['value' => $this->getLang('admin namespaces'), 'sort_by' => 'ns'];
74726c4179dSAnna Dabrowska        }
748df43a7beSAndreas Gohr        $headers[] = ['value' => $this->getLang('admin taggers'), 'sort_by' => 'taggers'];
749df43a7beSAndreas Gohr        $headers[] = ['value' => $this->getLang('admin actions'), 'sort_by' => false];
75089ed97adSAnna Dabrowska
751f6568bcbSAnna Dabrowska        $sort = explode(',', $this->getParam('sort'));
75289ed97adSAnna Dabrowska        $order_by = $sort[0];
75389ed97adSAnna Dabrowska        $desc = false;
75489ed97adSAnna Dabrowska        if (isset($sort[1]) && $sort[1] === 'desc') {
75589ed97adSAnna Dabrowska            $desc = true;
75689ed97adSAnna Dabrowska        }
75789ed97adSAnna Dabrowska        $filters = $INPUT->arr('tagging__filters');
75889ed97adSAnna Dabrowska
75989ed97adSAnna Dabrowska        $tags = $this->getAllTags($INPUT->str('filter'), $order_by, $desc, $filters);
76089ed97adSAnna Dabrowska
761df43a7beSAndreas Gohr        $form = new Form();
762a2246f69SAnna Dabrowska        // required in admin mode
76389ed97adSAnna Dabrowska        $form->setHiddenField('page', 'tagging');
76489ed97adSAnna Dabrowska        $form->setHiddenField('id', $ID);
765f6568bcbSAnna Dabrowska        $form->setHiddenField('[tagging]sort', $this->getParam('sort'));
76689ed97adSAnna Dabrowska
76789ed97adSAnna Dabrowska        /**
76889ed97adSAnna Dabrowska         * Actions dialog
76989ed97adSAnna Dabrowska         */
77089ed97adSAnna Dabrowska        $form->addTagOpen('div')->id('tagging__action-dialog')->attr('style', "display:none;");
77189ed97adSAnna Dabrowska        $form->addTagClose('div');
77289ed97adSAnna Dabrowska
77389ed97adSAnna Dabrowska        /**
77489ed97adSAnna Dabrowska         * Tag pages dialog
77589ed97adSAnna Dabrowska         */
77689ed97adSAnna Dabrowska        $form->addTagOpen('div')->id('tagging__taggedpages-dialog')->attr('style', "display:none;");
77789ed97adSAnna Dabrowska        $form->addTagClose('div');
77889ed97adSAnna Dabrowska
77989ed97adSAnna Dabrowska        /**
78089ed97adSAnna Dabrowska         * Tag management table
78189ed97adSAnna Dabrowska         */
78289ed97adSAnna Dabrowska        $form->addTagOpen('table')->addClass('inline plugin_tagging');
78389ed97adSAnna Dabrowska
784df43a7beSAndreas Gohr        $nscol = $this->conf['hidens'] ? '' : '<col class="wide-col" />';
785a305ec34SAnna Dabrowska        $form->addHTML(
786a305ec34SAnna Dabrowska            '<colgroup>
787df43a7beSAndreas Gohr                <col />
788df43a7beSAndreas Gohr                <col class="narrow-col" />'
78926c4179dSAnna Dabrowska                . $nscol .
790df43a7beSAndreas Gohr                '<col />
791df43a7beSAndreas Gohr                <col class="narrow-col" />
792a305ec34SAnna Dabrowska            </colgroup>'
793a305ec34SAnna Dabrowska        );
794a305ec34SAnna Dabrowska
79589ed97adSAnna Dabrowska        /**
79689ed97adSAnna Dabrowska         * Table headers
79789ed97adSAnna Dabrowska         */
79889ed97adSAnna Dabrowska        $form->addTagOpen('tr');
79989ed97adSAnna Dabrowska        foreach ($headers as $header) {
80089ed97adSAnna Dabrowska            $form->addTagOpen('th');
80189ed97adSAnna Dabrowska            if ($header['sort_by'] !== false) {
80289ed97adSAnna Dabrowska                $param = $header['sort_by'];
80389ed97adSAnna Dabrowska                $icon = 'arrow-both';
80489ed97adSAnna Dabrowska                $title = $this->getLang('admin sort ascending');
80589ed97adSAnna Dabrowska                if ($header['sort_by'] === $order_by) {
80689ed97adSAnna Dabrowska                    if ($desc === false) {
80789ed97adSAnna Dabrowska                        $icon = 'arrow-up';
80889ed97adSAnna Dabrowska                        $title = $this->getLang('admin sort descending');
80989ed97adSAnna Dabrowska                        $param .= ',desc';
81089ed97adSAnna Dabrowska                    } else {
81189ed97adSAnna Dabrowska                        $icon = 'arrow-down';
81289ed97adSAnna Dabrowska                    }
81389ed97adSAnna Dabrowska                }
81426f61833SAnna Dabrowska                $form->addButtonHTML(
815f6568bcbSAnna Dabrowska                    "tagging[sort]",
816df43a7beSAndreas Gohr                    $header['value'] . ' ' . inlineSVG(__DIR__ . "/images/$icon.svg")
817df43a7beSAndreas Gohr                )
81889ed97adSAnna Dabrowska                    ->addClass('plugin_tagging sort_button')
819f6568bcbSAnna Dabrowska                    ->attr('title', $title)
820f6568bcbSAnna Dabrowska                    ->val($param);
82189ed97adSAnna Dabrowska            } else {
82289ed97adSAnna Dabrowska                $form->addHTML($header['value']);
82389ed97adSAnna Dabrowska            }
82489ed97adSAnna Dabrowska            $form->addTagClose('th');
82589ed97adSAnna Dabrowska        }
82689ed97adSAnna Dabrowska        $form->addTagClose('tr');
82789ed97adSAnna Dabrowska
82889ed97adSAnna Dabrowska        /**
82989ed97adSAnna Dabrowska         * Table filters for all sortable columns
83089ed97adSAnna Dabrowska         */
83189ed97adSAnna Dabrowska        $form->addTagOpen('tr');
83289ed97adSAnna Dabrowska        foreach ($headers as $header) {
83389ed97adSAnna Dabrowska            $form->addTagOpen('th');
83489ed97adSAnna Dabrowska            if ($header['sort_by'] !== false) {
83589ed97adSAnna Dabrowska                $field = $header['sort_by'];
8367c96ae87SAnna Dabrowska                $input = $form->addTextInput("tagging__filters[$field]");
837a305ec34SAnna Dabrowska                $input->addClass('full-col');
83889ed97adSAnna Dabrowska            }
83989ed97adSAnna Dabrowska            $form->addTagClose('th');
84089ed97adSAnna Dabrowska        }
84189ed97adSAnna Dabrowska        $form->addTagClose('tr');
84289ed97adSAnna Dabrowska
84389ed97adSAnna Dabrowska
84489ed97adSAnna Dabrowska        foreach ($tags as $taginfo) {
84589ed97adSAnna Dabrowska            $tagname = $taginfo['tid'];
84689ed97adSAnna Dabrowska            $taggers = $taginfo['taggers'];
84789ed97adSAnna Dabrowska            $ns = $taginfo['ns'];
84889ed97adSAnna Dabrowska            $pids = explode(',', $taginfo['pids']);
84989ed97adSAnna Dabrowska
85089ed97adSAnna Dabrowska            $form->addTagOpen('tr');
85126f61833SAnna Dabrowska            $form->addHTML('<td>');
852a2246f69SAnna Dabrowska            $form->addHTML('<a class="tagslist" href="#" data-tid="' . $taginfo['tid'] . '">');
85326f61833SAnna Dabrowska            $form->addHTML(hsc($tagname) . '</a>');
85426f61833SAnna Dabrowska            $form->addHTML('</td>');
85589ed97adSAnna Dabrowska            $form->addHTML('<td>' . $taginfo['count'] . '</td>');
85626c4179dSAnna Dabrowska            if (!$this->conf['hidens']) {
85789ed97adSAnna Dabrowska                $form->addHTML('<td>' . hsc($ns) . '</td>');
85826c4179dSAnna Dabrowska            }
85989ed97adSAnna Dabrowska            $form->addHTML('<td>' . hsc($taggers) . '</td>');
86089ed97adSAnna Dabrowska
86189ed97adSAnna Dabrowska            /**
86289ed97adSAnna Dabrowska             * action buttons
86389ed97adSAnna Dabrowska             */
86489ed97adSAnna Dabrowska            $form->addHTML('<td>');
86589ed97adSAnna Dabrowska
86689ed97adSAnna Dabrowska            // check ACLs
86789ed97adSAnna Dabrowska            $userEdit = false;
86889ed97adSAnna Dabrowska            foreach ($pids as $pid) {
869749c70e5SAndreas Gohr                if (auth_quickaclcheck($pid) >= AUTH_EDIT) {
87089ed97adSAnna Dabrowska                    $userEdit = true;
871df43a7beSAndreas Gohr                    break;
87289ed97adSAnna Dabrowska                }
87389ed97adSAnna Dabrowska            }
87489ed97adSAnna Dabrowska
87589ed97adSAnna Dabrowska            if ($userEdit) {
87626f61833SAnna Dabrowska                $form->addButtonHTML(
877f6568bcbSAnna Dabrowska                    'tagging[actions][rename][' . $taginfo['tid'] . ']',
878df43a7beSAndreas Gohr                    inlineSVG(__DIR__ . '/images/edit.svg')
879df43a7beSAndreas Gohr                )
88026f61833SAnna Dabrowska                    ->addClass('plugin_tagging action_button')
88126f61833SAnna Dabrowska                    ->attr('data-action', 'rename')
88226f61833SAnna Dabrowska                    ->attr('data-tid', $taginfo['tid']);
88326f61833SAnna Dabrowska                $form->addButtonHTML(
884f6568bcbSAnna Dabrowska                    'tagging[actions][delete][' . $taginfo['tid'] . ']',
885df43a7beSAndreas Gohr                    inlineSVG(__DIR__ . '/images/delete.svg')
886df43a7beSAndreas Gohr                )
88726f61833SAnna Dabrowska                    ->addClass('plugin_tagging action_button')
88826f61833SAnna Dabrowska                    ->attr('data-action', 'delete')
88926f61833SAnna Dabrowska                    ->attr('data-tid', $taginfo['tid']);
89089ed97adSAnna Dabrowska            }
89189ed97adSAnna Dabrowska
89289ed97adSAnna Dabrowska            $form->addHTML('</td>');
89389ed97adSAnna Dabrowska            $form->addTagClose('tr');
89489ed97adSAnna Dabrowska        }
89589ed97adSAnna Dabrowska
89689ed97adSAnna Dabrowska        $form->addTagClose('table');
8970b033188SAnna Dabrowska        return '<div class="table">' . $form->toHTML() . '</div>';
89889ed97adSAnna Dabrowska    }
89989ed97adSAnna Dabrowska
90089ed97adSAnna Dabrowska    /**
901ec4796e4SAnna Dabrowska     * Display tag cleaner
902ec4796e4SAnna Dabrowska     *
903ec4796e4SAnna Dabrowska     * @return string
904ec4796e4SAnna Dabrowska     */
905ec4796e4SAnna Dabrowska    public function html_clean()
906ec4796e4SAnna Dabrowska    {
907ec4796e4SAnna Dabrowska        $invalid = $this->getInvalidTaggings();
908ec4796e4SAnna Dabrowska
909ec4796e4SAnna Dabrowska        if (!$invalid) {
910ec4796e4SAnna Dabrowska            return '<p><strong>' . $this->getLang('admin no invalid') . '</strong></p>';
911ec4796e4SAnna Dabrowska        }
912ec4796e4SAnna Dabrowska
913ec4796e4SAnna Dabrowska        $form = new Form();
914ec4796e4SAnna Dabrowska        $form->setHiddenField('do', 'admin');
915ec4796e4SAnna Dabrowska        $form->setHiddenField('page', $this->getPluginName());
916ec4796e4SAnna Dabrowska        $form->addButton('cmd[clean]', $this->getLang('admin clean'));
917ec4796e4SAnna Dabrowska
918ec4796e4SAnna Dabrowska        $html = $form->toHTML();
919ec4796e4SAnna Dabrowska
920ec4796e4SAnna Dabrowska        $html .= '<div class="table"><table class="inline plugin_tagging">';
921ec4796e4SAnna Dabrowska        $html .= '<thead><tr><th>' .
922ec4796e4SAnna Dabrowska            $this->getLang('admin nonexistent page') .
923ec4796e4SAnna Dabrowska            '</th><th>' .
924ec4796e4SAnna Dabrowska            $this->getLang('admin tags') .
925ec4796e4SAnna Dabrowska            '</th></tr></thead><tbody>';
926ec4796e4SAnna Dabrowska
927ec4796e4SAnna Dabrowska        foreach ($invalid as $row) {
928ec4796e4SAnna Dabrowska            $html .= '<tr><td>' . $row['pid'] . '</td><td>' . $row['tags'] . '</td></tr>';
929ec4796e4SAnna Dabrowska        }
930ec4796e4SAnna Dabrowska
931ec4796e4SAnna Dabrowska        $html .= '</tbody></table></div>';
932ec4796e4SAnna Dabrowska
933ec4796e4SAnna Dabrowska        return $html;
934ec4796e4SAnna Dabrowska    }
935ec4796e4SAnna Dabrowska
936ec4796e4SAnna Dabrowska    /**
937f6568bcbSAnna Dabrowska     * Returns all tagging parameters from the query string
938f6568bcbSAnna Dabrowska     *
939f6568bcbSAnna Dabrowska     * @return mixed
940f6568bcbSAnna Dabrowska     */
941f6568bcbSAnna Dabrowska    public function getParams()
942f6568bcbSAnna Dabrowska    {
943f6568bcbSAnna Dabrowska        global $INPUT;
944f6568bcbSAnna Dabrowska        return $INPUT->param('tagging', []);
945f6568bcbSAnna Dabrowska    }
946f6568bcbSAnna Dabrowska
947f6568bcbSAnna Dabrowska    /**
948f6568bcbSAnna Dabrowska     * Get a tagging parameter, empty string if not set
949f6568bcbSAnna Dabrowska     *
950f6568bcbSAnna Dabrowska     * @param string $name
951df43a7beSAndreas Gohr     * @return string
952f6568bcbSAnna Dabrowska     */
953f6568bcbSAnna Dabrowska    public function getParam($name)
954f6568bcbSAnna Dabrowska    {
955f6568bcbSAnna Dabrowska        $params = $this->getParams();
956f6568bcbSAnna Dabrowska        if ($params) {
957f6568bcbSAnna Dabrowska            return $params[$name] ?: '';
958f6568bcbSAnna Dabrowska        }
959df43a7beSAndreas Gohr        return '';
960f6568bcbSAnna Dabrowska    }
961f6568bcbSAnna Dabrowska
962f6568bcbSAnna Dabrowska    /**
963f6568bcbSAnna Dabrowska     * Sets a tagging parameter
964f6568bcbSAnna Dabrowska     *
965f6568bcbSAnna Dabrowska     * @param string $name
966f6568bcbSAnna Dabrowska     * @param string|array $value
967f6568bcbSAnna Dabrowska     */
968f6568bcbSAnna Dabrowska    public function setParam($name, $value)
969f6568bcbSAnna Dabrowska    {
970f6568bcbSAnna Dabrowska        global $INPUT;
971f6568bcbSAnna Dabrowska        $params = $this->getParams();
972f6568bcbSAnna Dabrowska        $params = array_merge($params, [$name => $value]);
973df43a7beSAndreas Gohr
974f6568bcbSAnna Dabrowska        $INPUT->set('tagging', $params);
975f6568bcbSAnna Dabrowska    }
976f6568bcbSAnna Dabrowska
977f6568bcbSAnna Dabrowska    /**
978f6568bcbSAnna Dabrowska     * Default sorting by tag id
979f6568bcbSAnna Dabrowska     */
980f6568bcbSAnna Dabrowska    public function setDefaultSort()
981f6568bcbSAnna Dabrowska    {
982f6568bcbSAnna Dabrowska        if (!$this->getParam('sort')) {
983f6568bcbSAnna Dabrowska            $this->setParam('sort', 'tid');
984f6568bcbSAnna Dabrowska        }
985f6568bcbSAnna Dabrowska    }
986f6568bcbSAnna Dabrowska
987f6568bcbSAnna Dabrowska    /**
9884a7da0a5SAnna Dabrowska     * Executes the query and returns the results as array
9891b4b4fa9SAnna Dabrowska     *
99099122157SAnna Dabrowska     * @param array $query
9911b4b4fa9SAnna Dabrowska     * @return array
9921b4b4fa9SAnna Dabrowska     */
9934a7da0a5SAnna Dabrowska    protected function queryDb($query)
9941b4b4fa9SAnna Dabrowska    {
9954a7da0a5SAnna Dabrowska        $db = $this->getDB();
9964a7da0a5SAnna Dabrowska        if (!$db) {
9974a7da0a5SAnna Dabrowska            return [];
998972f6adfSAnna Dabrowska        }
9994a7da0a5SAnna Dabrowska
100099122157SAnna Dabrowska        $res = $db->query($query[0], $query[1]);
10014a7da0a5SAnna Dabrowska        $res = $db->res2arr($res);
10024a7da0a5SAnna Dabrowska
10034a7da0a5SAnna Dabrowska        $ret = [];
10044a7da0a5SAnna Dabrowska        foreach ($res as $row) {
10054a7da0a5SAnna Dabrowska            $ret[$row['item']] = $row['cnt'];
10064a7da0a5SAnna Dabrowska        }
10074a7da0a5SAnna Dabrowska        return $ret;
1008972f6adfSAnna Dabrowska    }
100940b94b1aSAnna Dabrowska
101040b94b1aSAnna Dabrowska    /**
101140b94b1aSAnna Dabrowska     * Construct the HAVING part of the search query
101240b94b1aSAnna Dabrowska     *
101340b94b1aSAnna Dabrowska     * @param array $filters
101440b94b1aSAnna Dabrowska     * @return array
101540b94b1aSAnna Dabrowska     */
101640b94b1aSAnna Dabrowska    protected function getFilterSql($filters)
101740b94b1aSAnna Dabrowska    {
101840b94b1aSAnna Dabrowska        $having = '';
101940b94b1aSAnna Dabrowska        $parts = [];
102040b94b1aSAnna Dabrowska        $params = [];
102140b94b1aSAnna Dabrowska        $filters = array_filter($filters);
1022df43a7beSAndreas Gohr        if ($filters !== []) {
102340b94b1aSAnna Dabrowska            $having = ' HAVING ';
102440b94b1aSAnna Dabrowska            foreach ($filters as $filter => $value) {
102540b94b1aSAnna Dabrowska                $parts[] = " $filter LIKE ? ";
102640b94b1aSAnna Dabrowska                $params[] = "%$value%";
102740b94b1aSAnna Dabrowska            }
102840b94b1aSAnna Dabrowska            $having .= implode(' AND ', $parts);
102940b94b1aSAnna Dabrowska        }
103040b94b1aSAnna Dabrowska        return [$having, $params];
103140b94b1aSAnna Dabrowska    }
1032ec4796e4SAnna Dabrowska
1033ec4796e4SAnna Dabrowska    /**
1034ec4796e4SAnna Dabrowska     * Returns taggings of nonexistent pages
1035ec4796e4SAnna Dabrowska     *
1036ec4796e4SAnna Dabrowska     * @return array
1037ec4796e4SAnna Dabrowska     */
1038ec4796e4SAnna Dabrowska    protected function getInvalidTaggings()
1039ec4796e4SAnna Dabrowska    {
1040ec4796e4SAnna Dabrowska        $db = $this->getDB();
1041ec4796e4SAnna Dabrowska        $query = 'SELECT    "pid",
1042ec4796e4SAnna Dabrowska                            GROUP_CONCAT(CLEANTAG("tag")) AS "tags"
1043ec4796e4SAnna Dabrowska                            FROM "taggings"
1044f24ac95eSAnna Dabrowska                            WHERE NOT PAGEEXISTS(pid)
1045ec4796e4SAnna Dabrowska                            GROUP BY pid
1046ec4796e4SAnna Dabrowska                 ';
1047ec4796e4SAnna Dabrowska        $res = $db->query($query);
1048ec4796e4SAnna Dabrowska        return $db->res2arr($res);
1049ec4796e4SAnna Dabrowska    }
1050cd08599fSAnna Dabrowska}
1051