xref: /plugin/tagging/helper.php (revision e4443e5c8c37d7c4cd26d5afe99436bd6465ec43)
1f61105deSAdrian Lang<?php
2ec4796e4SAnna Dabrowska
3ec4796e4SAnna Dabrowskause dokuwiki\Form\Form;
4ec4796e4SAnna Dabrowska
5aa627deeSAndreas Gohr/**
6aa627deeSAndreas Gohr * Tagging Plugin (hlper component)
7aa627deeSAndreas Gohr *
8aa627deeSAndreas Gohr * @license GPL 2
9aa627deeSAndreas Gohr */
10e4543b6dSAdrian Langclass helper_plugin_tagging extends DokuWiki_Plugin {
11f61105deSAdrian Lang
12289f50bdSAndreas Gohr    /**
13b12334e1SAndreas Gohr     * Gives access to the database
14b12334e1SAndreas Gohr     *
15b12334e1SAndreas Gohr     * Initializes the SQLite helper and register the CLEANTAG function
16b12334e1SAndreas Gohr     *
17b12334e1SAndreas Gohr     * @return helper_plugin_sqlite|bool false if initialization fails
18289f50bdSAndreas Gohr     */
19289f50bdSAndreas Gohr    public function getDB() {
20302a38efSAndreas Gohr        static $db = null;
21aa627deeSAndreas Gohr        if ($db !== null) {
22f61105deSAdrian Lang            return $db;
23f61105deSAdrian Lang        }
24f61105deSAdrian Lang
25302a38efSAndreas Gohr        /** @var helper_plugin_sqlite $db */
26f61105deSAdrian Lang        $db = plugin_load('helper', 'sqlite');
27aa627deeSAndreas Gohr        if ($db === null) {
28f61105deSAdrian Lang            msg('The tagging plugin needs the sqlite plugin', -1);
29ca455b8eSMichael Große
30f61105deSAdrian Lang            return false;
31f61105deSAdrian Lang        }
32aa627deeSAndreas Gohr        $db->init('tagging', __DIR__ . '/db/');
33302a38efSAndreas Gohr        $db->create_function('CLEANTAG', array($this, 'cleanTag'), 1);
347e05e623SSzymon Olewniczak        $db->create_function('GROUP_SORT',
357e05e623SSzymon Olewniczak            function ($group, $newDelimiter) {
3640b94b1aSAnna Dabrowska                $ex = array_filter(explode(',', $group));
377e05e623SSzymon Olewniczak                sort($ex);
38ca455b8eSMichael Große
397e05e623SSzymon Olewniczak                return implode($newDelimiter, $ex);
407e05e623SSzymon Olewniczak            }, 2);
4140b94b1aSAnna Dabrowska        $db->create_function('GET_NS', 'getNS', 1);
42ca455b8eSMichael Große
43f61105deSAdrian Lang        return $db;
44f61105deSAdrian Lang    }
45f61105deSAdrian Lang
46302a38efSAndreas Gohr    /**
472ace74f4SAndreas Gohr     * Return the user to use for accessing tags
482ace74f4SAndreas Gohr     *
492ace74f4SAndreas Gohr     * Handles the singleuser mode by returning 'auto' as user. Returnes false when no user is logged in.
502ace74f4SAndreas Gohr     *
512ace74f4SAndreas Gohr     * @return bool|string
522ace74f4SAndreas Gohr     */
532ace74f4SAndreas Gohr    public function getUser() {
540cfde7e9SMichael Große        if (!isset($_SERVER['REMOTE_USER'])) {
550cfde7e9SMichael Große            return false;
560cfde7e9SMichael Große        }
570cfde7e9SMichael Große        if ($this->getConf('singleusermode')) {
580cfde7e9SMichael Große            return 'auto';
590cfde7e9SMichael Große        }
60ca455b8eSMichael Große
612ace74f4SAndreas Gohr        return $_SERVER['REMOTE_USER'];
622ace74f4SAndreas Gohr    }
632ace74f4SAndreas Gohr
642ace74f4SAndreas Gohr    /**
65*e4443e5cSAnna Dabrowska     * If plugin elasticsearch is installed, inform it that we have just made changes
66*e4443e5cSAnna Dabrowska     * to some data relevant to a page. The page should be re-indexed.
67*e4443e5cSAnna Dabrowska     *
68*e4443e5cSAnna Dabrowska     * @param string $id
69*e4443e5cSAnna Dabrowska     */
70*e4443e5cSAnna Dabrowska    public function updateElasticState($id)
71*e4443e5cSAnna Dabrowska    {
72*e4443e5cSAnna Dabrowska        /** @var \helper_plugin_elasticsearch_plugins $elasticHelper */
73*e4443e5cSAnna Dabrowska        $elasticHelper = plugin_load('helper', 'elasticsearch_plugins');
74*e4443e5cSAnna Dabrowska        if ($elasticHelper) {
75*e4443e5cSAnna Dabrowska            $elasticHelper->updateRefreshState($id);
76*e4443e5cSAnna Dabrowska        }
77*e4443e5cSAnna Dabrowska    }
78*e4443e5cSAnna Dabrowska
79*e4443e5cSAnna Dabrowska    /**
80302a38efSAndreas Gohr     * Canonicalizes the tag to its lower case nospace form
81302a38efSAndreas Gohr     *
82302a38efSAndreas Gohr     * @param $tag
830cfde7e9SMichael Große     *
84302a38efSAndreas Gohr     * @return string
85302a38efSAndreas Gohr     */
86302a38efSAndreas Gohr    public function cleanTag($tag) {
87a755cf7aSAnna Dabrowska        $tag = str_replace(array(' ', '-', '_', '#'), '', $tag);
88302a38efSAndreas Gohr        $tag = utf8_strtolower($tag);
89ca455b8eSMichael Große
90302a38efSAndreas Gohr        return $tag;
91302a38efSAndreas Gohr    }
92302a38efSAndreas Gohr
9356d82720SAndreas Gohr    /**
9431396860SSzymon Olewniczak     * Canonicalizes the namespace, remove the first colon and add glob
9531396860SSzymon Olewniczak     *
9631396860SSzymon Olewniczak     * @param $namespace
9731396860SSzymon Olewniczak     *
9831396860SSzymon Olewniczak     * @return string
9931396860SSzymon Olewniczak     */
10031396860SSzymon Olewniczak    public function globNamespace($namespace) {
101de379874SAnna Dabrowska        return cleanId($namespace) . '*';
10231396860SSzymon Olewniczak    }
10331396860SSzymon Olewniczak
10431396860SSzymon Olewniczak    /**
10556d82720SAndreas Gohr     * Create or Update tags of a page
10656d82720SAndreas Gohr     *
10756d82720SAndreas Gohr     * Uses the translation plugin to store the language of a page (if available)
10856d82720SAndreas Gohr     *
10956d82720SAndreas Gohr     * @param string $id The page ID
11056d82720SAndreas Gohr     * @param string $user
11156d82720SAndreas Gohr     * @param array  $tags
1120cfde7e9SMichael Große     *
11356d82720SAndreas Gohr     * @return bool|SQLiteResult
11456d82720SAndreas Gohr     */
115f61105deSAdrian Lang    public function replaceTags($id, $user, $tags) {
11656d82720SAndreas Gohr        global $conf;
11756d82720SAndreas Gohr        /** @var helper_plugin_translation $trans */
11856d82720SAndreas Gohr        $trans = plugin_load('helper', 'translation');
11956d82720SAndreas Gohr        if ($trans) {
12056d82720SAndreas Gohr            $lang = $trans->realLC($trans->getLangPart($id));
12156d82720SAndreas Gohr        } else {
12256d82720SAndreas Gohr            $lang = $conf['lang'];
12356d82720SAndreas Gohr        }
12456d82720SAndreas Gohr
125f61105deSAdrian Lang        $db = $this->getDB();
126f61105deSAdrian Lang        $db->query('BEGIN TRANSACTION');
127f61105deSAdrian Lang        $queries = array(array('DELETE FROM taggings WHERE pid = ? AND tagger = ?', $id, $user));
128f61105deSAdrian Lang        foreach ($tags as $tag) {
12956d82720SAndreas Gohr            $queries[] = array('INSERT INTO taggings (pid, tagger, tag, lang) VALUES(?, ?, ?, ?)', $id, $user, $tag, $lang);
130f61105deSAdrian Lang        }
131f61105deSAdrian Lang
132f61105deSAdrian Lang        foreach ($queries as $query) {
133f61105deSAdrian Lang            if (!call_user_func_array(array($db, 'query'), $query)) {
134f61105deSAdrian Lang                $db->query('ROLLBACK TRANSACTION');
135ca455b8eSMichael Große
136f61105deSAdrian Lang                return false;
137f61105deSAdrian Lang            }
138f61105deSAdrian Lang        }
139ca455b8eSMichael Große
140f61105deSAdrian Lang        return $db->query('COMMIT TRANSACTION');
141f61105deSAdrian Lang    }
142f61105deSAdrian Lang
1430a518a11SAndreas Gohr    /**
144b12334e1SAndreas Gohr     * Get a list of Tags or Pages matching search criteria
1450a518a11SAndreas Gohr     *
146b12334e1SAndreas Gohr     * @param array  $filter What to search for array('field' => 'searchterm')
147b12334e1SAndreas Gohr     * @param string $type   What field to return 'tag'|'pid'
148077ff864SAndreas Gohr     * @param int    $limit  Limit to this many results, 0 for all
1490cfde7e9SMichael Große     *
1500a518a11SAndreas Gohr     * @return array associative array in form of value => count
1510a518a11SAndreas Gohr     */
152077ff864SAndreas Gohr    public function findItems($filter, $type, $limit = 0) {
153f61105deSAdrian Lang
1544a7da0a5SAnna Dabrowska        global $INPUT;
155b12334e1SAndreas Gohr
1564a7da0a5SAnna Dabrowska        /** @var helper_plugin_tagging_querybuilder $queryBuilder */
15765d49a60SAnna Dabrowska        $queryBuilder = new \helper_plugin_tagging_querybuilder();
1581b4b4fa9SAnna Dabrowska
1594a7da0a5SAnna Dabrowska        $queryBuilder->setField($type);
1604a7da0a5SAnna Dabrowska        $queryBuilder->setLimit($limit);
161739c5360SAnna Dabrowska        $queryBuilder->setTags($this->extractFromQuery($filter));
1624a7da0a5SAnna Dabrowska        if (isset($filter['ns'])) $queryBuilder->includeNS($filter['ns']);
1634a7da0a5SAnna Dabrowska        if (isset($filter['notns'])) $queryBuilder->excludeNS($filter['notns']);
1644a7da0a5SAnna Dabrowska        if (isset($filter['tagger'])) $queryBuilder->setTagger($filter['tagger']);
1654a7da0a5SAnna Dabrowska        if (isset($filter['pid'])) $queryBuilder->setPid($filter['pid']);
166b12334e1SAndreas Gohr
1674a7da0a5SAnna Dabrowska        return $this->queryDb($queryBuilder->getQuery());
168ca455b8eSMichael Große
169f61105deSAdrian Lang    }
170f61105deSAdrian Lang
171b12334e1SAndreas Gohr    /**
172302a38efSAndreas Gohr     * Constructs the URL to search for a tag
173302a38efSAndreas Gohr     *
1745540f91dSAndreas Gohr     * @param string $tag
1755540f91dSAndreas Gohr     * @param string $ns
1760cfde7e9SMichael Große     *
177302a38efSAndreas Gohr     * @return string
178302a38efSAndreas Gohr     */
1795540f91dSAndreas Gohr    public function getTagSearchURL($tag, $ns = '') {
180a99fe09cSAnna Dabrowska        $ret = '?do=search&sf=1&q=' . rawurlencode('#' . $this->cleanTag($tag));
1810cfde7e9SMichael Große        if ($ns) {
1820cfde7e9SMichael Große            $ret .= rawurlencode(' @' . $ns);
1830cfde7e9SMichael Große        }
1845540f91dSAndreas Gohr
1855540f91dSAndreas Gohr        return $ret;
186f61105deSAdrian Lang    }
187f61105deSAdrian Lang
1885540f91dSAndreas Gohr    /**
1895540f91dSAndreas Gohr     * Calculates the size levels for the given list of clouds
1905540f91dSAndreas Gohr     *
1915540f91dSAndreas Gohr     * Automatically determines sensible tresholds
1925540f91dSAndreas Gohr     *
1935540f91dSAndreas Gohr     * @param array $tags list of tags => count
1945540f91dSAndreas Gohr     * @param int   $levels
1950cfde7e9SMichael Große     *
1965540f91dSAndreas Gohr     * @return mixed
1975540f91dSAndreas Gohr     */
198f61105deSAdrian Lang    public function cloudData($tags, $levels = 10) {
199f61105deSAdrian Lang        $min = min($tags);
200f61105deSAdrian Lang        $max = max($tags);
201f61105deSAdrian Lang
202f61105deSAdrian Lang        // calculate tresholds
203f61105deSAdrian Lang        $tresholds = array();
204f61105deSAdrian Lang        for ($i = 0; $i <= $levels; $i++) {
205f61105deSAdrian Lang            $tresholds[$i] = pow($max - $min + 1, $i / $levels) + $min - 1;
206f61105deSAdrian Lang        }
207f61105deSAdrian Lang
208f61105deSAdrian Lang        // assign weights
209f61105deSAdrian Lang        foreach ($tags as $tag => $cnt) {
210f61105deSAdrian Lang            foreach ($tresholds as $tresh => $val) {
211f61105deSAdrian Lang                if ($cnt <= $val) {
212f61105deSAdrian Lang                    $tags[$tag] = $tresh;
213f61105deSAdrian Lang                    break;
214f61105deSAdrian Lang                }
215f61105deSAdrian Lang                $tags[$tag] = $levels;
216f61105deSAdrian Lang            }
217f61105deSAdrian Lang        }
218ca455b8eSMichael Große
219f61105deSAdrian Lang        return $tags;
220f61105deSAdrian Lang    }
221f61105deSAdrian Lang
2225540f91dSAndreas Gohr    /**
2235540f91dSAndreas Gohr     * Display a tag cloud
2245540f91dSAndreas Gohr     *
2255540f91dSAndreas Gohr     * @param array    $tags   list of tags => count
2265540f91dSAndreas Gohr     * @param string   $type   'tag'
2275540f91dSAndreas Gohr     * @param Callable $func   The function to print the link (gets tag and ns)
2285540f91dSAndreas Gohr     * @param bool     $wrap   wrap cloud in UL tags?
2295540f91dSAndreas Gohr     * @param bool     $return returnn HTML instead of printing?
2305540f91dSAndreas Gohr     * @param string   $ns     Add this namespace to search links
2310cfde7e9SMichael Große     *
2325540f91dSAndreas Gohr     * @return string
2335540f91dSAndreas Gohr     */
2345540f91dSAndreas Gohr    public function html_cloud($tags, $type, $func, $wrap = true, $return = false, $ns = '') {
235a66f6715SAndreas Gohr        global $INFO;
236a66f6715SAndreas Gohr
237a66f6715SAndreas Gohr        $hidden_str = $this->getConf('hiddenprefix');
238a66f6715SAndreas Gohr        $hidden_len = strlen($hidden_str);
239a66f6715SAndreas Gohr
240f61105deSAdrian Lang        $ret = '';
2410cfde7e9SMichael Große        if ($wrap) {
2420cfde7e9SMichael Große            $ret .= '<ul class="tagging_cloud clearfix">';
2430cfde7e9SMichael Große        }
244f61105deSAdrian Lang        if (count($tags) === 0) {
245f61105deSAdrian Lang            // Produce valid XHTML (ul needs a child)
246f61105deSAdrian Lang            $this->setupLocale();
247f61105deSAdrian Lang            $ret .= '<li><div class="li">' . $this->lang['js']['no' . $type . 's'] . '</div></li>';
248f61105deSAdrian Lang        } else {
249f61105deSAdrian Lang            $tags = $this->cloudData($tags);
250f61105deSAdrian Lang            foreach ($tags as $val => $size) {
251a66f6715SAndreas Gohr                // skip hidden tags for users that can't edit
252aa627deeSAndreas Gohr                if ($type === 'tag' and
253a66f6715SAndreas Gohr                    $hidden_len and
254a66f6715SAndreas Gohr                    substr($val, 0, $hidden_len) == $hidden_str and
255a66f6715SAndreas Gohr                    !($this->getUser() && $INFO['writable'])
256a66f6715SAndreas Gohr                ) {
257a66f6715SAndreas Gohr                    continue;
258a66f6715SAndreas Gohr                }
259a66f6715SAndreas Gohr
260f61105deSAdrian Lang                $ret .= '<li class="t' . $size . '"><div class="li">';
2615540f91dSAndreas Gohr                $ret .= call_user_func($func, $val, $ns);
262f61105deSAdrian Lang                $ret .= '</div></li>';
263f61105deSAdrian Lang            }
264f61105deSAdrian Lang        }
2650cfde7e9SMichael Große        if ($wrap) {
2660cfde7e9SMichael Große            $ret .= '</ul>';
2670cfde7e9SMichael Große        }
2680cfde7e9SMichael Große        if ($return) {
2690cfde7e9SMichael Große            return $ret;
2700cfde7e9SMichael Große        }
271f61105deSAdrian Lang        echo $ret;
272ca455b8eSMichael Große
2735540f91dSAndreas Gohr        return '';
274f61105deSAdrian Lang    }
275f61105deSAdrian Lang
2765540f91dSAndreas Gohr    /**
2770b6fad27Ssandos187     * Display a List of Page Links
2780b6fad27Ssandos187     *
2790b6fad27Ssandos187     * @param array    $pids   list of pids => count
2800b6fad27Ssandos187     * @return string
2810b6fad27Ssandos187     */
2820b6fad27Ssandos187    public function html_page_list($pids) {
2830b6fad27Ssandos187        $ret = '<div class="search_quickresult">';
2840b6fad27Ssandos187        $ret .= '<ul class="search_quickhits">';
2850b6fad27Ssandos187
2860b6fad27Ssandos187        if (count($pids) === 0) {
2870b6fad27Ssandos187            // Produce valid XHTML (ul needs a child)
2880b6fad27Ssandos187            $ret .= '<li><div class="li">' . $this->lang['js']['nopages'] . '</div></li>';
2890b6fad27Ssandos187        } else {
290bdf1ecf0SAnna Dabrowska            foreach (array_keys($pids) as $val) {
2910b6fad27Ssandos187                $ret .= '<li><div class="li">';
292db3ab356SAnna Dabrowska                $ret .= html_wikilink(":$val");
2930b6fad27Ssandos187                $ret .= '</div></li>';
2940b6fad27Ssandos187            }
2950b6fad27Ssandos187        }
2960b6fad27Ssandos187
2970b6fad27Ssandos187        $ret .= '</ul>';
2980b6fad27Ssandos187        $ret .= '</div>';
2990b6fad27Ssandos187        $ret .= '<div class="clearer"></div>';
3000b6fad27Ssandos187
3010b6fad27Ssandos187        return $ret;
3020b6fad27Ssandos187    }
3030b6fad27Ssandos187
3040b6fad27Ssandos187    /**
3055540f91dSAndreas Gohr     * Get the link to a search for the given tag
3065540f91dSAndreas Gohr     *
3075540f91dSAndreas Gohr     * @param string $tag search for this tag
3085540f91dSAndreas Gohr     * @param string $ns  limit search to this namespace
3090cfde7e9SMichael Große     *
3105540f91dSAndreas Gohr     * @return string
3115540f91dSAndreas Gohr     */
3125540f91dSAndreas Gohr    protected function linkToSearch($tag, $ns = '') {
3135540f91dSAndreas Gohr        return '<a href="' . hsc($this->getTagSearchURL($tag, $ns)) . '">' . $tag . '</a>';
314f61105deSAdrian Lang    }
315f61105deSAdrian Lang
316fb1d0583SAndreas Gohr    /**
317fb1d0583SAndreas Gohr     * Display the Tags for the current page and prepare the tag editing form
3183496cc8aSAndreas Gohr     *
3193496cc8aSAndreas Gohr     * @param bool $print Should the HTML be printed or returned?
3200cfde7e9SMichael Große     *
3213496cc8aSAndreas Gohr     * @return string
322fb1d0583SAndreas Gohr     */
3233496cc8aSAndreas Gohr    public function tpl_tags($print = true) {
324f61105deSAdrian Lang        global $INFO;
325f61105deSAdrian Lang        global $lang;
3263bf0e2f1SMichael Große
3273bf0e2f1SMichael Große        $filter = array('pid' => $INFO['id']);
3283bf0e2f1SMichael Große        if ($this->getConf('singleusermode')) {
3293bf0e2f1SMichael Große            $filter['tagger'] = 'auto';
3303bf0e2f1SMichael Große        }
3313bf0e2f1SMichael Große
3323bf0e2f1SMichael Große        $tags = $this->findItems($filter, 'tag');
3333496cc8aSAndreas Gohr
3343496cc8aSAndreas Gohr        $ret = '';
3353496cc8aSAndreas Gohr
3363496cc8aSAndreas Gohr        $ret .= '<div class="plugin_tagging_edit">';
3373496cc8aSAndreas Gohr        $ret .= $this->html_cloud($tags, 'tag', array($this, 'linkToSearch'), true, true);
338f61105deSAdrian Lang
3392ace74f4SAndreas Gohr        if ($this->getUser() && $INFO['writable']) {
340f61105deSAdrian Lang            $lang['btn_tagging_edit'] = $lang['btn_secedit'];
341e5b42768SSzymon Olewniczak            $ret .= '<div id="tagging__edit_buttons_group">';
3423496cc8aSAndreas Gohr            $ret .= html_btn('tagging_edit', $INFO['id'], '', array());
343dd52fd45SSzymon Olewniczak            if (auth_isadmin()) {
34426f61833SAnna Dabrowska                $ret .= '<label>'
34526f61833SAnna Dabrowska                    . $this->getLang('toggle admin mode')
34626f61833SAnna Dabrowska                    . '<input type="checkbox" id="tagging__edit_toggle_admin" /></label>';
347dd52fd45SSzymon Olewniczak            }
348e5b42768SSzymon Olewniczak            $ret .= '</div>';
3492819ffcdSSzymon Olewniczak            $form = new dokuwiki\Form\Form();
3502819ffcdSSzymon Olewniczak            $form->id('tagging__edit');
3512819ffcdSSzymon Olewniczak            $form->setHiddenField('tagging[id]', $INFO['id']);
3522819ffcdSSzymon Olewniczak            $form->setHiddenField('call', 'plugin_tagging_save');
3532819ffcdSSzymon Olewniczak            $tags = $this->findItems(array(
3542819ffcdSSzymon Olewniczak                'pid'    => $INFO['id'],
355ca455b8eSMichael Große                'tagger' => $this->getUser(),
3562819ffcdSSzymon Olewniczak            ), 'tag');
35726f61833SAnna Dabrowska            $form->addTextarea('tagging[tags]')
35826f61833SAnna Dabrowska                ->val(implode(', ', array_keys($tags)))
35926f61833SAnna Dabrowska                ->addClass('edit')
36026f61833SAnna Dabrowska                ->attr('rows', 4);
361cf52ec2dSSzymon Olewniczak            $form->addButton('', $lang['btn_save'])->id('tagging__edit_save');
362cf52ec2dSSzymon Olewniczak            $form->addButton('', $lang['btn_cancel'])->id('tagging__edit_cancel');
3632819ffcdSSzymon Olewniczak            $ret .= $form->toHTML();
364f61105deSAdrian Lang        }
3653496cc8aSAndreas Gohr        $ret .= '</div>';
3663496cc8aSAndreas Gohr
3670cfde7e9SMichael Große        if ($print) {
3680cfde7e9SMichael Große            echo $ret;
3690cfde7e9SMichael Große        }
370ca455b8eSMichael Große
3713496cc8aSAndreas Gohr        return $ret;
372f61105deSAdrian Lang    }
373872edc7cSRené Corinth
3748a1a3846SAndreas Gohr    /**
375a99b66c1SSzymon Olewniczak     * @param string $namespace empty for entire wiki
376a99b66c1SSzymon Olewniczak     *
37740b94b1aSAnna Dabrowska     * @param string $order_by
37840b94b1aSAnna Dabrowska     * @param bool $desc
37940b94b1aSAnna Dabrowska     * @param array $filters
3808a1a3846SAndreas Gohr     * @return array
3818a1a3846SAndreas Gohr     */
382f6568bcbSAnna Dabrowska    public function getAllTags($namespace = '', $order_by = 'tid', $desc = false, $filters = []) {
3837c96ae87SAnna Dabrowska        $order_fields = array('pid', 'tid', 'taggers', 'ns', 'count');
384f0084ee1SSzymon Olewniczak        if (!in_array($order_by, $order_fields)) {
385f0084ee1SSzymon Olewniczak            msg('cannot sort by ' . $order_by . ' field does not exists', -1);
386f0084ee1SSzymon Olewniczak            $order_by = 'tag';
387f0084ee1SSzymon Olewniczak        }
388872edc7cSRené Corinth
38940b94b1aSAnna Dabrowska        list($having, $params) = $this->getFilterSql($filters);
39040b94b1aSAnna Dabrowska
391a2246f69SAnna Dabrowska        $db = $this->getDB();
392872edc7cSRené Corinth
393f0084ee1SSzymon Olewniczak        $query = 'SELECT    "pid",
394ca455b8eSMichael Große                            CLEANTAG("tag") AS "tid",
395f0084ee1SSzymon Olewniczak                            GROUP_SORT(GROUP_CONCAT("tagger"), \', \') AS "taggers",
39640b94b1aSAnna Dabrowska                            GROUP_SORT(GROUP_CONCAT(GET_NS("pid")), \', \') AS "ns",
39789ed97adSAnna Dabrowska                            GROUP_SORT(GROUP_CONCAT("pid"), \', \') AS "pids",
398193a767dSSzymon Olewniczak                            COUNT(*) AS "count"
39957e45304SSzymon Olewniczak                        FROM "taggings"
4004227fca4SAnna Dabrowska                        WHERE "pid" GLOB ? AND GETACCESSLEVEL(pid) >= ' . AUTH_READ
4014227fca4SAnna Dabrowska                        . ' GROUP BY "tid"';
40240b94b1aSAnna Dabrowska        $query .= $having;
40340b94b1aSAnna Dabrowska        $query .=      'ORDER BY ' . $order_by;
404ca455b8eSMichael Große        if ($desc) {
405ca455b8eSMichael Große            $query .= ' DESC';
406ca455b8eSMichael Große        }
407cb469644SSzymon Olewniczak
40840b94b1aSAnna Dabrowska        array_unshift($params, $this->globNamespace($namespace));
40940b94b1aSAnna Dabrowska        $res = $db->query($query, $params);
410872edc7cSRené Corinth
4117e05e623SSzymon Olewniczak        return $db->res2arr($res);
412872edc7cSRené Corinth    }
413872edc7cSRené Corinth
4148a1a3846SAndreas Gohr    /**
41572431326SMichael Große     * Get all pages with tags and their tags
41672431326SMichael Große     *
417790ca788SAndreas Gohr     * @return array ['pid' => ['tag1','tag2','tag3']]
41872431326SMichael Große     */
41972431326SMichael Große    public function getAllTagsByPage() {
42072431326SMichael Große        $query = '
42172431326SMichael Große        SELECT pid, GROUP_CONCAT(tag) AS tags
42272431326SMichael Große        FROM taggings
42372431326SMichael Große        GROUP BY pid
42472431326SMichael Große        ';
42572431326SMichael Große        $db = $this->getDb();
42672431326SMichael Große        $res = $db->query($query);
427790ca788SAndreas Gohr        return array_map(
428790ca788SAndreas Gohr            function ($i) {
429790ca788SAndreas Gohr                return explode(',', $i);
430790ca788SAndreas Gohr            },
431790ca788SAndreas Gohr            array_column($db->res2arr($res), 'tags', 'pid')
432790ca788SAndreas Gohr        );
43372431326SMichael Große    }
43472431326SMichael Große
43572431326SMichael Große    /**
4368a1a3846SAndreas Gohr     * Renames a tag
4378a1a3846SAndreas Gohr     *
4388a1a3846SAndreas Gohr     * @param string $formerTagName
4394227fca4SAnna Dabrowska     * @param string $newTagNames
4408a1a3846SAndreas Gohr     */
4414227fca4SAnna Dabrowska    public function renameTag($formerTagName, $newTagNames) {
442872edc7cSRené Corinth
4434227fca4SAnna Dabrowska        if (empty($formerTagName) || empty($newTagNames)) {
4448a1a3846SAndreas Gohr            msg($this->getLang("admin enter tag names"), -1);
4458a1a3846SAndreas Gohr            return;
446872edc7cSRené Corinth        }
447872edc7cSRené Corinth
448870d77ddSAnna Dabrowska        $keepFormerTag = false;
449870d77ddSAnna Dabrowska
4504227fca4SAnna Dabrowska        // enable splitting tags on rename
451870d77ddSAnna Dabrowska        $newTagNames = array_map(function ($tag) {
452870d77ddSAnna Dabrowska            return $this->cleanTag($tag);
453870d77ddSAnna Dabrowska        }, explode(',', $newTagNames));
4544227fca4SAnna Dabrowska
4554227fca4SAnna Dabrowska        $db = $this->getDB();
456872edc7cSRené Corinth
4574227fca4SAnna Dabrowska        // non-admins can rename only their own tags
4584227fca4SAnna Dabrowska        if (!auth_isadmin()) {
4594227fca4SAnna Dabrowska            $queryTagger =' AND tagger = ?';
4604227fca4SAnna Dabrowska            $tagger = $this->getUser();
4614227fca4SAnna Dabrowska        } else {
4624227fca4SAnna Dabrowska            $queryTagger = '';
4634227fca4SAnna Dabrowska            $tagger = '';
4644227fca4SAnna Dabrowska        }
4654227fca4SAnna Dabrowska
4660ec63874SAnna Dabrowska        $insertQuery = 'INSERT INTO taggings ';
4670ec63874SAnna Dabrowska        $insertQuery .= 'SELECT pid, ?, tagger, lang FROM taggings';
4680ec63874SAnna Dabrowska        $where = ' WHERE CLEANTAG(tag) = ?';
4690ec63874SAnna Dabrowska        $where .= ' AND GETACCESSLEVEL(pid) >= ' . AUTH_EDIT;
4700ec63874SAnna Dabrowska        $where .= $queryTagger;
4710ec63874SAnna Dabrowska
4720ec63874SAnna Dabrowska        $db->query('BEGIN TRANSACTION');
4730ec63874SAnna Dabrowska
4740ec63874SAnna Dabrowska        // insert new tags first
4750ec63874SAnna Dabrowska        foreach ($newTagNames as $newTag) {
476870d77ddSAnna Dabrowska            if ($newTag === $this->cleanTag($formerTagName)) {
477870d77ddSAnna Dabrowska                $keepFormerTag = true;
478870d77ddSAnna Dabrowska                continue;
479870d77ddSAnna Dabrowska            }
480870d77ddSAnna Dabrowska            $params = [$newTag, $this->cleanTag($formerTagName)];
4814227fca4SAnna Dabrowska            if ($tagger) array_push($params, $tagger);
4820ec63874SAnna Dabrowska            $res = $db->query($insertQuery . $where, $params);
4830ec63874SAnna Dabrowska            if ($res === false) {
4840ec63874SAnna Dabrowska                $db->query('ROLLBACK TRANSACTION');
4850ec63874SAnna Dabrowska                return;
4864227fca4SAnna Dabrowska            }
4870ec63874SAnna Dabrowska            $db->res_close($res);
4880ec63874SAnna Dabrowska        }
4890ec63874SAnna Dabrowska
490870d77ddSAnna Dabrowska        // finally delete the renamed tags
491870d77ddSAnna Dabrowska        if (!$keepFormerTag) {
4920ec63874SAnna Dabrowska            $deleteQuery = 'DELETE FROM taggings';
4930ec63874SAnna Dabrowska            $params = [$this->cleanTag($formerTagName)];
4940ec63874SAnna Dabrowska            if ($tagger) array_push($params, $tagger);
4950ec63874SAnna Dabrowska            if ($db->query($deleteQuery . $where, $params) === false) {
4960ec63874SAnna Dabrowska                $db->query('ROLLBACK TRANSACTION');
4970ec63874SAnna Dabrowska                return;
4980ec63874SAnna Dabrowska            }
499870d77ddSAnna Dabrowska        }
5000ec63874SAnna Dabrowska
5010ec63874SAnna Dabrowska        $db->query('COMMIT TRANSACTION');
502872edc7cSRené Corinth
503fb1d0583SAndreas Gohr        msg($this->getLang("admin renamed"), 1);
504ca455b8eSMichael Große
5058a1a3846SAndreas Gohr        return;
506872edc7cSRené Corinth    }
507872edc7cSRené Corinth
5088f630140SSzymon Olewniczak    /**
509dd52fd45SSzymon Olewniczak     * Rename or delete a tag for all users
510dd52fd45SSzymon Olewniczak     *
511dd52fd45SSzymon Olewniczak     * @param string $pid
512dd52fd45SSzymon Olewniczak     * @param string $formerTagName
513dd52fd45SSzymon Olewniczak     * @param string $newTagName
514dd52fd45SSzymon Olewniczak     *
515dd52fd45SSzymon Olewniczak     * @return array
516dd52fd45SSzymon Olewniczak     */
517dd52fd45SSzymon Olewniczak    public function modifyPageTag($pid, $formerTagName, $newTagName) {
518dd52fd45SSzymon Olewniczak
519dd52fd45SSzymon Olewniczak        $db = $this->getDb();
520dd52fd45SSzymon Olewniczak
52126f61833SAnna Dabrowska        $res = $db->query(
52226f61833SAnna Dabrowska            'SELECT pid FROM taggings WHERE CLEANTAG(tag) = ? AND pid = ?',
52326f61833SAnna Dabrowska            $this->cleanTag($formerTagName),
52426f61833SAnna Dabrowska            $pid
52526f61833SAnna Dabrowska        );
526dd52fd45SSzymon Olewniczak        $check = $db->res2arr($res);
527dd52fd45SSzymon Olewniczak
528dd52fd45SSzymon Olewniczak        if (empty($check)) {
529dd52fd45SSzymon Olewniczak            return array(true, $this->getLang('admin tag does not exists'));
530dd52fd45SSzymon Olewniczak        }
531dd52fd45SSzymon Olewniczak
532dd52fd45SSzymon Olewniczak        if (empty($newTagName)) {
53326f61833SAnna Dabrowska            $res = $db->query(
53426f61833SAnna Dabrowska                'DELETE FROM taggings WHERE pid = ? AND CLEANTAG(tag) = ?',
53526f61833SAnna Dabrowska                $pid,
53626f61833SAnna Dabrowska                $this->cleanTag($formerTagName)
53726f61833SAnna Dabrowska            );
538dd52fd45SSzymon Olewniczak        } else {
53926f61833SAnna Dabrowska            $res = $db->query(
54026f61833SAnna Dabrowska                'UPDATE taggings SET tag = ? WHERE pid = ? AND CLEANTAG(tag) = ?',
54126f61833SAnna Dabrowska                $newTagName,
54226f61833SAnna Dabrowska                $pid,
54326f61833SAnna Dabrowska                $this->cleanTag($formerTagName)
54426f61833SAnna Dabrowska            );
545dd52fd45SSzymon Olewniczak        }
546dd52fd45SSzymon Olewniczak        $db->res2arr($res);
547dd52fd45SSzymon Olewniczak
548dd52fd45SSzymon Olewniczak        return array(false, $this->getLang('admin renamed'));
549dd52fd45SSzymon Olewniczak    }
550dd52fd45SSzymon Olewniczak
551dd52fd45SSzymon Olewniczak    /**
5528f630140SSzymon Olewniczak     * Deletes a tag
5538f630140SSzymon Olewniczak     *
5548f630140SSzymon Olewniczak     * @param array  $tags
55531396860SSzymon Olewniczak     * @param string $namespace current namespace context as in getAllTags()
5568f630140SSzymon Olewniczak     */
55731396860SSzymon Olewniczak    public function deleteTags($tags, $namespace = '') {
558ca455b8eSMichael Große        if (empty($tags)) {
559ca455b8eSMichael Große            return;
560ca455b8eSMichael Große        }
5618f630140SSzymon Olewniczak
56231396860SSzymon Olewniczak        $namespace = cleanId($namespace);
56331396860SSzymon Olewniczak
5641f5991a7SMichael Große        $db = $this->getDB();
5658f630140SSzymon Olewniczak
566de379874SAnna Dabrowska        $queryBody = 'FROM taggings WHERE pid GLOB ? AND (' .
56731396860SSzymon Olewniczak            implode(' OR ', array_fill(0, count($tags), 'CLEANTAG(tag) = ?')) . ')';
56831396860SSzymon Olewniczak        $args = array_map(array($this, 'cleanTag'), $tags);
56931396860SSzymon Olewniczak        array_unshift($args, $this->globNamespace($namespace));
5708f630140SSzymon Olewniczak
5714227fca4SAnna Dabrowska        // non-admins can delete only their own tags
5724227fca4SAnna Dabrowska        if (!auth_isadmin()) {
5734227fca4SAnna Dabrowska            $queryBody .= ' AND tagger = ?';
5744227fca4SAnna Dabrowska            array_push($args, $this->getUser());
5754227fca4SAnna Dabrowska        }
576ca455b8eSMichael Große
5771f5991a7SMichael Große        $affectedPagesQuery= 'SELECT DISTINCT pid ' . $queryBody;
5781f5991a7SMichael Große        $resAffectedPages = $db->query($affectedPagesQuery, $args);
5791f5991a7SMichael Große        $numAffectedPages = count($resAffectedPages->fetchAll());
5801f5991a7SMichael Große
5811f5991a7SMichael Große        $deleteQuery = 'DELETE ' . $queryBody;
5821f5991a7SMichael Große        $db->query($deleteQuery, $args);
5831f5991a7SMichael Große
5841f5991a7SMichael Große        msg(sprintf($this->getLang("admin deleted"), count($tags), $numAffectedPages), 1);
5858f630140SSzymon Olewniczak    }
586cd08599fSAnna Dabrowska
587cd08599fSAnna Dabrowska    /**
588ec4796e4SAnna Dabrowska     * Delete taggings of nonexistent pages
589ec4796e4SAnna Dabrowska     */
590ec4796e4SAnna Dabrowska    public function deleteInvalidTaggings()
591ec4796e4SAnna Dabrowska    {
592ec4796e4SAnna Dabrowska        $db = $this->getDB();
593ec4796e4SAnna Dabrowska        $query = 'DELETE    FROM "taggings"
5941815f49fSAnna Dabrowska                            WHERE NOT PAGEEXISTS(pid)
595ec4796e4SAnna Dabrowska                 ';
596ec4796e4SAnna Dabrowska        $res = $db->query($query);
597ec4796e4SAnna Dabrowska        $db->res_close($res);
598ec4796e4SAnna Dabrowska    }
599ec4796e4SAnna Dabrowska
600ec4796e4SAnna Dabrowska    /**
601cd08599fSAnna Dabrowska     * Updates tags with a new page name
602cd08599fSAnna Dabrowska     *
603cd08599fSAnna Dabrowska     * @param string $oldName
604cd08599fSAnna Dabrowska     * @param string $newName
605cd08599fSAnna Dabrowska     */
606cd08599fSAnna Dabrowska    public function renamePage($oldName, $newName) {
607f6568bcbSAnna Dabrowska        $db = $this->getDB();
608cd08599fSAnna Dabrowska        $db->query('UPDATE taggings SET pid = ? WHERE pid = ?', $newName, $oldName);
609cd08599fSAnna Dabrowska    }
610972f6adfSAnna Dabrowska
611972f6adfSAnna Dabrowska    /**
6121b4b4fa9SAnna Dabrowska     * Extracts tags from search query
6131b4b4fa9SAnna Dabrowska     *
6141b4b4fa9SAnna Dabrowska     * @param array $parsedQuery
6151b4b4fa9SAnna Dabrowska     * @return array
6161b4b4fa9SAnna Dabrowska     */
617739c5360SAnna Dabrowska    public function extractFromQuery($parsedQuery)
6181b4b4fa9SAnna Dabrowska    {
6191b4b4fa9SAnna Dabrowska        $tags = [];
6201b4b4fa9SAnna Dabrowska        if (isset($parsedQuery['phrases'][0])) {
6211b4b4fa9SAnna Dabrowska            $tags = $parsedQuery['phrases'];
6221b4b4fa9SAnna Dabrowska        } elseif (isset($parsedQuery['and'][0])) {
6231b4b4fa9SAnna Dabrowska            $tags = $parsedQuery['and'];
6241b4b4fa9SAnna Dabrowska        } elseif (isset($parsedQuery['tag'])) {
6251b4b4fa9SAnna Dabrowska            // handle autocomplete call
6261b4b4fa9SAnna Dabrowska            $tags[] = $parsedQuery['tag'];
6271b4b4fa9SAnna Dabrowska        }
6281b4b4fa9SAnna Dabrowska        return $tags;
6291b4b4fa9SAnna Dabrowska    }
6301b4b4fa9SAnna Dabrowska
6311b4b4fa9SAnna Dabrowska    /**
6324a7da0a5SAnna Dabrowska     * Search for tagged pages
633972f6adfSAnna Dabrowska     *
634739c5360SAnna Dabrowska     * @param array $tagFiler
6354a7da0a5SAnna Dabrowska     * @return array
636972f6adfSAnna Dabrowska     */
637739c5360SAnna Dabrowska    public function searchPages($tagFiler)
638972f6adfSAnna Dabrowska    {
6391b4b4fa9SAnna Dabrowska        global $INPUT;
6401b4b4fa9SAnna Dabrowska        global $QUERY;
6414a7da0a5SAnna Dabrowska        $parsedQuery = ft_queryParser(new Doku_Indexer(), $QUERY);
642972f6adfSAnna Dabrowska
6431b4b4fa9SAnna Dabrowska        /** @var helper_plugin_tagging_querybuilder $queryBuilder */
64465d49a60SAnna Dabrowska        $queryBuilder = new \helper_plugin_tagging_querybuilder();
645972f6adfSAnna Dabrowska
6464a7da0a5SAnna Dabrowska        $queryBuilder->setField('pid');
647cbe7b4baSAnna Dabrowska        $queryBuilder->setTags($tagFiler);
648f014dfc9SAnna Dabrowska        $queryBuilder->setLogicalAnd($INPUT->str('tagging-logic') === 'and');
6494a7da0a5SAnna Dabrowska        if (isset($parsedQuery['ns'])) $queryBuilder->includeNS($parsedQuery['ns']);
6504a7da0a5SAnna Dabrowska        if (isset($parsedQuery['notns'])) $queryBuilder->excludeNS($parsedQuery['notns']);
6514a7da0a5SAnna Dabrowska        if (isset($parsedQuery['tagger'])) $queryBuilder->setTagger($parsedQuery['tagger']);
6524a7da0a5SAnna Dabrowska        if (isset($parsedQuery['pid'])) $queryBuilder->setPid($parsedQuery['pid']);
653972f6adfSAnna Dabrowska
6544a7da0a5SAnna Dabrowska        return $this->queryDb($queryBuilder->getPages());
655972f6adfSAnna Dabrowska    }
656972f6adfSAnna Dabrowska
6571b4b4fa9SAnna Dabrowska    /**
6584227fca4SAnna Dabrowska     * Syntax to allow users to manage tags on regular pages, respects ACLs
6594227fca4SAnna Dabrowska     * @param string $ns
6604227fca4SAnna Dabrowska     * @return string
6614227fca4SAnna Dabrowska     */
6624227fca4SAnna Dabrowska    public function manageTags($ns)
6634227fca4SAnna Dabrowska    {
6644227fca4SAnna Dabrowska        global $INPUT;
6654227fca4SAnna Dabrowska
666f6568bcbSAnna Dabrowska        $this->setDefaultSort();
6674227fca4SAnna Dabrowska
6684227fca4SAnna Dabrowska        // initially set namespace filter to what is defined in syntax
6694227fca4SAnna Dabrowska        if ($ns && !$INPUT->has('tagging__filters')) {
6704227fca4SAnna Dabrowska            $INPUT->set('tagging__filters', ['ns' => $ns]);
6714227fca4SAnna Dabrowska        }
6724227fca4SAnna Dabrowska
6734227fca4SAnna Dabrowska        return $this->html_table();
6744227fca4SAnna Dabrowska    }
6754227fca4SAnna Dabrowska
6764227fca4SAnna Dabrowska    /**
677a2246f69SAnna Dabrowska     * HTML list of tagged pages
678a2246f69SAnna Dabrowska     *
679a2246f69SAnna Dabrowska     * @param string $tid
680a2246f69SAnna Dabrowska     * @return string
681a2246f69SAnna Dabrowska     */
682a2246f69SAnna Dabrowska    public function getPagesHtml($tid)
683a2246f69SAnna Dabrowska    {
684a2246f69SAnna Dabrowska        $html = '';
685a2246f69SAnna Dabrowska
686a2246f69SAnna Dabrowska        $db = $this->getDB();
68731bddc5fSAnna Dabrowska        $sql = 'SELECT pid from taggings where CLEANTAG(tag) = CLEANTAG(?)';
688a2246f69SAnna Dabrowska        $res =  $db->query($sql, $tid);
689a2246f69SAnna Dabrowska        $pages = $db->res2arr($res);
690a2246f69SAnna Dabrowska
691a2246f69SAnna Dabrowska        if ($pages) {
692a2246f69SAnna Dabrowska            $html .= '<ul>';
693a2246f69SAnna Dabrowska            foreach ($pages as $page) {
694a2246f69SAnna Dabrowska                $pid = $page['pid'];
695a2246f69SAnna Dabrowska                $html .= '<li><a href="' . wl($pid) . '" target="_blank">' . $pid . '</li>';
696a2246f69SAnna Dabrowska            }
697a2246f69SAnna Dabrowska            $html .= '</ul>';
698a2246f69SAnna Dabrowska        }
699a2246f69SAnna Dabrowska
700a2246f69SAnna Dabrowska        return $html;
701a2246f69SAnna Dabrowska    }
702a2246f69SAnna Dabrowska
703a2246f69SAnna Dabrowska    /**
70489ed97adSAnna Dabrowska     * Display tag management table
70589ed97adSAnna Dabrowska     */
70689ed97adSAnna Dabrowska    public function html_table() {
70789ed97adSAnna Dabrowska        global $ID, $INPUT;
70889ed97adSAnna Dabrowska
70989ed97adSAnna Dabrowska        $headers = array(
71089ed97adSAnna Dabrowska            array('value' => $this->getLang('admin tag'), 'sort_by' => 'tid'),
71126c4179dSAnna Dabrowska            array('value' => $this->getLang('admin occurrence'), 'sort_by' => 'count')
71226c4179dSAnna Dabrowska        );
71326c4179dSAnna Dabrowska
71426c4179dSAnna Dabrowska        if (!$this->conf['hidens']) {
71526c4179dSAnna Dabrowska            array_push(
71626c4179dSAnna Dabrowska                $headers,
71726c4179dSAnna Dabrowska                ['value' => $this->getLang('admin namespaces'), 'sort_by' => 'ns']
71826c4179dSAnna Dabrowska            );
71926c4179dSAnna Dabrowska        }
72026c4179dSAnna Dabrowska
72126c4179dSAnna Dabrowska        array_push($headers,
72289ed97adSAnna Dabrowska            array('value' => $this->getLang('admin taggers'), 'sort_by' => 'taggers'),
72326c4179dSAnna Dabrowska            array('value' => $this->getLang('admin actions'), 'sort_by' => false)
72489ed97adSAnna Dabrowska        );
72589ed97adSAnna Dabrowska
726f6568bcbSAnna Dabrowska        $sort = explode(',', $this->getParam('sort'));
72789ed97adSAnna Dabrowska        $order_by = $sort[0];
72889ed97adSAnna Dabrowska        $desc = false;
72989ed97adSAnna Dabrowska        if (isset($sort[1]) && $sort[1] === 'desc') {
73089ed97adSAnna Dabrowska            $desc = true;
73189ed97adSAnna Dabrowska        }
73289ed97adSAnna Dabrowska        $filters = $INPUT->arr('tagging__filters');
73389ed97adSAnna Dabrowska
73489ed97adSAnna Dabrowska        $tags = $this->getAllTags($INPUT->str('filter'), $order_by, $desc, $filters);
73589ed97adSAnna Dabrowska
736a2246f69SAnna Dabrowska        $form = new \dokuwiki\Form\Form();
737a2246f69SAnna Dabrowska        // required in admin mode
73889ed97adSAnna Dabrowska        $form->setHiddenField('page', 'tagging');
73989ed97adSAnna Dabrowska        $form->setHiddenField('id', $ID);
740f6568bcbSAnna Dabrowska        $form->setHiddenField('[tagging]sort', $this->getParam('sort'));
74189ed97adSAnna Dabrowska
74289ed97adSAnna Dabrowska        /**
74389ed97adSAnna Dabrowska         * Actions dialog
74489ed97adSAnna Dabrowska         */
74589ed97adSAnna Dabrowska        $form->addTagOpen('div')->id('tagging__action-dialog')->attr('style', "display:none;");
74689ed97adSAnna Dabrowska        $form->addTagClose('div');
74789ed97adSAnna Dabrowska
74889ed97adSAnna Dabrowska        /**
74989ed97adSAnna Dabrowska         * Tag pages dialog
75089ed97adSAnna Dabrowska         */
75189ed97adSAnna Dabrowska        $form->addTagOpen('div')->id('tagging__taggedpages-dialog')->attr('style', "display:none;");
75289ed97adSAnna Dabrowska        $form->addTagClose('div');
75389ed97adSAnna Dabrowska
75489ed97adSAnna Dabrowska        /**
75589ed97adSAnna Dabrowska         * Tag management table
75689ed97adSAnna Dabrowska         */
75789ed97adSAnna Dabrowska        $form->addTagOpen('table')->addClass('inline plugin_tagging');
75889ed97adSAnna Dabrowska
75926c4179dSAnna Dabrowska        $nscol = $this->conf['hidens'] ? '' : '<col class="wide-col"></col>';
760a305ec34SAnna Dabrowska        $form->addHTML(
761a305ec34SAnna Dabrowska            '<colgroup>
762a305ec34SAnna Dabrowska                <col></col>
76326c4179dSAnna Dabrowska                <col class="narrow-col"></col>'
76426c4179dSAnna Dabrowska                . $nscol .
76526c4179dSAnna Dabrowska                '<col></col>
766a305ec34SAnna Dabrowska                <col class="narrow-col"></col>
767a305ec34SAnna Dabrowska            </colgroup>'
768a305ec34SAnna Dabrowska        );
769a305ec34SAnna Dabrowska
77089ed97adSAnna Dabrowska        /**
77189ed97adSAnna Dabrowska         * Table headers
77289ed97adSAnna Dabrowska         */
77389ed97adSAnna Dabrowska        $form->addTagOpen('tr');
77489ed97adSAnna Dabrowska        foreach ($headers as $header) {
77589ed97adSAnna Dabrowska            $form->addTagOpen('th');
77689ed97adSAnna Dabrowska            if ($header['sort_by'] !== false) {
77789ed97adSAnna Dabrowska                $param = $header['sort_by'];
77889ed97adSAnna Dabrowska                $icon = 'arrow-both';
77989ed97adSAnna Dabrowska                $title = $this->getLang('admin sort ascending');
78089ed97adSAnna Dabrowska                if ($header['sort_by'] === $order_by) {
78189ed97adSAnna Dabrowska                    if ($desc === false) {
78289ed97adSAnna Dabrowska                        $icon = 'arrow-up';
78389ed97adSAnna Dabrowska                        $title = $this->getLang('admin sort descending');
78489ed97adSAnna Dabrowska                        $param .= ',desc';
78589ed97adSAnna Dabrowska                    } else {
78689ed97adSAnna Dabrowska                        $icon = 'arrow-down';
78789ed97adSAnna Dabrowska                    }
78889ed97adSAnna Dabrowska                }
78926f61833SAnna Dabrowska                $form->addButtonHTML(
790f6568bcbSAnna Dabrowska                    "tagging[sort]",
79126f61833SAnna Dabrowska                    $header['value'] . ' ' . inlineSVG(__DIR__ . "/images/$icon.svg"))
79289ed97adSAnna Dabrowska                    ->addClass('plugin_tagging sort_button')
793f6568bcbSAnna Dabrowska                    ->attr('title', $title)
794f6568bcbSAnna Dabrowska                    ->val($param);
79589ed97adSAnna Dabrowska            } else {
79689ed97adSAnna Dabrowska                $form->addHTML($header['value']);
79789ed97adSAnna Dabrowska            }
79889ed97adSAnna Dabrowska            $form->addTagClose('th');
79989ed97adSAnna Dabrowska        }
80089ed97adSAnna Dabrowska        $form->addTagClose('tr');
80189ed97adSAnna Dabrowska
80289ed97adSAnna Dabrowska        /**
80389ed97adSAnna Dabrowska         * Table filters for all sortable columns
80489ed97adSAnna Dabrowska         */
80589ed97adSAnna Dabrowska        $form->addTagOpen('tr');
80689ed97adSAnna Dabrowska        foreach ($headers as $header) {
80789ed97adSAnna Dabrowska            $form->addTagOpen('th');
80889ed97adSAnna Dabrowska            if ($header['sort_by'] !== false) {
80989ed97adSAnna Dabrowska                $field = $header['sort_by'];
8107c96ae87SAnna Dabrowska                $input = $form->addTextInput("tagging__filters[$field]");
811a305ec34SAnna Dabrowska                $input->addClass('full-col');
81289ed97adSAnna Dabrowska            }
81389ed97adSAnna Dabrowska            $form->addTagClose('th');
81489ed97adSAnna Dabrowska        }
81589ed97adSAnna Dabrowska        $form->addTagClose('tr');
81689ed97adSAnna Dabrowska
81789ed97adSAnna Dabrowska
81889ed97adSAnna Dabrowska        foreach ($tags as $taginfo) {
81989ed97adSAnna Dabrowska            $tagname = $taginfo['tid'];
82089ed97adSAnna Dabrowska            $taggers = $taginfo['taggers'];
82189ed97adSAnna Dabrowska            $ns = $taginfo['ns'];
82289ed97adSAnna Dabrowska            $pids = explode(',',$taginfo['pids']);
82389ed97adSAnna Dabrowska
82489ed97adSAnna Dabrowska            $form->addTagOpen('tr');
82526f61833SAnna Dabrowska            $form->addHTML('<td>');
826a2246f69SAnna Dabrowska            $form->addHTML('<a class="tagslist" href="#" data-tid="' . $taginfo['tid'] . '">');
82726f61833SAnna Dabrowska            $form->addHTML( hsc($tagname) . '</a>');
82826f61833SAnna Dabrowska            $form->addHTML('</td>');
82989ed97adSAnna Dabrowska            $form->addHTML('<td>' . $taginfo['count'] . '</td>');
83026c4179dSAnna Dabrowska            if (!$this->conf['hidens']) {
83189ed97adSAnna Dabrowska                $form->addHTML('<td>' . hsc($ns) . '</td>');
83226c4179dSAnna Dabrowska            }
83389ed97adSAnna Dabrowska            $form->addHTML('<td>' . hsc($taggers) . '</td>');
83489ed97adSAnna Dabrowska
83589ed97adSAnna Dabrowska            /**
83689ed97adSAnna Dabrowska             * action buttons
83789ed97adSAnna Dabrowska             */
83889ed97adSAnna Dabrowska            $form->addHTML('<td>');
83989ed97adSAnna Dabrowska
84089ed97adSAnna Dabrowska            // check ACLs
84189ed97adSAnna Dabrowska            $userEdit = false;
84289ed97adSAnna Dabrowska            /** @var \helper_plugin_sqlite $sqliteHelper */
84389ed97adSAnna Dabrowska            $sqliteHelper = plugin_load('helper', 'sqlite');
84489ed97adSAnna Dabrowska            foreach ($pids as $pid) {
84589ed97adSAnna Dabrowska                if ($sqliteHelper->_getAccessLevel($pid) >= AUTH_EDIT) {
84689ed97adSAnna Dabrowska                    $userEdit = true;
84789ed97adSAnna Dabrowska                    continue;
84889ed97adSAnna Dabrowska                }
84989ed97adSAnna Dabrowska            }
85089ed97adSAnna Dabrowska
85189ed97adSAnna Dabrowska            if ($userEdit) {
85226f61833SAnna Dabrowska                $form->addButtonHTML(
853f6568bcbSAnna Dabrowska                    'tagging[actions][rename][' . $taginfo['tid'] . ']',
85426f61833SAnna Dabrowska                    inlineSVG(__DIR__ . '/images/edit.svg'))
85526f61833SAnna Dabrowska                    ->addClass('plugin_tagging action_button')
85626f61833SAnna Dabrowska                    ->attr('data-action', 'rename')
85726f61833SAnna Dabrowska                    ->attr('data-tid', $taginfo['tid']);
85826f61833SAnna Dabrowska                $form->addButtonHTML(
859f6568bcbSAnna Dabrowska                    'tagging[actions][delete][' . $taginfo['tid'] . ']',
86026f61833SAnna Dabrowska                    inlineSVG(__DIR__ . '/images/delete.svg'))
86126f61833SAnna Dabrowska                    ->addClass('plugin_tagging action_button')
86226f61833SAnna Dabrowska                    ->attr('data-action', 'delete')
86326f61833SAnna Dabrowska                    ->attr('data-tid', $taginfo['tid']);
86489ed97adSAnna Dabrowska            }
86589ed97adSAnna Dabrowska
86689ed97adSAnna Dabrowska            $form->addHTML('</td>');
86789ed97adSAnna Dabrowska            $form->addTagClose('tr');
86889ed97adSAnna Dabrowska        }
86989ed97adSAnna Dabrowska
87089ed97adSAnna Dabrowska        $form->addTagClose('table');
8710b033188SAnna Dabrowska        return '<div class="table">' . $form->toHTML() . '</div>';
87289ed97adSAnna Dabrowska    }
87389ed97adSAnna Dabrowska
87489ed97adSAnna Dabrowska    /**
875ec4796e4SAnna Dabrowska     * Display tag cleaner
876ec4796e4SAnna Dabrowska     *
877ec4796e4SAnna Dabrowska     * @return string
878ec4796e4SAnna Dabrowska     */
879ec4796e4SAnna Dabrowska    public function html_clean()
880ec4796e4SAnna Dabrowska    {
881ec4796e4SAnna Dabrowska        $invalid = $this->getInvalidTaggings();
882ec4796e4SAnna Dabrowska
883ec4796e4SAnna Dabrowska        if (!$invalid) {
884ec4796e4SAnna Dabrowska            return '<p><strong>' . $this->getLang('admin no invalid') . '</strong></p>';
885ec4796e4SAnna Dabrowska        }
886ec4796e4SAnna Dabrowska
887ec4796e4SAnna Dabrowska        $form = new Form();
888ec4796e4SAnna Dabrowska        $form->setHiddenField('do', 'admin');
889ec4796e4SAnna Dabrowska        $form->setHiddenField('page', $this->getPluginName());
890ec4796e4SAnna Dabrowska        $form->addButton('cmd[clean]', $this->getLang('admin clean'));
891ec4796e4SAnna Dabrowska
892ec4796e4SAnna Dabrowska        $html = $form->toHTML();
893ec4796e4SAnna Dabrowska
894ec4796e4SAnna Dabrowska        $html .= '<div class="table"><table class="inline plugin_tagging">';
895ec4796e4SAnna Dabrowska        $html .= '<thead><tr><th>' .
896ec4796e4SAnna Dabrowska            $this->getLang('admin nonexistent page') .
897ec4796e4SAnna Dabrowska            '</th><th>' .
898ec4796e4SAnna Dabrowska            $this->getLang('admin tags') .
899ec4796e4SAnna Dabrowska            '</th></tr></thead><tbody>';
900ec4796e4SAnna Dabrowska
901ec4796e4SAnna Dabrowska        foreach ($invalid as $row) {
902ec4796e4SAnna Dabrowska            $html .= '<tr><td>' . $row['pid'] . '</td><td>' . $row['tags'] . '</td></tr>';
903ec4796e4SAnna Dabrowska        }
904ec4796e4SAnna Dabrowska
905ec4796e4SAnna Dabrowska        $html .= '</tbody></table></div>';
906ec4796e4SAnna Dabrowska
907ec4796e4SAnna Dabrowska        return $html;
908ec4796e4SAnna Dabrowska    }
909ec4796e4SAnna Dabrowska
910ec4796e4SAnna Dabrowska    /**
911f6568bcbSAnna Dabrowska     * Returns all tagging parameters from the query string
912f6568bcbSAnna Dabrowska     *
913f6568bcbSAnna Dabrowska     * @return mixed
914f6568bcbSAnna Dabrowska     */
915f6568bcbSAnna Dabrowska    public function getParams()
916f6568bcbSAnna Dabrowska    {
917f6568bcbSAnna Dabrowska        global $INPUT;
918f6568bcbSAnna Dabrowska        return $INPUT->param('tagging', []);
919f6568bcbSAnna Dabrowska    }
920f6568bcbSAnna Dabrowska
921f6568bcbSAnna Dabrowska    /**
922f6568bcbSAnna Dabrowska     * Get a tagging parameter, empty string if not set
923f6568bcbSAnna Dabrowska     *
924f6568bcbSAnna Dabrowska     * @param string $name
925f6568bcbSAnna Dabrowska     * @return mixed
926f6568bcbSAnna Dabrowska     */
927f6568bcbSAnna Dabrowska    public function getParam($name)
928f6568bcbSAnna Dabrowska    {
929f6568bcbSAnna Dabrowska        $params = $this->getParams();
930f6568bcbSAnna Dabrowska        if ($params) {
931f6568bcbSAnna Dabrowska            return $params[$name] ?: '';
932f6568bcbSAnna Dabrowska        }
933f6568bcbSAnna Dabrowska    }
934f6568bcbSAnna Dabrowska
935f6568bcbSAnna Dabrowska    /**
936f6568bcbSAnna Dabrowska     * Sets a tagging parameter
937f6568bcbSAnna Dabrowska     *
938f6568bcbSAnna Dabrowska     * @param string $name
939f6568bcbSAnna Dabrowska     * @param string|array $value
940f6568bcbSAnna Dabrowska     */
941f6568bcbSAnna Dabrowska    public function setParam($name, $value)
942f6568bcbSAnna Dabrowska    {
943f6568bcbSAnna Dabrowska        global $INPUT;
944f6568bcbSAnna Dabrowska        $params = $this->getParams();
945f6568bcbSAnna Dabrowska        $params = array_merge($params, [$name => $value]);
946f6568bcbSAnna Dabrowska        $INPUT->set('tagging', $params);
947f6568bcbSAnna Dabrowska    }
948f6568bcbSAnna Dabrowska
949f6568bcbSAnna Dabrowska    /**
950f6568bcbSAnna Dabrowska     * Default sorting by tag id
951f6568bcbSAnna Dabrowska     */
952f6568bcbSAnna Dabrowska    public function setDefaultSort()
953f6568bcbSAnna Dabrowska    {
954f6568bcbSAnna Dabrowska        if (!$this->getParam('sort')) {
955f6568bcbSAnna Dabrowska            $this->setParam('sort', 'tid');
956f6568bcbSAnna Dabrowska        }
957f6568bcbSAnna Dabrowska    }
958f6568bcbSAnna Dabrowska
959f6568bcbSAnna Dabrowska    /**
9604a7da0a5SAnna Dabrowska     * Executes the query and returns the results as array
9611b4b4fa9SAnna Dabrowska     *
96299122157SAnna Dabrowska     * @param array $query
9631b4b4fa9SAnna Dabrowska     * @return array
9641b4b4fa9SAnna Dabrowska     */
9654a7da0a5SAnna Dabrowska    protected function queryDb($query)
9661b4b4fa9SAnna Dabrowska    {
9674a7da0a5SAnna Dabrowska        $db = $this->getDB();
9684a7da0a5SAnna Dabrowska        if (!$db) {
9694a7da0a5SAnna Dabrowska            return [];
970972f6adfSAnna Dabrowska        }
9714a7da0a5SAnna Dabrowska
97299122157SAnna Dabrowska        $res = $db->query($query[0], $query[1]);
9734a7da0a5SAnna Dabrowska        $res = $db->res2arr($res);
9744a7da0a5SAnna Dabrowska
9754a7da0a5SAnna Dabrowska        $ret = [];
9764a7da0a5SAnna Dabrowska        foreach ($res as $row) {
9774a7da0a5SAnna Dabrowska            $ret[$row['item']] = $row['cnt'];
9784a7da0a5SAnna Dabrowska        }
9794a7da0a5SAnna Dabrowska        return $ret;
980972f6adfSAnna Dabrowska    }
98140b94b1aSAnna Dabrowska
98240b94b1aSAnna Dabrowska    /**
98340b94b1aSAnna Dabrowska     * Construct the HAVING part of the search query
98440b94b1aSAnna Dabrowska     *
98540b94b1aSAnna Dabrowska     * @param array $filters
98640b94b1aSAnna Dabrowska     * @return array
98740b94b1aSAnna Dabrowska     */
98840b94b1aSAnna Dabrowska    protected function getFilterSql($filters)
98940b94b1aSAnna Dabrowska    {
99040b94b1aSAnna Dabrowska        $having = '';
99140b94b1aSAnna Dabrowska        $parts = [];
99240b94b1aSAnna Dabrowska        $params = [];
99340b94b1aSAnna Dabrowska        $filters = array_filter($filters);
99440b94b1aSAnna Dabrowska        if (!empty($filters)) {
99540b94b1aSAnna Dabrowska            $having = ' HAVING ';
99640b94b1aSAnna Dabrowska            foreach ($filters as $filter => $value) {
99740b94b1aSAnna Dabrowska                $parts[] = " $filter LIKE ? ";
99840b94b1aSAnna Dabrowska                $params[] = "%$value%";
99940b94b1aSAnna Dabrowska            }
100040b94b1aSAnna Dabrowska            $having .= implode(' AND ', $parts);
100140b94b1aSAnna Dabrowska        }
100240b94b1aSAnna Dabrowska        return [$having, $params];
100340b94b1aSAnna Dabrowska    }
1004ec4796e4SAnna Dabrowska
1005ec4796e4SAnna Dabrowska    /**
1006ec4796e4SAnna Dabrowska     * Returns taggings of nonexistent pages
1007ec4796e4SAnna Dabrowska     *
1008ec4796e4SAnna Dabrowska     * @return array
1009ec4796e4SAnna Dabrowska     */
1010ec4796e4SAnna Dabrowska    protected function getInvalidTaggings()
1011ec4796e4SAnna Dabrowska    {
1012ec4796e4SAnna Dabrowska        $db = $this->getDB();
1013ec4796e4SAnna Dabrowska        $query = 'SELECT    "pid",
1014ec4796e4SAnna Dabrowska                            GROUP_CONCAT(CLEANTAG("tag")) AS "tags"
1015ec4796e4SAnna Dabrowska                            FROM "taggings"
1016f24ac95eSAnna Dabrowska                            WHERE NOT PAGEEXISTS(pid)
1017ec4796e4SAnna Dabrowska                            GROUP BY pid
1018ec4796e4SAnna Dabrowska                 ';
1019ec4796e4SAnna Dabrowska        $res = $db->query($query);
1020ec4796e4SAnna Dabrowska        return $db->res2arr($res);
1021ec4796e4SAnna Dabrowska    }
1022cd08599fSAnna Dabrowska}
1023