xref: /plugin/tagging/helper.php (revision ec4796e489f2a3088158205d5fecf50b486ecea2)
1f61105deSAdrian Lang<?php
2*ec4796e4SAnna Dabrowska
3*ec4796e4SAnna Dabrowskause dokuwiki\Form\Form;
4*ec4796e4SAnna 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    /**
65302a38efSAndreas Gohr     * Canonicalizes the tag to its lower case nospace form
66302a38efSAndreas Gohr     *
67302a38efSAndreas Gohr     * @param $tag
680cfde7e9SMichael Große     *
69302a38efSAndreas Gohr     * @return string
70302a38efSAndreas Gohr     */
71302a38efSAndreas Gohr    public function cleanTag($tag) {
72aa627deeSAndreas Gohr        $tag = str_replace(array(' ', '-', '_'), '', $tag);
73302a38efSAndreas Gohr        $tag = utf8_strtolower($tag);
74ca455b8eSMichael Große
75302a38efSAndreas Gohr        return $tag;
76302a38efSAndreas Gohr    }
77302a38efSAndreas Gohr
7856d82720SAndreas Gohr    /**
7931396860SSzymon Olewniczak     * Canonicalizes the namespace, remove the first colon and add glob
8031396860SSzymon Olewniczak     *
8131396860SSzymon Olewniczak     * @param $namespace
8231396860SSzymon Olewniczak     *
8331396860SSzymon Olewniczak     * @return string
8431396860SSzymon Olewniczak     */
8531396860SSzymon Olewniczak    public function globNamespace($namespace) {
86de379874SAnna Dabrowska        return cleanId($namespace) . '*';
8731396860SSzymon Olewniczak    }
8831396860SSzymon Olewniczak
8931396860SSzymon Olewniczak    /**
9056d82720SAndreas Gohr     * Create or Update tags of a page
9156d82720SAndreas Gohr     *
9256d82720SAndreas Gohr     * Uses the translation plugin to store the language of a page (if available)
9356d82720SAndreas Gohr     *
9456d82720SAndreas Gohr     * @param string $id The page ID
9556d82720SAndreas Gohr     * @param string $user
9656d82720SAndreas Gohr     * @param array  $tags
970cfde7e9SMichael Große     *
9856d82720SAndreas Gohr     * @return bool|SQLiteResult
9956d82720SAndreas Gohr     */
100f61105deSAdrian Lang    public function replaceTags($id, $user, $tags) {
10156d82720SAndreas Gohr        global $conf;
10256d82720SAndreas Gohr        /** @var helper_plugin_translation $trans */
10356d82720SAndreas Gohr        $trans = plugin_load('helper', 'translation');
10456d82720SAndreas Gohr        if ($trans) {
10556d82720SAndreas Gohr            $lang = $trans->realLC($trans->getLangPart($id));
10656d82720SAndreas Gohr        } else {
10756d82720SAndreas Gohr            $lang = $conf['lang'];
10856d82720SAndreas Gohr        }
10956d82720SAndreas Gohr
110f61105deSAdrian Lang        $db = $this->getDB();
111f61105deSAdrian Lang        $db->query('BEGIN TRANSACTION');
112f61105deSAdrian Lang        $queries = array(array('DELETE FROM taggings WHERE pid = ? AND tagger = ?', $id, $user));
113f61105deSAdrian Lang        foreach ($tags as $tag) {
11456d82720SAndreas Gohr            $queries[] = array('INSERT INTO taggings (pid, tagger, tag, lang) VALUES(?, ?, ?, ?)', $id, $user, $tag, $lang);
115f61105deSAdrian Lang        }
116f61105deSAdrian Lang
117f61105deSAdrian Lang        foreach ($queries as $query) {
118f61105deSAdrian Lang            if (!call_user_func_array(array($db, 'query'), $query)) {
119f61105deSAdrian Lang                $db->query('ROLLBACK TRANSACTION');
120ca455b8eSMichael Große
121f61105deSAdrian Lang                return false;
122f61105deSAdrian Lang            }
123f61105deSAdrian Lang        }
124ca455b8eSMichael Große
125f61105deSAdrian Lang        return $db->query('COMMIT TRANSACTION');
126f61105deSAdrian Lang    }
127f61105deSAdrian Lang
1280a518a11SAndreas Gohr    /**
129b12334e1SAndreas Gohr     * Get a list of Tags or Pages matching search criteria
1300a518a11SAndreas Gohr     *
131b12334e1SAndreas Gohr     * @param array  $filter What to search for array('field' => 'searchterm')
132b12334e1SAndreas Gohr     * @param string $type   What field to return 'tag'|'pid'
133077ff864SAndreas Gohr     * @param int    $limit  Limit to this many results, 0 for all
1340cfde7e9SMichael Große     *
1350a518a11SAndreas Gohr     * @return array associative array in form of value => count
1360a518a11SAndreas Gohr     */
137077ff864SAndreas Gohr    public function findItems($filter, $type, $limit = 0) {
138f61105deSAdrian Lang
1394a7da0a5SAnna Dabrowska        global $INPUT;
140b12334e1SAndreas Gohr
1414a7da0a5SAnna Dabrowska        /** @var helper_plugin_tagging_querybuilder $queryBuilder */
14265d49a60SAnna Dabrowska        $queryBuilder = new \helper_plugin_tagging_querybuilder();
1431b4b4fa9SAnna Dabrowska
1444a7da0a5SAnna Dabrowska        $queryBuilder->setField($type);
1454a7da0a5SAnna Dabrowska        $queryBuilder->setLimit($limit);
1464a7da0a5SAnna Dabrowska        $queryBuilder->setTags($this->getTags($filter));
1474a7da0a5SAnna Dabrowska        if (isset($filter['ns'])) $queryBuilder->includeNS($filter['ns']);
1484a7da0a5SAnna Dabrowska        if (isset($filter['notns'])) $queryBuilder->excludeNS($filter['notns']);
1494a7da0a5SAnna Dabrowska        if (isset($filter['tagger'])) $queryBuilder->setTagger($filter['tagger']);
1504a7da0a5SAnna Dabrowska        if (isset($filter['pid'])) $queryBuilder->setPid($filter['pid']);
151b12334e1SAndreas Gohr
1524a7da0a5SAnna Dabrowska        return $this->queryDb($queryBuilder->getQuery());
153ca455b8eSMichael Große
154f61105deSAdrian Lang    }
155f61105deSAdrian Lang
156b12334e1SAndreas Gohr    /**
157302a38efSAndreas Gohr     * Constructs the URL to search for a tag
158302a38efSAndreas Gohr     *
1595540f91dSAndreas Gohr     * @param string $tag
1605540f91dSAndreas Gohr     * @param string $ns
1610cfde7e9SMichael Große     *
162302a38efSAndreas Gohr     * @return string
163302a38efSAndreas Gohr     */
1645540f91dSAndreas Gohr    public function getTagSearchURL($tag, $ns = '') {
165bed9f360SAndreas Gohr        // wrap tag in quotes if non clean
1662cb25ec1SAndreas Gohr        $ctag = utf8_stripspecials($this->cleanTag($tag));
1670cfde7e9SMichael Große        if ($ctag != utf8_strtolower($tag)) {
1680cfde7e9SMichael Große            $tag = '"' . $tag . '"';
1690cfde7e9SMichael Große        }
170bed9f360SAndreas Gohr
171018370fcSsheesupport        $ret = '?do=search&sf=1&id=' . rawurlencode($tag);
1720cfde7e9SMichael Große        if ($ns) {
1730cfde7e9SMichael Große            $ret .= rawurlencode(' @' . $ns);
1740cfde7e9SMichael Große        }
1755540f91dSAndreas Gohr
1765540f91dSAndreas Gohr        return $ret;
177f61105deSAdrian Lang    }
178f61105deSAdrian Lang
1795540f91dSAndreas Gohr    /**
1805540f91dSAndreas Gohr     * Calculates the size levels for the given list of clouds
1815540f91dSAndreas Gohr     *
1825540f91dSAndreas Gohr     * Automatically determines sensible tresholds
1835540f91dSAndreas Gohr     *
1845540f91dSAndreas Gohr     * @param array $tags list of tags => count
1855540f91dSAndreas Gohr     * @param int   $levels
1860cfde7e9SMichael Große     *
1875540f91dSAndreas Gohr     * @return mixed
1885540f91dSAndreas Gohr     */
189f61105deSAdrian Lang    public function cloudData($tags, $levels = 10) {
190f61105deSAdrian Lang        $min = min($tags);
191f61105deSAdrian Lang        $max = max($tags);
192f61105deSAdrian Lang
193f61105deSAdrian Lang        // calculate tresholds
194f61105deSAdrian Lang        $tresholds = array();
195f61105deSAdrian Lang        for ($i = 0; $i <= $levels; $i++) {
196f61105deSAdrian Lang            $tresholds[$i] = pow($max - $min + 1, $i / $levels) + $min - 1;
197f61105deSAdrian Lang        }
198f61105deSAdrian Lang
199f61105deSAdrian Lang        // assign weights
200f61105deSAdrian Lang        foreach ($tags as $tag => $cnt) {
201f61105deSAdrian Lang            foreach ($tresholds as $tresh => $val) {
202f61105deSAdrian Lang                if ($cnt <= $val) {
203f61105deSAdrian Lang                    $tags[$tag] = $tresh;
204f61105deSAdrian Lang                    break;
205f61105deSAdrian Lang                }
206f61105deSAdrian Lang                $tags[$tag] = $levels;
207f61105deSAdrian Lang            }
208f61105deSAdrian Lang        }
209ca455b8eSMichael Große
210f61105deSAdrian Lang        return $tags;
211f61105deSAdrian Lang    }
212f61105deSAdrian Lang
2135540f91dSAndreas Gohr    /**
2145540f91dSAndreas Gohr     * Display a tag cloud
2155540f91dSAndreas Gohr     *
2165540f91dSAndreas Gohr     * @param array    $tags   list of tags => count
2175540f91dSAndreas Gohr     * @param string   $type   'tag'
2185540f91dSAndreas Gohr     * @param Callable $func   The function to print the link (gets tag and ns)
2195540f91dSAndreas Gohr     * @param bool     $wrap   wrap cloud in UL tags?
2205540f91dSAndreas Gohr     * @param bool     $return returnn HTML instead of printing?
2215540f91dSAndreas Gohr     * @param string   $ns     Add this namespace to search links
2220cfde7e9SMichael Große     *
2235540f91dSAndreas Gohr     * @return string
2245540f91dSAndreas Gohr     */
2255540f91dSAndreas Gohr    public function html_cloud($tags, $type, $func, $wrap = true, $return = false, $ns = '') {
226a66f6715SAndreas Gohr        global $INFO;
227a66f6715SAndreas Gohr
228a66f6715SAndreas Gohr        $hidden_str = $this->getConf('hiddenprefix');
229a66f6715SAndreas Gohr        $hidden_len = strlen($hidden_str);
230a66f6715SAndreas Gohr
231f61105deSAdrian Lang        $ret = '';
2320cfde7e9SMichael Große        if ($wrap) {
2330cfde7e9SMichael Große            $ret .= '<ul class="tagging_cloud clearfix">';
2340cfde7e9SMichael Große        }
235f61105deSAdrian Lang        if (count($tags) === 0) {
236f61105deSAdrian Lang            // Produce valid XHTML (ul needs a child)
237f61105deSAdrian Lang            $this->setupLocale();
238f61105deSAdrian Lang            $ret .= '<li><div class="li">' . $this->lang['js']['no' . $type . 's'] . '</div></li>';
239f61105deSAdrian Lang        } else {
240f61105deSAdrian Lang            $tags = $this->cloudData($tags);
241f61105deSAdrian Lang            foreach ($tags as $val => $size) {
242a66f6715SAndreas Gohr                // skip hidden tags for users that can't edit
243aa627deeSAndreas Gohr                if ($type === 'tag' and
244a66f6715SAndreas Gohr                    $hidden_len and
245a66f6715SAndreas Gohr                    substr($val, 0, $hidden_len) == $hidden_str and
246a66f6715SAndreas Gohr                    !($this->getUser() && $INFO['writable'])
247a66f6715SAndreas Gohr                ) {
248a66f6715SAndreas Gohr                    continue;
249a66f6715SAndreas Gohr                }
250a66f6715SAndreas Gohr
251f61105deSAdrian Lang                $ret .= '<li class="t' . $size . '"><div class="li">';
2525540f91dSAndreas Gohr                $ret .= call_user_func($func, $val, $ns);
253f61105deSAdrian Lang                $ret .= '</div></li>';
254f61105deSAdrian Lang            }
255f61105deSAdrian Lang        }
2560cfde7e9SMichael Große        if ($wrap) {
2570cfde7e9SMichael Große            $ret .= '</ul>';
2580cfde7e9SMichael Große        }
2590cfde7e9SMichael Große        if ($return) {
2600cfde7e9SMichael Große            return $ret;
2610cfde7e9SMichael Große        }
262f61105deSAdrian Lang        echo $ret;
263ca455b8eSMichael Große
2645540f91dSAndreas Gohr        return '';
265f61105deSAdrian Lang    }
266f61105deSAdrian Lang
2675540f91dSAndreas Gohr    /**
2685540f91dSAndreas Gohr     * Get the link to a search for the given tag
2695540f91dSAndreas Gohr     *
2705540f91dSAndreas Gohr     * @param string $tag search for this tag
2715540f91dSAndreas Gohr     * @param string $ns  limit search to this namespace
2720cfde7e9SMichael Große     *
2735540f91dSAndreas Gohr     * @return string
2745540f91dSAndreas Gohr     */
2755540f91dSAndreas Gohr    protected function linkToSearch($tag, $ns = '') {
2765540f91dSAndreas Gohr        return '<a href="' . hsc($this->getTagSearchURL($tag, $ns)) . '">' . $tag . '</a>';
277f61105deSAdrian Lang    }
278f61105deSAdrian Lang
279fb1d0583SAndreas Gohr    /**
280fb1d0583SAndreas Gohr     * Display the Tags for the current page and prepare the tag editing form
2813496cc8aSAndreas Gohr     *
2823496cc8aSAndreas Gohr     * @param bool $print Should the HTML be printed or returned?
2830cfde7e9SMichael Große     *
2843496cc8aSAndreas Gohr     * @return string
285fb1d0583SAndreas Gohr     */
2863496cc8aSAndreas Gohr    public function tpl_tags($print = true) {
287f61105deSAdrian Lang        global $INFO;
288f61105deSAdrian Lang        global $lang;
2893bf0e2f1SMichael Große
2903bf0e2f1SMichael Große        $filter = array('pid' => $INFO['id']);
2913bf0e2f1SMichael Große        if ($this->getConf('singleusermode')) {
2923bf0e2f1SMichael Große            $filter['tagger'] = 'auto';
2933bf0e2f1SMichael Große        }
2943bf0e2f1SMichael Große
2953bf0e2f1SMichael Große        $tags = $this->findItems($filter, 'tag');
2963496cc8aSAndreas Gohr
2973496cc8aSAndreas Gohr        $ret = '';
2983496cc8aSAndreas Gohr
2993496cc8aSAndreas Gohr        $ret .= '<div class="plugin_tagging_edit">';
3003496cc8aSAndreas Gohr        $ret .= $this->html_cloud($tags, 'tag', array($this, 'linkToSearch'), true, true);
301f61105deSAdrian Lang
3022ace74f4SAndreas Gohr        if ($this->getUser() && $INFO['writable']) {
303f61105deSAdrian Lang            $lang['btn_tagging_edit'] = $lang['btn_secedit'];
304e5b42768SSzymon Olewniczak            $ret .= '<div id="tagging__edit_buttons_group">';
3053496cc8aSAndreas Gohr            $ret .= html_btn('tagging_edit', $INFO['id'], '', array());
306dd52fd45SSzymon Olewniczak            if (auth_isadmin()) {
30726f61833SAnna Dabrowska                $ret .= '<label>'
30826f61833SAnna Dabrowska                    . $this->getLang('toggle admin mode')
30926f61833SAnna Dabrowska                    . '<input type="checkbox" id="tagging__edit_toggle_admin" /></label>';
310dd52fd45SSzymon Olewniczak            }
311e5b42768SSzymon Olewniczak            $ret .= '</div>';
3122819ffcdSSzymon Olewniczak            $form = new dokuwiki\Form\Form();
3132819ffcdSSzymon Olewniczak            $form->id('tagging__edit');
3142819ffcdSSzymon Olewniczak            $form->setHiddenField('tagging[id]', $INFO['id']);
3152819ffcdSSzymon Olewniczak            $form->setHiddenField('call', 'plugin_tagging_save');
3162819ffcdSSzymon Olewniczak            $tags = $this->findItems(array(
3172819ffcdSSzymon Olewniczak                'pid'    => $INFO['id'],
318ca455b8eSMichael Große                'tagger' => $this->getUser(),
3192819ffcdSSzymon Olewniczak            ), 'tag');
32026f61833SAnna Dabrowska            $form->addTextarea('tagging[tags]')
32126f61833SAnna Dabrowska                ->val(implode(', ', array_keys($tags)))
32226f61833SAnna Dabrowska                ->addClass('edit')
32326f61833SAnna Dabrowska                ->attr('rows', 4);
324cf52ec2dSSzymon Olewniczak            $form->addButton('', $lang['btn_save'])->id('tagging__edit_save');
325cf52ec2dSSzymon Olewniczak            $form->addButton('', $lang['btn_cancel'])->id('tagging__edit_cancel');
3262819ffcdSSzymon Olewniczak            $ret .= $form->toHTML();
327f61105deSAdrian Lang        }
3283496cc8aSAndreas Gohr        $ret .= '</div>';
3293496cc8aSAndreas Gohr
3300cfde7e9SMichael Große        if ($print) {
3310cfde7e9SMichael Große            echo $ret;
3320cfde7e9SMichael Große        }
333ca455b8eSMichael Große
3343496cc8aSAndreas Gohr        return $ret;
335f61105deSAdrian Lang    }
336872edc7cSRené Corinth
3378a1a3846SAndreas Gohr    /**
338a99b66c1SSzymon Olewniczak     * @param string $namespace empty for entire wiki
339a99b66c1SSzymon Olewniczak     *
34040b94b1aSAnna Dabrowska     * @param string $order_by
34140b94b1aSAnna Dabrowska     * @param bool $desc
34240b94b1aSAnna Dabrowska     * @param array $filters
3438a1a3846SAndreas Gohr     * @return array
3448a1a3846SAndreas Gohr     */
345f6568bcbSAnna Dabrowska    public function getAllTags($namespace = '', $order_by = 'tid', $desc = false, $filters = []) {
3467c96ae87SAnna Dabrowska        $order_fields = array('pid', 'tid', 'taggers', 'ns', 'count');
347f0084ee1SSzymon Olewniczak        if (!in_array($order_by, $order_fields)) {
348f0084ee1SSzymon Olewniczak            msg('cannot sort by ' . $order_by . ' field does not exists', -1);
349f0084ee1SSzymon Olewniczak            $order_by = 'tag';
350f0084ee1SSzymon Olewniczak        }
351872edc7cSRené Corinth
35240b94b1aSAnna Dabrowska        list($having, $params) = $this->getFilterSql($filters);
35340b94b1aSAnna Dabrowska
354a2246f69SAnna Dabrowska        $db = $this->getDB();
355872edc7cSRené Corinth
356f0084ee1SSzymon Olewniczak        $query = 'SELECT    "pid",
357ca455b8eSMichael Große                            CLEANTAG("tag") AS "tid",
358f0084ee1SSzymon Olewniczak                            GROUP_SORT(GROUP_CONCAT("tagger"), \', \') AS "taggers",
35940b94b1aSAnna Dabrowska                            GROUP_SORT(GROUP_CONCAT(GET_NS("pid")), \', \') AS "ns",
36089ed97adSAnna Dabrowska                            GROUP_SORT(GROUP_CONCAT("pid"), \', \') AS "pids",
361193a767dSSzymon Olewniczak                            COUNT(*) AS "count"
36257e45304SSzymon Olewniczak                        FROM "taggings"
3634227fca4SAnna Dabrowska                        WHERE "pid" GLOB ? AND GETACCESSLEVEL(pid) >= ' . AUTH_READ
3644227fca4SAnna Dabrowska                        . ' GROUP BY "tid"';
36540b94b1aSAnna Dabrowska        $query .= $having;
36640b94b1aSAnna Dabrowska        $query .=      'ORDER BY ' . $order_by;
367ca455b8eSMichael Große        if ($desc) {
368ca455b8eSMichael Große            $query .= ' DESC';
369ca455b8eSMichael Große        }
370cb469644SSzymon Olewniczak
37140b94b1aSAnna Dabrowska        array_unshift($params, $this->globNamespace($namespace));
37240b94b1aSAnna Dabrowska        $res = $db->query($query, $params);
373872edc7cSRené Corinth
3747e05e623SSzymon Olewniczak        return $db->res2arr($res);
375872edc7cSRené Corinth    }
376872edc7cSRené Corinth
3778a1a3846SAndreas Gohr    /**
37872431326SMichael Große     * Get all pages with tags and their tags
37972431326SMichael Große     *
380790ca788SAndreas Gohr     * @return array ['pid' => ['tag1','tag2','tag3']]
38172431326SMichael Große     */
38272431326SMichael Große    public function getAllTagsByPage() {
38372431326SMichael Große        $query = '
38472431326SMichael Große        SELECT pid, GROUP_CONCAT(tag) AS tags
38572431326SMichael Große        FROM taggings
38672431326SMichael Große        GROUP BY pid
38772431326SMichael Große        ';
38872431326SMichael Große        $db = $this->getDb();
38972431326SMichael Große        $res = $db->query($query);
390790ca788SAndreas Gohr        return array_map(
391790ca788SAndreas Gohr            function ($i) {
392790ca788SAndreas Gohr                return explode(',', $i);
393790ca788SAndreas Gohr            },
394790ca788SAndreas Gohr            array_column($db->res2arr($res), 'tags', 'pid')
395790ca788SAndreas Gohr        );
39672431326SMichael Große    }
39772431326SMichael Große
39872431326SMichael Große    /**
3998a1a3846SAndreas Gohr     * Renames a tag
4008a1a3846SAndreas Gohr     *
4018a1a3846SAndreas Gohr     * @param string $formerTagName
4024227fca4SAnna Dabrowska     * @param string $newTagNames
4038a1a3846SAndreas Gohr     */
4044227fca4SAnna Dabrowska    public function renameTag($formerTagName, $newTagNames) {
405872edc7cSRené Corinth
4064227fca4SAnna Dabrowska        if (empty($formerTagName) || empty($newTagNames)) {
4078a1a3846SAndreas Gohr            msg($this->getLang("admin enter tag names"), -1);
4088a1a3846SAndreas Gohr            return;
409872edc7cSRené Corinth        }
410872edc7cSRené Corinth
411870d77ddSAnna Dabrowska        $keepFormerTag = false;
412870d77ddSAnna Dabrowska
4134227fca4SAnna Dabrowska        // enable splitting tags on rename
414870d77ddSAnna Dabrowska        $newTagNames = array_map(function ($tag) {
415870d77ddSAnna Dabrowska            return $this->cleanTag($tag);
416870d77ddSAnna Dabrowska        }, explode(',', $newTagNames));
4174227fca4SAnna Dabrowska
4184227fca4SAnna Dabrowska        $db = $this->getDB();
419872edc7cSRené Corinth
4204227fca4SAnna Dabrowska        // non-admins can rename only their own tags
4214227fca4SAnna Dabrowska        if (!auth_isadmin()) {
4224227fca4SAnna Dabrowska            $queryTagger =' AND tagger = ?';
4234227fca4SAnna Dabrowska            $tagger = $this->getUser();
4244227fca4SAnna Dabrowska        } else {
4254227fca4SAnna Dabrowska            $queryTagger = '';
4264227fca4SAnna Dabrowska            $tagger = '';
4274227fca4SAnna Dabrowska        }
4284227fca4SAnna Dabrowska
4290ec63874SAnna Dabrowska        $insertQuery = 'INSERT INTO taggings ';
4300ec63874SAnna Dabrowska        $insertQuery .= 'SELECT pid, ?, tagger, lang FROM taggings';
4310ec63874SAnna Dabrowska        $where = ' WHERE CLEANTAG(tag) = ?';
4320ec63874SAnna Dabrowska        $where .= ' AND GETACCESSLEVEL(pid) >= ' . AUTH_EDIT;
4330ec63874SAnna Dabrowska        $where .= $queryTagger;
4340ec63874SAnna Dabrowska
4350ec63874SAnna Dabrowska        $db->query('BEGIN TRANSACTION');
4360ec63874SAnna Dabrowska
4370ec63874SAnna Dabrowska        // insert new tags first
4380ec63874SAnna Dabrowska        foreach ($newTagNames as $newTag) {
439870d77ddSAnna Dabrowska            if ($newTag === $this->cleanTag($formerTagName)) {
440870d77ddSAnna Dabrowska                $keepFormerTag = true;
441870d77ddSAnna Dabrowska                continue;
442870d77ddSAnna Dabrowska            }
443870d77ddSAnna Dabrowska            $params = [$newTag, $this->cleanTag($formerTagName)];
4444227fca4SAnna Dabrowska            if ($tagger) array_push($params, $tagger);
4450ec63874SAnna Dabrowska            $res = $db->query($insertQuery . $where, $params);
4460ec63874SAnna Dabrowska            if ($res === false) {
4470ec63874SAnna Dabrowska                $db->query('ROLLBACK TRANSACTION');
4480ec63874SAnna Dabrowska                return;
4494227fca4SAnna Dabrowska            }
4500ec63874SAnna Dabrowska            $db->res_close($res);
4510ec63874SAnna Dabrowska        }
4520ec63874SAnna Dabrowska
453870d77ddSAnna Dabrowska        // finally delete the renamed tags
454870d77ddSAnna Dabrowska        if (!$keepFormerTag) {
4550ec63874SAnna Dabrowska            $deleteQuery = 'DELETE FROM taggings';
4560ec63874SAnna Dabrowska            $params = [$this->cleanTag($formerTagName)];
4570ec63874SAnna Dabrowska            if ($tagger) array_push($params, $tagger);
4580ec63874SAnna Dabrowska            if ($db->query($deleteQuery . $where, $params) === false) {
4590ec63874SAnna Dabrowska                $db->query('ROLLBACK TRANSACTION');
4600ec63874SAnna Dabrowska                return;
4610ec63874SAnna Dabrowska            }
462870d77ddSAnna Dabrowska        }
4630ec63874SAnna Dabrowska
4640ec63874SAnna Dabrowska        $db->query('COMMIT TRANSACTION');
465872edc7cSRené Corinth
466fb1d0583SAndreas Gohr        msg($this->getLang("admin renamed"), 1);
467ca455b8eSMichael Große
4688a1a3846SAndreas Gohr        return;
469872edc7cSRené Corinth    }
470872edc7cSRené Corinth
4718f630140SSzymon Olewniczak    /**
472dd52fd45SSzymon Olewniczak     * Rename or delete a tag for all users
473dd52fd45SSzymon Olewniczak     *
474dd52fd45SSzymon Olewniczak     * @param string $pid
475dd52fd45SSzymon Olewniczak     * @param string $formerTagName
476dd52fd45SSzymon Olewniczak     * @param string $newTagName
477dd52fd45SSzymon Olewniczak     *
478dd52fd45SSzymon Olewniczak     * @return array
479dd52fd45SSzymon Olewniczak     */
480dd52fd45SSzymon Olewniczak    public function modifyPageTag($pid, $formerTagName, $newTagName) {
481dd52fd45SSzymon Olewniczak
482dd52fd45SSzymon Olewniczak        $db = $this->getDb();
483dd52fd45SSzymon Olewniczak
48426f61833SAnna Dabrowska        $res = $db->query(
48526f61833SAnna Dabrowska            'SELECT pid FROM taggings WHERE CLEANTAG(tag) = ? AND pid = ?',
48626f61833SAnna Dabrowska            $this->cleanTag($formerTagName),
48726f61833SAnna Dabrowska            $pid
48826f61833SAnna Dabrowska        );
489dd52fd45SSzymon Olewniczak        $check = $db->res2arr($res);
490dd52fd45SSzymon Olewniczak
491dd52fd45SSzymon Olewniczak        if (empty($check)) {
492dd52fd45SSzymon Olewniczak            return array(true, $this->getLang('admin tag does not exists'));
493dd52fd45SSzymon Olewniczak        }
494dd52fd45SSzymon Olewniczak
495dd52fd45SSzymon Olewniczak        if (empty($newTagName)) {
49626f61833SAnna Dabrowska            $res = $db->query(
49726f61833SAnna Dabrowska                'DELETE FROM taggings WHERE pid = ? AND CLEANTAG(tag) = ?',
49826f61833SAnna Dabrowska                $pid,
49926f61833SAnna Dabrowska                $this->cleanTag($formerTagName)
50026f61833SAnna Dabrowska            );
501dd52fd45SSzymon Olewniczak        } else {
50226f61833SAnna Dabrowska            $res = $db->query(
50326f61833SAnna Dabrowska                'UPDATE taggings SET tag = ? WHERE pid = ? AND CLEANTAG(tag) = ?',
50426f61833SAnna Dabrowska                $newTagName,
50526f61833SAnna Dabrowska                $pid,
50626f61833SAnna Dabrowska                $this->cleanTag($formerTagName)
50726f61833SAnna Dabrowska            );
508dd52fd45SSzymon Olewniczak        }
509dd52fd45SSzymon Olewniczak        $db->res2arr($res);
510dd52fd45SSzymon Olewniczak
511dd52fd45SSzymon Olewniczak        return array(false, $this->getLang('admin renamed'));
512dd52fd45SSzymon Olewniczak    }
513dd52fd45SSzymon Olewniczak
514dd52fd45SSzymon Olewniczak    /**
5158f630140SSzymon Olewniczak     * Deletes a tag
5168f630140SSzymon Olewniczak     *
5178f630140SSzymon Olewniczak     * @param array  $tags
51831396860SSzymon Olewniczak     * @param string $namespace current namespace context as in getAllTags()
5198f630140SSzymon Olewniczak     */
52031396860SSzymon Olewniczak    public function deleteTags($tags, $namespace = '') {
521ca455b8eSMichael Große        if (empty($tags)) {
522ca455b8eSMichael Große            return;
523ca455b8eSMichael Große        }
5248f630140SSzymon Olewniczak
52531396860SSzymon Olewniczak        $namespace = cleanId($namespace);
52631396860SSzymon Olewniczak
5271f5991a7SMichael Große        $db = $this->getDB();
5288f630140SSzymon Olewniczak
529de379874SAnna Dabrowska        $queryBody = 'FROM taggings WHERE pid GLOB ? AND (' .
53031396860SSzymon Olewniczak            implode(' OR ', array_fill(0, count($tags), 'CLEANTAG(tag) = ?')) . ')';
53131396860SSzymon Olewniczak        $args = array_map(array($this, 'cleanTag'), $tags);
53231396860SSzymon Olewniczak        array_unshift($args, $this->globNamespace($namespace));
5338f630140SSzymon Olewniczak
5344227fca4SAnna Dabrowska        // non-admins can delete only their own tags
5354227fca4SAnna Dabrowska        if (!auth_isadmin()) {
5364227fca4SAnna Dabrowska            $queryBody .= ' AND tagger = ?';
5374227fca4SAnna Dabrowska            array_push($args, $this->getUser());
5384227fca4SAnna Dabrowska        }
539ca455b8eSMichael Große
5401f5991a7SMichael Große        $affectedPagesQuery= 'SELECT DISTINCT pid ' . $queryBody;
5411f5991a7SMichael Große        $resAffectedPages = $db->query($affectedPagesQuery, $args);
5421f5991a7SMichael Große        $numAffectedPages = count($resAffectedPages->fetchAll());
5431f5991a7SMichael Große
5441f5991a7SMichael Große        $deleteQuery = 'DELETE ' . $queryBody;
5451f5991a7SMichael Große        $db->query($deleteQuery, $args);
5461f5991a7SMichael Große
5471f5991a7SMichael Große        msg(sprintf($this->getLang("admin deleted"), count($tags), $numAffectedPages), 1);
5488f630140SSzymon Olewniczak    }
549cd08599fSAnna Dabrowska
550cd08599fSAnna Dabrowska    /**
551*ec4796e4SAnna Dabrowska     * Delete taggings of nonexistent pages
552*ec4796e4SAnna Dabrowska     */
553*ec4796e4SAnna Dabrowska    public function deleteInvalidTaggings()
554*ec4796e4SAnna Dabrowska    {
555*ec4796e4SAnna Dabrowska        $db = $this->getDB();
556*ec4796e4SAnna Dabrowska        $query = 'DELETE    FROM "taggings"
557*ec4796e4SAnna Dabrowska                            WHERE PAGEEXISTS(pid) IS FALSE
558*ec4796e4SAnna Dabrowska                 ';
559*ec4796e4SAnna Dabrowska        $res = $db->query($query);
560*ec4796e4SAnna Dabrowska        $db->res_close($res);
561*ec4796e4SAnna Dabrowska    }
562*ec4796e4SAnna Dabrowska
563*ec4796e4SAnna Dabrowska    /**
564cd08599fSAnna Dabrowska     * Updates tags with a new page name
565cd08599fSAnna Dabrowska     *
566cd08599fSAnna Dabrowska     * @param string $oldName
567cd08599fSAnna Dabrowska     * @param string $newName
568cd08599fSAnna Dabrowska     */
569cd08599fSAnna Dabrowska    public function renamePage($oldName, $newName) {
570f6568bcbSAnna Dabrowska        $db = $this->getDB();
571cd08599fSAnna Dabrowska        $db->query('UPDATE taggings SET pid = ? WHERE pid = ?', $newName, $oldName);
572cd08599fSAnna Dabrowska    }
573972f6adfSAnna Dabrowska
574972f6adfSAnna Dabrowska    /**
5751b4b4fa9SAnna Dabrowska     * Extracts tags from search query
5761b4b4fa9SAnna Dabrowska     *
5771b4b4fa9SAnna Dabrowska     * @param array $parsedQuery
5781b4b4fa9SAnna Dabrowska     * @return array
5791b4b4fa9SAnna Dabrowska     */
5801b4b4fa9SAnna Dabrowska    public function getTags($parsedQuery)
5811b4b4fa9SAnna Dabrowska    {
5821b4b4fa9SAnna Dabrowska        $tags = [];
5831b4b4fa9SAnna Dabrowska        if (isset($parsedQuery['phrases'][0])) {
5841b4b4fa9SAnna Dabrowska            $tags = $parsedQuery['phrases'];
5851b4b4fa9SAnna Dabrowska        } elseif (isset($parsedQuery['and'][0])) {
5861b4b4fa9SAnna Dabrowska            $tags = $parsedQuery['and'];
5871b4b4fa9SAnna Dabrowska        } elseif (isset($parsedQuery['tag'])) {
5881b4b4fa9SAnna Dabrowska            // handle autocomplete call
5891b4b4fa9SAnna Dabrowska            $tags[] = $parsedQuery['tag'];
5901b4b4fa9SAnna Dabrowska        }
5911b4b4fa9SAnna Dabrowska        return $tags;
5921b4b4fa9SAnna Dabrowska    }
5931b4b4fa9SAnna Dabrowska
5941b4b4fa9SAnna Dabrowska    /**
5954a7da0a5SAnna Dabrowska     * Search for tagged pages
596972f6adfSAnna Dabrowska     *
5974a7da0a5SAnna Dabrowska     * @return array
598972f6adfSAnna Dabrowska     */
5994a7da0a5SAnna Dabrowska    public function searchPages()
600972f6adfSAnna Dabrowska    {
6011b4b4fa9SAnna Dabrowska        global $INPUT;
6021b4b4fa9SAnna Dabrowska        global $QUERY;
6034a7da0a5SAnna Dabrowska        $parsedQuery = ft_queryParser(new Doku_Indexer(), $QUERY);
604972f6adfSAnna Dabrowska
6051b4b4fa9SAnna Dabrowska        /** @var helper_plugin_tagging_querybuilder $queryBuilder */
60665d49a60SAnna Dabrowska        $queryBuilder = new \helper_plugin_tagging_querybuilder();
607972f6adfSAnna Dabrowska
6084a7da0a5SAnna Dabrowska        $queryBuilder->setField('pid');
6094a7da0a5SAnna Dabrowska        $queryBuilder->setTags($this->getTags($parsedQuery));
6104a7da0a5SAnna Dabrowska        $queryBuilder->setLogicalAnd($INPUT->str('taggings') === 'and');
6114a7da0a5SAnna Dabrowska        if (isset($parsedQuery['ns'])) $queryBuilder->includeNS($parsedQuery['ns']);
6124a7da0a5SAnna Dabrowska        if (isset($parsedQuery['notns'])) $queryBuilder->excludeNS($parsedQuery['notns']);
6134a7da0a5SAnna Dabrowska        if (isset($parsedQuery['tagger'])) $queryBuilder->setTagger($parsedQuery['tagger']);
6144a7da0a5SAnna Dabrowska        if (isset($parsedQuery['pid'])) $queryBuilder->setPid($parsedQuery['pid']);
615972f6adfSAnna Dabrowska
6164a7da0a5SAnna Dabrowska        return $this->queryDb($queryBuilder->getPages());
617972f6adfSAnna Dabrowska    }
618972f6adfSAnna Dabrowska
6191b4b4fa9SAnna Dabrowska    /**
6204227fca4SAnna Dabrowska     * Syntax to allow users to manage tags on regular pages, respects ACLs
6214227fca4SAnna Dabrowska     * @param string $ns
6224227fca4SAnna Dabrowska     * @return string
6234227fca4SAnna Dabrowska     */
6244227fca4SAnna Dabrowska    public function manageTags($ns)
6254227fca4SAnna Dabrowska    {
6264227fca4SAnna Dabrowska        global $INPUT;
6274227fca4SAnna Dabrowska
628f6568bcbSAnna Dabrowska        $this->setDefaultSort();
6294227fca4SAnna Dabrowska
6304227fca4SAnna Dabrowska        // initially set namespace filter to what is defined in syntax
6314227fca4SAnna Dabrowska        if ($ns && !$INPUT->has('tagging__filters')) {
6324227fca4SAnna Dabrowska            $INPUT->set('tagging__filters', ['ns' => $ns]);
6334227fca4SAnna Dabrowska        }
6344227fca4SAnna Dabrowska
6354227fca4SAnna Dabrowska        return $this->html_table();
6364227fca4SAnna Dabrowska    }
6374227fca4SAnna Dabrowska
6384227fca4SAnna Dabrowska    /**
639a2246f69SAnna Dabrowska     * HTML list of tagged pages
640a2246f69SAnna Dabrowska     *
641a2246f69SAnna Dabrowska     * @param string $tid
642a2246f69SAnna Dabrowska     * @return string
643a2246f69SAnna Dabrowska     */
644a2246f69SAnna Dabrowska    public function getPagesHtml($tid)
645a2246f69SAnna Dabrowska    {
646a2246f69SAnna Dabrowska        $html = '';
647a2246f69SAnna Dabrowska
648a2246f69SAnna Dabrowska        $db = $this->getDB();
64931bddc5fSAnna Dabrowska        $sql = 'SELECT pid from taggings where CLEANTAG(tag) = CLEANTAG(?)';
650a2246f69SAnna Dabrowska        $res =  $db->query($sql, $tid);
651a2246f69SAnna Dabrowska        $pages = $db->res2arr($res);
652a2246f69SAnna Dabrowska
653a2246f69SAnna Dabrowska        if ($pages) {
654a2246f69SAnna Dabrowska            $html .= '<ul>';
655a2246f69SAnna Dabrowska            foreach ($pages as $page) {
656a2246f69SAnna Dabrowska                $pid = $page['pid'];
657a2246f69SAnna Dabrowska                $html .= '<li><a href="' . wl($pid) . '" target="_blank">' . $pid . '</li>';
658a2246f69SAnna Dabrowska            }
659a2246f69SAnna Dabrowska            $html .= '</ul>';
660a2246f69SAnna Dabrowska        }
661a2246f69SAnna Dabrowska
662a2246f69SAnna Dabrowska        return $html;
663a2246f69SAnna Dabrowska    }
664a2246f69SAnna Dabrowska
665a2246f69SAnna Dabrowska    /**
66689ed97adSAnna Dabrowska     * Display tag management table
66789ed97adSAnna Dabrowska     */
66889ed97adSAnna Dabrowska    public function html_table() {
66989ed97adSAnna Dabrowska        global $ID, $INPUT;
67089ed97adSAnna Dabrowska
67189ed97adSAnna Dabrowska        $headers = array(
67289ed97adSAnna Dabrowska            array('value' => $this->getLang('admin tag'), 'sort_by' => 'tid'),
67389ed97adSAnna Dabrowska            array('value' => $this->getLang('admin occurrence'), 'sort_by' => 'count'),
67489ed97adSAnna Dabrowska            array('value' => $this->getLang('admin namespaces'), 'sort_by' => 'ns'),
67589ed97adSAnna Dabrowska            array('value' => $this->getLang('admin taggers'), 'sort_by' => 'taggers'),
67689ed97adSAnna Dabrowska            array('value' => $this->getLang('admin actions'), 'sort_by' => false),
67789ed97adSAnna Dabrowska        );
67889ed97adSAnna Dabrowska
679f6568bcbSAnna Dabrowska        $sort = explode(',', $this->getParam('sort'));
68089ed97adSAnna Dabrowska        $order_by = $sort[0];
68189ed97adSAnna Dabrowska        $desc = false;
68289ed97adSAnna Dabrowska        if (isset($sort[1]) && $sort[1] === 'desc') {
68389ed97adSAnna Dabrowska            $desc = true;
68489ed97adSAnna Dabrowska        }
68589ed97adSAnna Dabrowska        $filters = $INPUT->arr('tagging__filters');
68689ed97adSAnna Dabrowska
68789ed97adSAnna Dabrowska        $tags = $this->getAllTags($INPUT->str('filter'), $order_by, $desc, $filters);
68889ed97adSAnna Dabrowska
689a2246f69SAnna Dabrowska        $form = new \dokuwiki\Form\Form();
690a2246f69SAnna Dabrowska        // required in admin mode
69189ed97adSAnna Dabrowska        $form->setHiddenField('page', 'tagging');
69289ed97adSAnna Dabrowska        $form->setHiddenField('id', $ID);
693f6568bcbSAnna Dabrowska        $form->setHiddenField('[tagging]sort', $this->getParam('sort'));
69489ed97adSAnna Dabrowska
69589ed97adSAnna Dabrowska        /**
69689ed97adSAnna Dabrowska         * Actions dialog
69789ed97adSAnna Dabrowska         */
69889ed97adSAnna Dabrowska        $form->addTagOpen('div')->id('tagging__action-dialog')->attr('style', "display:none;");
69989ed97adSAnna Dabrowska        $form->addTagClose('div');
70089ed97adSAnna Dabrowska
70189ed97adSAnna Dabrowska        /**
70289ed97adSAnna Dabrowska         * Tag pages dialog
70389ed97adSAnna Dabrowska         */
70489ed97adSAnna Dabrowska        $form->addTagOpen('div')->id('tagging__taggedpages-dialog')->attr('style', "display:none;");
70589ed97adSAnna Dabrowska        $form->addTagClose('div');
70689ed97adSAnna Dabrowska
70789ed97adSAnna Dabrowska        /**
70889ed97adSAnna Dabrowska         * Tag management table
70989ed97adSAnna Dabrowska         */
71089ed97adSAnna Dabrowska        $form->addTagOpen('table')->addClass('inline plugin_tagging');
71189ed97adSAnna Dabrowska
712a305ec34SAnna Dabrowska        $form->addHTML(
713a305ec34SAnna Dabrowska            '<colgroup>
714a305ec34SAnna Dabrowska                <col></col>
715a305ec34SAnna Dabrowska                <col class="narrow-col"></col>
716a305ec34SAnna Dabrowska                <col class="wide-col"></col>
717a305ec34SAnna Dabrowska                <col></col>
718a305ec34SAnna Dabrowska                <col class="narrow-col"></col>
719a305ec34SAnna Dabrowska            </colgroup>'
720a305ec34SAnna Dabrowska        );
721a305ec34SAnna Dabrowska
72289ed97adSAnna Dabrowska        /**
72389ed97adSAnna Dabrowska         * Table headers
72489ed97adSAnna Dabrowska         */
72589ed97adSAnna Dabrowska        $form->addTagOpen('tr');
72689ed97adSAnna Dabrowska        foreach ($headers as $header) {
72789ed97adSAnna Dabrowska            $form->addTagOpen('th');
72889ed97adSAnna Dabrowska            if ($header['sort_by'] !== false) {
72989ed97adSAnna Dabrowska                $param = $header['sort_by'];
73089ed97adSAnna Dabrowska                $icon = 'arrow-both';
73189ed97adSAnna Dabrowska                $title = $this->getLang('admin sort ascending');
73289ed97adSAnna Dabrowska                if ($header['sort_by'] === $order_by) {
73389ed97adSAnna Dabrowska                    if ($desc === false) {
73489ed97adSAnna Dabrowska                        $icon = 'arrow-up';
73589ed97adSAnna Dabrowska                        $title = $this->getLang('admin sort descending');
73689ed97adSAnna Dabrowska                        $param .= ',desc';
73789ed97adSAnna Dabrowska                    } else {
73889ed97adSAnna Dabrowska                        $icon = 'arrow-down';
73989ed97adSAnna Dabrowska                    }
74089ed97adSAnna Dabrowska                }
74126f61833SAnna Dabrowska                $form->addButtonHTML(
742f6568bcbSAnna Dabrowska                    "tagging[sort]",
74326f61833SAnna Dabrowska                    $header['value'] . ' ' . inlineSVG(__DIR__ . "/images/$icon.svg"))
74489ed97adSAnna Dabrowska                    ->addClass('plugin_tagging sort_button')
745f6568bcbSAnna Dabrowska                    ->attr('title', $title)
746f6568bcbSAnna Dabrowska                    ->val($param);
74789ed97adSAnna Dabrowska            } else {
74889ed97adSAnna Dabrowska                $form->addHTML($header['value']);
74989ed97adSAnna Dabrowska            }
75089ed97adSAnna Dabrowska            $form->addTagClose('th');
75189ed97adSAnna Dabrowska        }
75289ed97adSAnna Dabrowska        $form->addTagClose('tr');
75389ed97adSAnna Dabrowska
75489ed97adSAnna Dabrowska        /**
75589ed97adSAnna Dabrowska         * Table filters for all sortable columns
75689ed97adSAnna Dabrowska         */
75789ed97adSAnna Dabrowska        $form->addTagOpen('tr');
75889ed97adSAnna Dabrowska        foreach ($headers as $header) {
75989ed97adSAnna Dabrowska            $form->addTagOpen('th');
76089ed97adSAnna Dabrowska            if ($header['sort_by'] !== false) {
76189ed97adSAnna Dabrowska                $field = $header['sort_by'];
7627c96ae87SAnna Dabrowska                $input = $form->addTextInput("tagging__filters[$field]");
763a305ec34SAnna Dabrowska                $input->addClass('full-col');
76489ed97adSAnna Dabrowska            }
76589ed97adSAnna Dabrowska            $form->addTagClose('th');
76689ed97adSAnna Dabrowska        }
76789ed97adSAnna Dabrowska        $form->addTagClose('tr');
76889ed97adSAnna Dabrowska
76989ed97adSAnna Dabrowska
77089ed97adSAnna Dabrowska        foreach ($tags as $taginfo) {
77189ed97adSAnna Dabrowska            $tagname = $taginfo['tid'];
77289ed97adSAnna Dabrowska            $taggers = $taginfo['taggers'];
77389ed97adSAnna Dabrowska            $ns = $taginfo['ns'];
77489ed97adSAnna Dabrowska            $pids = explode(',',$taginfo['pids']);
77589ed97adSAnna Dabrowska
77689ed97adSAnna Dabrowska            $form->addTagOpen('tr');
77726f61833SAnna Dabrowska            $form->addHTML('<td>');
778a2246f69SAnna Dabrowska            $form->addHTML('<a class="tagslist" href="#" data-tid="' . $taginfo['tid'] . '">');
77926f61833SAnna Dabrowska            $form->addHTML( hsc($tagname) . '</a>');
78026f61833SAnna Dabrowska            $form->addHTML('</td>');
78189ed97adSAnna Dabrowska            $form->addHTML('<td>' . $taginfo['count'] . '</td>');
78289ed97adSAnna Dabrowska            $form->addHTML('<td>' . hsc($ns) . '</td>');
78389ed97adSAnna Dabrowska            $form->addHTML('<td>' . hsc($taggers) . '</td>');
78489ed97adSAnna Dabrowska
78589ed97adSAnna Dabrowska            /**
78689ed97adSAnna Dabrowska             * action buttons
78789ed97adSAnna Dabrowska             */
78889ed97adSAnna Dabrowska            $form->addHTML('<td>');
78989ed97adSAnna Dabrowska
79089ed97adSAnna Dabrowska            // check ACLs
79189ed97adSAnna Dabrowska            $userEdit = false;
79289ed97adSAnna Dabrowska            /** @var \helper_plugin_sqlite $sqliteHelper */
79389ed97adSAnna Dabrowska            $sqliteHelper = plugin_load('helper', 'sqlite');
79489ed97adSAnna Dabrowska            foreach ($pids as $pid) {
79589ed97adSAnna Dabrowska                if ($sqliteHelper->_getAccessLevel($pid) >= AUTH_EDIT) {
79689ed97adSAnna Dabrowska                    $userEdit = true;
79789ed97adSAnna Dabrowska                    continue;
79889ed97adSAnna Dabrowska                }
79989ed97adSAnna Dabrowska            }
80089ed97adSAnna Dabrowska
80189ed97adSAnna Dabrowska            if ($userEdit) {
80226f61833SAnna Dabrowska                $form->addButtonHTML(
803f6568bcbSAnna Dabrowska                    'tagging[actions][rename][' . $taginfo['tid'] . ']',
80426f61833SAnna Dabrowska                    inlineSVG(__DIR__ . '/images/edit.svg'))
80526f61833SAnna Dabrowska                    ->addClass('plugin_tagging action_button')
80626f61833SAnna Dabrowska                    ->attr('data-action', 'rename')
80726f61833SAnna Dabrowska                    ->attr('data-tid', $taginfo['tid']);
80826f61833SAnna Dabrowska                $form->addButtonHTML(
809f6568bcbSAnna Dabrowska                    'tagging[actions][delete][' . $taginfo['tid'] . ']',
81026f61833SAnna Dabrowska                    inlineSVG(__DIR__ . '/images/delete.svg'))
81126f61833SAnna Dabrowska                    ->addClass('plugin_tagging action_button')
81226f61833SAnna Dabrowska                    ->attr('data-action', 'delete')
81326f61833SAnna Dabrowska                    ->attr('data-tid', $taginfo['tid']);
81489ed97adSAnna Dabrowska            }
81589ed97adSAnna Dabrowska
81689ed97adSAnna Dabrowska            $form->addHTML('</td>');
81789ed97adSAnna Dabrowska            $form->addTagClose('tr');
81889ed97adSAnna Dabrowska        }
81989ed97adSAnna Dabrowska
82089ed97adSAnna Dabrowska        $form->addTagClose('table');
8210b033188SAnna Dabrowska        return '<div class="table">' . $form->toHTML() . '</div>';
82289ed97adSAnna Dabrowska    }
82389ed97adSAnna Dabrowska
82489ed97adSAnna Dabrowska    /**
825*ec4796e4SAnna Dabrowska     * Display tag cleaner
826*ec4796e4SAnna Dabrowska     *
827*ec4796e4SAnna Dabrowska     * @return string
828*ec4796e4SAnna Dabrowska     */
829*ec4796e4SAnna Dabrowska    public function html_clean()
830*ec4796e4SAnna Dabrowska    {
831*ec4796e4SAnna Dabrowska        $invalid = $this->getInvalidTaggings();
832*ec4796e4SAnna Dabrowska
833*ec4796e4SAnna Dabrowska        if (!$invalid) {
834*ec4796e4SAnna Dabrowska            return '<p><strong>' . $this->getLang('admin no invalid') . '</strong></p>';
835*ec4796e4SAnna Dabrowska        }
836*ec4796e4SAnna Dabrowska
837*ec4796e4SAnna Dabrowska        $form = new Form();
838*ec4796e4SAnna Dabrowska        $form->setHiddenField('do', 'admin');
839*ec4796e4SAnna Dabrowska        $form->setHiddenField('page', $this->getPluginName());
840*ec4796e4SAnna Dabrowska        $form->addButton('cmd[clean]', $this->getLang('admin clean'));
841*ec4796e4SAnna Dabrowska
842*ec4796e4SAnna Dabrowska        $html = $form->toHTML();
843*ec4796e4SAnna Dabrowska
844*ec4796e4SAnna Dabrowska        $html .= '<div class="table"><table class="inline plugin_tagging">';
845*ec4796e4SAnna Dabrowska        $html .= '<thead><tr><th>' .
846*ec4796e4SAnna Dabrowska            $this->getLang('admin nonexistent page') .
847*ec4796e4SAnna Dabrowska            '</th><th>' .
848*ec4796e4SAnna Dabrowska            $this->getLang('admin tags') .
849*ec4796e4SAnna Dabrowska            '</th></tr></thead><tbody>';
850*ec4796e4SAnna Dabrowska
851*ec4796e4SAnna Dabrowska        foreach ($invalid as $row) {
852*ec4796e4SAnna Dabrowska            $html .= '<tr><td>' . $row['pid'] . '</td><td>' . $row['tags'] . '</td></tr>';
853*ec4796e4SAnna Dabrowska        }
854*ec4796e4SAnna Dabrowska
855*ec4796e4SAnna Dabrowska        $html .= '</tbody></table></div>';
856*ec4796e4SAnna Dabrowska
857*ec4796e4SAnna Dabrowska        return $html;
858*ec4796e4SAnna Dabrowska    }
859*ec4796e4SAnna Dabrowska
860*ec4796e4SAnna Dabrowska    /**
861f6568bcbSAnna Dabrowska     * Returns all tagging parameters from the query string
862f6568bcbSAnna Dabrowska     *
863f6568bcbSAnna Dabrowska     * @return mixed
864f6568bcbSAnna Dabrowska     */
865f6568bcbSAnna Dabrowska    public function getParams()
866f6568bcbSAnna Dabrowska    {
867f6568bcbSAnna Dabrowska        global $INPUT;
868f6568bcbSAnna Dabrowska        return $INPUT->param('tagging', []);
869f6568bcbSAnna Dabrowska    }
870f6568bcbSAnna Dabrowska
871f6568bcbSAnna Dabrowska    /**
872f6568bcbSAnna Dabrowska     * Get a tagging parameter, empty string if not set
873f6568bcbSAnna Dabrowska     *
874f6568bcbSAnna Dabrowska     * @param string $name
875f6568bcbSAnna Dabrowska     * @return mixed
876f6568bcbSAnna Dabrowska     */
877f6568bcbSAnna Dabrowska    public function getParam($name)
878f6568bcbSAnna Dabrowska    {
879f6568bcbSAnna Dabrowska        $params = $this->getParams();
880f6568bcbSAnna Dabrowska        if ($params) {
881f6568bcbSAnna Dabrowska            return $params[$name] ?: '';
882f6568bcbSAnna Dabrowska        }
883f6568bcbSAnna Dabrowska    }
884f6568bcbSAnna Dabrowska
885f6568bcbSAnna Dabrowska    /**
886f6568bcbSAnna Dabrowska     * Sets a tagging parameter
887f6568bcbSAnna Dabrowska     *
888f6568bcbSAnna Dabrowska     * @param string $name
889f6568bcbSAnna Dabrowska     * @param string|array $value
890f6568bcbSAnna Dabrowska     */
891f6568bcbSAnna Dabrowska    public function setParam($name, $value)
892f6568bcbSAnna Dabrowska    {
893f6568bcbSAnna Dabrowska        global $INPUT;
894f6568bcbSAnna Dabrowska        $params = $this->getParams();
895f6568bcbSAnna Dabrowska        $params = array_merge($params, [$name => $value]);
896f6568bcbSAnna Dabrowska        $INPUT->set('tagging', $params);
897f6568bcbSAnna Dabrowska    }
898f6568bcbSAnna Dabrowska
899f6568bcbSAnna Dabrowska    /**
900f6568bcbSAnna Dabrowska     * Default sorting by tag id
901f6568bcbSAnna Dabrowska     */
902f6568bcbSAnna Dabrowska    public function setDefaultSort()
903f6568bcbSAnna Dabrowska    {
904f6568bcbSAnna Dabrowska        if (!$this->getParam('sort')) {
905f6568bcbSAnna Dabrowska            $this->setParam('sort', 'tid');
906f6568bcbSAnna Dabrowska        }
907f6568bcbSAnna Dabrowska    }
908f6568bcbSAnna Dabrowska
909f6568bcbSAnna Dabrowska    /**
9104a7da0a5SAnna Dabrowska     * Executes the query and returns the results as array
9111b4b4fa9SAnna Dabrowska     *
91299122157SAnna Dabrowska     * @param array $query
9131b4b4fa9SAnna Dabrowska     * @return array
9141b4b4fa9SAnna Dabrowska     */
9154a7da0a5SAnna Dabrowska    protected function queryDb($query)
9161b4b4fa9SAnna Dabrowska    {
9174a7da0a5SAnna Dabrowska        $db = $this->getDB();
9184a7da0a5SAnna Dabrowska        if (!$db) {
9194a7da0a5SAnna Dabrowska            return [];
920972f6adfSAnna Dabrowska        }
9214a7da0a5SAnna Dabrowska
92299122157SAnna Dabrowska        $res = $db->query($query[0], $query[1]);
9234a7da0a5SAnna Dabrowska        $res = $db->res2arr($res);
9244a7da0a5SAnna Dabrowska
9254a7da0a5SAnna Dabrowska        $ret = [];
9264a7da0a5SAnna Dabrowska        foreach ($res as $row) {
9274a7da0a5SAnna Dabrowska            $ret[$row['item']] = $row['cnt'];
9284a7da0a5SAnna Dabrowska        }
9294a7da0a5SAnna Dabrowska        return $ret;
930972f6adfSAnna Dabrowska    }
93140b94b1aSAnna Dabrowska
93240b94b1aSAnna Dabrowska    /**
93340b94b1aSAnna Dabrowska     * Construct the HAVING part of the search query
93440b94b1aSAnna Dabrowska     *
93540b94b1aSAnna Dabrowska     * @param array $filters
93640b94b1aSAnna Dabrowska     * @return array
93740b94b1aSAnna Dabrowska     */
93840b94b1aSAnna Dabrowska    protected function getFilterSql($filters)
93940b94b1aSAnna Dabrowska    {
94040b94b1aSAnna Dabrowska        $having = '';
94140b94b1aSAnna Dabrowska        $parts = [];
94240b94b1aSAnna Dabrowska        $params = [];
94340b94b1aSAnna Dabrowska        $filters = array_filter($filters);
94440b94b1aSAnna Dabrowska        if (!empty($filters)) {
94540b94b1aSAnna Dabrowska            $having = ' HAVING ';
94640b94b1aSAnna Dabrowska            foreach ($filters as $filter => $value) {
94740b94b1aSAnna Dabrowska                $parts[] = " $filter LIKE ? ";
94840b94b1aSAnna Dabrowska                $params[] = "%$value%";
94940b94b1aSAnna Dabrowska            }
95040b94b1aSAnna Dabrowska            $having .= implode(' AND ', $parts);
95140b94b1aSAnna Dabrowska        }
95240b94b1aSAnna Dabrowska        return [$having, $params];
95340b94b1aSAnna Dabrowska    }
954*ec4796e4SAnna Dabrowska
955*ec4796e4SAnna Dabrowska    /**
956*ec4796e4SAnna Dabrowska     * Returns taggings of nonexistent pages
957*ec4796e4SAnna Dabrowska     *
958*ec4796e4SAnna Dabrowska     * @return array
959*ec4796e4SAnna Dabrowska     */
960*ec4796e4SAnna Dabrowska    protected function getInvalidTaggings()
961*ec4796e4SAnna Dabrowska    {
962*ec4796e4SAnna Dabrowska        $db = $this->getDB();
963*ec4796e4SAnna Dabrowska        $query = 'SELECT    "pid",
964*ec4796e4SAnna Dabrowska                            GROUP_CONCAT(CLEANTAG("tag")) AS "tags"
965*ec4796e4SAnna Dabrowska                            FROM "taggings"
966*ec4796e4SAnna Dabrowska                            WHERE PAGEEXISTS(pid) IS FALSE
967*ec4796e4SAnna Dabrowska                            GROUP BY pid
968*ec4796e4SAnna Dabrowska                 ';
969*ec4796e4SAnna Dabrowska        $res = $db->query($query);
970*ec4796e4SAnna Dabrowska        return $db->res2arr($res);
971*ec4796e4SAnna Dabrowska    }
972cd08599fSAnna Dabrowska}
973