xref: /plugin/tagging/helper.php (revision 1b4b4fa991ead6a21eec9148846f6b5e23d3fecb)
1f61105deSAdrian Lang<?php
2aa627deeSAndreas Gohr/**
3aa627deeSAndreas Gohr * Tagging Plugin (hlper component)
4aa627deeSAndreas Gohr *
5aa627deeSAndreas Gohr * @license GPL 2
6aa627deeSAndreas Gohr */
7e4543b6dSAdrian Langclass helper_plugin_tagging extends DokuWiki_Plugin {
8f61105deSAdrian Lang
9*1b4b4fa9SAnna Dabrowska    // filter whitelist
10*1b4b4fa9SAnna Dabrowska    const KNOWN_FILTERS = ['pid', 'tag', 'ns', 'notns', 'tagger'];
11*1b4b4fa9SAnna Dabrowska
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) {
367e05e623SSzymon Olewniczak                $ex = explode(',', $group);
377e05e623SSzymon Olewniczak                sort($ex);
38ca455b8eSMichael Große
397e05e623SSzymon Olewniczak                return implode($newDelimiter, $ex);
407e05e623SSzymon Olewniczak            }, 2);
41ca455b8eSMichael Große
42f61105deSAdrian Lang        return $db;
43f61105deSAdrian Lang    }
44f61105deSAdrian Lang
45302a38efSAndreas Gohr    /**
462ace74f4SAndreas Gohr     * Return the user to use for accessing tags
472ace74f4SAndreas Gohr     *
482ace74f4SAndreas Gohr     * Handles the singleuser mode by returning 'auto' as user. Returnes false when no user is logged in.
492ace74f4SAndreas Gohr     *
502ace74f4SAndreas Gohr     * @return bool|string
512ace74f4SAndreas Gohr     */
522ace74f4SAndreas Gohr    public function getUser() {
530cfde7e9SMichael Große        if (!isset($_SERVER['REMOTE_USER'])) {
540cfde7e9SMichael Große            return false;
550cfde7e9SMichael Große        }
560cfde7e9SMichael Große        if ($this->getConf('singleusermode')) {
570cfde7e9SMichael Große            return 'auto';
580cfde7e9SMichael Große        }
59ca455b8eSMichael Große
602ace74f4SAndreas Gohr        return $_SERVER['REMOTE_USER'];
612ace74f4SAndreas Gohr    }
622ace74f4SAndreas Gohr
632ace74f4SAndreas Gohr    /**
64302a38efSAndreas Gohr     * Canonicalizes the tag to its lower case nospace form
65302a38efSAndreas Gohr     *
66302a38efSAndreas Gohr     * @param $tag
670cfde7e9SMichael Große     *
68302a38efSAndreas Gohr     * @return string
69302a38efSAndreas Gohr     */
70302a38efSAndreas Gohr    public function cleanTag($tag) {
71aa627deeSAndreas Gohr        $tag = str_replace(array(' ', '-', '_'), '', $tag);
72302a38efSAndreas Gohr        $tag = utf8_strtolower($tag);
73ca455b8eSMichael Große
74302a38efSAndreas Gohr        return $tag;
75302a38efSAndreas Gohr    }
76302a38efSAndreas Gohr
7756d82720SAndreas Gohr    /**
7831396860SSzymon Olewniczak     * Canonicalizes the namespace, remove the first colon and add glob
7931396860SSzymon Olewniczak     *
8031396860SSzymon Olewniczak     * @param $namespace
8131396860SSzymon Olewniczak     *
8231396860SSzymon Olewniczak     * @return string
8331396860SSzymon Olewniczak     */
8431396860SSzymon Olewniczak    public function globNamespace($namespace) {
85de379874SAnna Dabrowska        return cleanId($namespace) . '*';
8631396860SSzymon Olewniczak    }
8731396860SSzymon Olewniczak
8831396860SSzymon Olewniczak    /**
8956d82720SAndreas Gohr     * Create or Update tags of a page
9056d82720SAndreas Gohr     *
9156d82720SAndreas Gohr     * Uses the translation plugin to store the language of a page (if available)
9256d82720SAndreas Gohr     *
9356d82720SAndreas Gohr     * @param string $id The page ID
9456d82720SAndreas Gohr     * @param string $user
9556d82720SAndreas Gohr     * @param array  $tags
960cfde7e9SMichael Große     *
9756d82720SAndreas Gohr     * @return bool|SQLiteResult
9856d82720SAndreas Gohr     */
99f61105deSAdrian Lang    public function replaceTags($id, $user, $tags) {
10056d82720SAndreas Gohr        global $conf;
10156d82720SAndreas Gohr        /** @var helper_plugin_translation $trans */
10256d82720SAndreas Gohr        $trans = plugin_load('helper', 'translation');
10356d82720SAndreas Gohr        if ($trans) {
10456d82720SAndreas Gohr            $lang = $trans->realLC($trans->getLangPart($id));
10556d82720SAndreas Gohr        } else {
10656d82720SAndreas Gohr            $lang = $conf['lang'];
10756d82720SAndreas Gohr        }
10856d82720SAndreas Gohr
109f61105deSAdrian Lang        $db = $this->getDB();
110f61105deSAdrian Lang        $db->query('BEGIN TRANSACTION');
111f61105deSAdrian Lang        $queries = array(array('DELETE FROM taggings WHERE pid = ? AND tagger = ?', $id, $user));
112f61105deSAdrian Lang        foreach ($tags as $tag) {
11356d82720SAndreas Gohr            $queries[] = array('INSERT INTO taggings (pid, tagger, tag, lang) VALUES(?, ?, ?, ?)', $id, $user, $tag, $lang);
114f61105deSAdrian Lang        }
115f61105deSAdrian Lang
116f61105deSAdrian Lang        foreach ($queries as $query) {
117f61105deSAdrian Lang            if (!call_user_func_array(array($db, 'query'), $query)) {
118f61105deSAdrian Lang                $db->query('ROLLBACK TRANSACTION');
119ca455b8eSMichael Große
120f61105deSAdrian Lang                return false;
121f61105deSAdrian Lang            }
122f61105deSAdrian Lang        }
123ca455b8eSMichael Große
124f61105deSAdrian Lang        return $db->query('COMMIT TRANSACTION');
125f61105deSAdrian Lang    }
126f61105deSAdrian Lang
1270a518a11SAndreas Gohr    /**
128b12334e1SAndreas Gohr     * Get a list of Tags or Pages matching search criteria
1290a518a11SAndreas Gohr     *
130b12334e1SAndreas Gohr     * @param array  $filter What to search for array('field' => 'searchterm')
131b12334e1SAndreas Gohr     * @param string $type   What field to return 'tag'|'pid'
132077ff864SAndreas Gohr     * @param int    $limit  Limit to this many results, 0 for all
1330cfde7e9SMichael Große     *
1340a518a11SAndreas Gohr     * @return array associative array in form of value => count
1350a518a11SAndreas Gohr     */
136077ff864SAndreas Gohr    public function findItems($filter, $type, $limit = 0) {
137f61105deSAdrian Lang        $db = $this->getDB();
1380cfde7e9SMichael Große        if (!$db) {
1390cfde7e9SMichael Große            return array();
1400cfde7e9SMichael Große        }
141f61105deSAdrian Lang
142*1b4b4fa9SAnna Dabrowska        $sql = $this->buildQuery($filter, $type, $limit);
143b12334e1SAndreas Gohr
144b12334e1SAndreas Gohr        // run query and turn into associative array
145972f6adfSAnna Dabrowska        $data = [];
146*1b4b4fa9SAnna Dabrowska        foreach ($filter as $key => $item) {
147*1b4b4fa9SAnna Dabrowska            // not all filter values are arrays
148*1b4b4fa9SAnna Dabrowska            if (!is_array($item)) $item = [$item];
149*1b4b4fa9SAnna Dabrowska
150*1b4b4fa9SAnna Dabrowska            if ($key === 'ns' || $key === 'notns') {
151*1b4b4fa9SAnna Dabrowska                $item = $this->formatNS($item);
152*1b4b4fa9SAnna Dabrowska            }
153*1b4b4fa9SAnna Dabrowska            $data = array_merge($data, $item);
154972f6adfSAnna Dabrowska        }
155972f6adfSAnna Dabrowska        $res = $db->query($sql, $data);
156f61105deSAdrian Lang        $res = $db->res2arr($res);
157b12334e1SAndreas Gohr
158f61105deSAdrian Lang        $ret = array();
159b12334e1SAndreas Gohr        foreach ($res as $row) {
160b12334e1SAndreas Gohr            $ret[$row['item']] = $row['cnt'];
161f61105deSAdrian Lang        }
162ca455b8eSMichael Große
163f61105deSAdrian Lang        return $ret;
164f61105deSAdrian Lang    }
165f61105deSAdrian Lang
166b12334e1SAndreas Gohr    /**
167302a38efSAndreas Gohr     * Constructs the URL to search for a tag
168302a38efSAndreas Gohr     *
1695540f91dSAndreas Gohr     * @param string $tag
1705540f91dSAndreas Gohr     * @param string $ns
1710cfde7e9SMichael Große     *
172302a38efSAndreas Gohr     * @return string
173302a38efSAndreas Gohr     */
1745540f91dSAndreas Gohr    public function getTagSearchURL($tag, $ns = '') {
175bed9f360SAndreas Gohr        // wrap tag in quotes if non clean
1762cb25ec1SAndreas Gohr        $ctag = utf8_stripspecials($this->cleanTag($tag));
1770cfde7e9SMichael Große        if ($ctag != utf8_strtolower($tag)) {
1780cfde7e9SMichael Große            $tag = '"' . $tag . '"';
1790cfde7e9SMichael Große        }
180bed9f360SAndreas Gohr
181018370fcSsheesupport        $ret = '?do=search&sf=1&id=' . rawurlencode($tag);
1820cfde7e9SMichael Große        if ($ns) {
1830cfde7e9SMichael Große            $ret .= rawurlencode(' @' . $ns);
1840cfde7e9SMichael Große        }
1855540f91dSAndreas Gohr
1865540f91dSAndreas Gohr        return $ret;
187f61105deSAdrian Lang    }
188f61105deSAdrian Lang
1895540f91dSAndreas Gohr    /**
1905540f91dSAndreas Gohr     * Calculates the size levels for the given list of clouds
1915540f91dSAndreas Gohr     *
1925540f91dSAndreas Gohr     * Automatically determines sensible tresholds
1935540f91dSAndreas Gohr     *
1945540f91dSAndreas Gohr     * @param array $tags list of tags => count
1955540f91dSAndreas Gohr     * @param int   $levels
1960cfde7e9SMichael Große     *
1975540f91dSAndreas Gohr     * @return mixed
1985540f91dSAndreas Gohr     */
199f61105deSAdrian Lang    public function cloudData($tags, $levels = 10) {
200f61105deSAdrian Lang        $min = min($tags);
201f61105deSAdrian Lang        $max = max($tags);
202f61105deSAdrian Lang
203f61105deSAdrian Lang        // calculate tresholds
204f61105deSAdrian Lang        $tresholds = array();
205f61105deSAdrian Lang        for ($i = 0; $i <= $levels; $i++) {
206f61105deSAdrian Lang            $tresholds[$i] = pow($max - $min + 1, $i / $levels) + $min - 1;
207f61105deSAdrian Lang        }
208f61105deSAdrian Lang
209f61105deSAdrian Lang        // assign weights
210f61105deSAdrian Lang        foreach ($tags as $tag => $cnt) {
211f61105deSAdrian Lang            foreach ($tresholds as $tresh => $val) {
212f61105deSAdrian Lang                if ($cnt <= $val) {
213f61105deSAdrian Lang                    $tags[$tag] = $tresh;
214f61105deSAdrian Lang                    break;
215f61105deSAdrian Lang                }
216f61105deSAdrian Lang                $tags[$tag] = $levels;
217f61105deSAdrian Lang            }
218f61105deSAdrian Lang        }
219ca455b8eSMichael Große
220f61105deSAdrian Lang        return $tags;
221f61105deSAdrian Lang    }
222f61105deSAdrian Lang
2235540f91dSAndreas Gohr    /**
2245540f91dSAndreas Gohr     * Display a tag cloud
2255540f91dSAndreas Gohr     *
2265540f91dSAndreas Gohr     * @param array    $tags   list of tags => count
2275540f91dSAndreas Gohr     * @param string   $type   'tag'
2285540f91dSAndreas Gohr     * @param Callable $func   The function to print the link (gets tag and ns)
2295540f91dSAndreas Gohr     * @param bool     $wrap   wrap cloud in UL tags?
2305540f91dSAndreas Gohr     * @param bool     $return returnn HTML instead of printing?
2315540f91dSAndreas Gohr     * @param string   $ns     Add this namespace to search links
2320cfde7e9SMichael Große     *
2335540f91dSAndreas Gohr     * @return string
2345540f91dSAndreas Gohr     */
2355540f91dSAndreas Gohr    public function html_cloud($tags, $type, $func, $wrap = true, $return = false, $ns = '') {
236a66f6715SAndreas Gohr        global $INFO;
237a66f6715SAndreas Gohr
238a66f6715SAndreas Gohr        $hidden_str = $this->getConf('hiddenprefix');
239a66f6715SAndreas Gohr        $hidden_len = strlen($hidden_str);
240a66f6715SAndreas Gohr
241f61105deSAdrian Lang        $ret = '';
2420cfde7e9SMichael Große        if ($wrap) {
2430cfde7e9SMichael Große            $ret .= '<ul class="tagging_cloud clearfix">';
2440cfde7e9SMichael Große        }
245f61105deSAdrian Lang        if (count($tags) === 0) {
246f61105deSAdrian Lang            // Produce valid XHTML (ul needs a child)
247f61105deSAdrian Lang            $this->setupLocale();
248f61105deSAdrian Lang            $ret .= '<li><div class="li">' . $this->lang['js']['no' . $type . 's'] . '</div></li>';
249f61105deSAdrian Lang        } else {
250f61105deSAdrian Lang            $tags = $this->cloudData($tags);
251f61105deSAdrian Lang            foreach ($tags as $val => $size) {
252a66f6715SAndreas Gohr                // skip hidden tags for users that can't edit
253aa627deeSAndreas Gohr                if ($type === 'tag' and
254a66f6715SAndreas Gohr                    $hidden_len and
255a66f6715SAndreas Gohr                    substr($val, 0, $hidden_len) == $hidden_str and
256a66f6715SAndreas Gohr                    !($this->getUser() && $INFO['writable'])
257a66f6715SAndreas Gohr                ) {
258a66f6715SAndreas Gohr                    continue;
259a66f6715SAndreas Gohr                }
260a66f6715SAndreas Gohr
261f61105deSAdrian Lang                $ret .= '<li class="t' . $size . '"><div class="li">';
2625540f91dSAndreas Gohr                $ret .= call_user_func($func, $val, $ns);
263f61105deSAdrian Lang                $ret .= '</div></li>';
264f61105deSAdrian Lang            }
265f61105deSAdrian Lang        }
2660cfde7e9SMichael Große        if ($wrap) {
2670cfde7e9SMichael Große            $ret .= '</ul>';
2680cfde7e9SMichael Große        }
2690cfde7e9SMichael Große        if ($return) {
2700cfde7e9SMichael Große            return $ret;
2710cfde7e9SMichael Große        }
272f61105deSAdrian Lang        echo $ret;
273ca455b8eSMichael Große
2745540f91dSAndreas Gohr        return '';
275f61105deSAdrian Lang    }
276f61105deSAdrian Lang
2775540f91dSAndreas Gohr    /**
2785540f91dSAndreas Gohr     * Get the link to a search for the given tag
2795540f91dSAndreas Gohr     *
2805540f91dSAndreas Gohr     * @param string $tag search for this tag
2815540f91dSAndreas Gohr     * @param string $ns  limit search to this namespace
2820cfde7e9SMichael Große     *
2835540f91dSAndreas Gohr     * @return string
2845540f91dSAndreas Gohr     */
2855540f91dSAndreas Gohr    protected function linkToSearch($tag, $ns = '') {
2865540f91dSAndreas Gohr        return '<a href="' . hsc($this->getTagSearchURL($tag, $ns)) . '">' . $tag . '</a>';
287f61105deSAdrian Lang    }
288f61105deSAdrian Lang
289fb1d0583SAndreas Gohr    /**
290fb1d0583SAndreas Gohr     * Display the Tags for the current page and prepare the tag editing form
2913496cc8aSAndreas Gohr     *
2923496cc8aSAndreas Gohr     * @param bool $print Should the HTML be printed or returned?
2930cfde7e9SMichael Große     *
2943496cc8aSAndreas Gohr     * @return string
295fb1d0583SAndreas Gohr     */
2963496cc8aSAndreas Gohr    public function tpl_tags($print = true) {
297f61105deSAdrian Lang        global $INFO;
298f61105deSAdrian Lang        global $lang;
2993bf0e2f1SMichael Große
3003bf0e2f1SMichael Große        $filter = array('pid' => $INFO['id']);
3013bf0e2f1SMichael Große        if ($this->getConf('singleusermode')) {
3023bf0e2f1SMichael Große            $filter['tagger'] = 'auto';
3033bf0e2f1SMichael Große        }
3043bf0e2f1SMichael Große
3053bf0e2f1SMichael Große        $tags = $this->findItems($filter, 'tag');
3063496cc8aSAndreas Gohr
3073496cc8aSAndreas Gohr        $ret = '';
3083496cc8aSAndreas Gohr
3093496cc8aSAndreas Gohr        $ret .= '<div class="plugin_tagging_edit">';
3103496cc8aSAndreas Gohr        $ret .= $this->html_cloud($tags, 'tag', array($this, 'linkToSearch'), true, true);
311f61105deSAdrian Lang
3122ace74f4SAndreas Gohr        if ($this->getUser() && $INFO['writable']) {
313f61105deSAdrian Lang            $lang['btn_tagging_edit'] = $lang['btn_secedit'];
314e5b42768SSzymon Olewniczak            $ret .= '<div id="tagging__edit_buttons_group">';
3153496cc8aSAndreas Gohr            $ret .= html_btn('tagging_edit', $INFO['id'], '', array());
316dd52fd45SSzymon Olewniczak            if (auth_isadmin()) {
317e5b42768SSzymon Olewniczak                $ret .= '<label>' . $this->getLang('toggle admin mode') . '<input type="checkbox" id="tagging__edit_toggle_admin" /></label>';
318dd52fd45SSzymon Olewniczak            }
319e5b42768SSzymon Olewniczak            $ret .= '</div>';
3202819ffcdSSzymon Olewniczak            $form = new dokuwiki\Form\Form();
3212819ffcdSSzymon Olewniczak            $form->id('tagging__edit');
3222819ffcdSSzymon Olewniczak            $form->setHiddenField('tagging[id]', $INFO['id']);
3232819ffcdSSzymon Olewniczak            $form->setHiddenField('call', 'plugin_tagging_save');
3242819ffcdSSzymon Olewniczak            $tags = $this->findItems(array(
3252819ffcdSSzymon Olewniczak                'pid'    => $INFO['id'],
326ca455b8eSMichael Große                'tagger' => $this->getUser(),
3272819ffcdSSzymon Olewniczak            ), 'tag');
3284b1e7a12SAndreas Gohr            $form->addTextarea('tagging[tags]')->val(implode(', ', array_keys($tags)))->addClass('edit')->attr('rows', 4);
329cf52ec2dSSzymon Olewniczak            $form->addButton('', $lang['btn_save'])->id('tagging__edit_save');
330cf52ec2dSSzymon Olewniczak            $form->addButton('', $lang['btn_cancel'])->id('tagging__edit_cancel');
3312819ffcdSSzymon Olewniczak            $ret .= $form->toHTML();
332f61105deSAdrian Lang        }
3333496cc8aSAndreas Gohr        $ret .= '</div>';
3343496cc8aSAndreas Gohr
3350cfde7e9SMichael Große        if ($print) {
3360cfde7e9SMichael Große            echo $ret;
3370cfde7e9SMichael Große        }
338ca455b8eSMichael Große
3393496cc8aSAndreas Gohr        return $ret;
340f61105deSAdrian Lang    }
341872edc7cSRené Corinth
3428a1a3846SAndreas Gohr    /**
343a99b66c1SSzymon Olewniczak     * @param string $namespace empty for entire wiki
344a99b66c1SSzymon Olewniczak     *
3458a1a3846SAndreas Gohr     * @return array
3468a1a3846SAndreas Gohr     */
347cb469644SSzymon Olewniczak    public function getAllTags($namespace = '', $order_by = 'tag', $desc = false) {
3488e9d0162SSzymon Olewniczak        $order_fields = array('pid', 'tid', 'orig', 'taggers', 'count');
349f0084ee1SSzymon Olewniczak        if (!in_array($order_by, $order_fields)) {
350f0084ee1SSzymon Olewniczak            msg('cannot sort by ' . $order_by . ' field does not exists', -1);
351f0084ee1SSzymon Olewniczak            $order_by = 'tag';
352f0084ee1SSzymon Olewniczak        }
353872edc7cSRené Corinth
354872edc7cSRené Corinth        $db = $this->getDb();
355872edc7cSRené Corinth
356f0084ee1SSzymon Olewniczak        $query = 'SELECT    "pid",
357ca455b8eSMichael Große                            CLEANTAG("tag") AS "tid",
358f0084ee1SSzymon Olewniczak                            GROUP_SORT(GROUP_CONCAT("tag"), \', \') AS "orig",
359f0084ee1SSzymon Olewniczak                            GROUP_SORT(GROUP_CONCAT("tagger"), \', \') AS "taggers",
360193a767dSSzymon Olewniczak                            COUNT(*) AS "count"
36157e45304SSzymon Olewniczak                        FROM "taggings"
362de379874SAnna Dabrowska                        WHERE "pid" GLOB ?
363f0084ee1SSzymon Olewniczak                        GROUP BY "tid"
364f0084ee1SSzymon Olewniczak                        ORDER BY ' . $order_by;
365ca455b8eSMichael Große        if ($desc) {
366ca455b8eSMichael Große            $query .= ' DESC';
367ca455b8eSMichael Große        }
368cb469644SSzymon Olewniczak
369f0084ee1SSzymon Olewniczak        $res = $db->query($query, $this->globNamespace($namespace));
370872edc7cSRené Corinth
3717e05e623SSzymon Olewniczak        return $db->res2arr($res);
372872edc7cSRené Corinth    }
373872edc7cSRené Corinth
3748a1a3846SAndreas Gohr    /**
37572431326SMichael Große     * Get all pages with tags and their tags
37672431326SMichael Große     *
377790ca788SAndreas Gohr     * @return array ['pid' => ['tag1','tag2','tag3']]
37872431326SMichael Große     */
37972431326SMichael Große    public function getAllTagsByPage() {
38072431326SMichael Große        $query = '
38172431326SMichael Große        SELECT pid, GROUP_CONCAT(tag) AS tags
38272431326SMichael Große        FROM taggings
38372431326SMichael Große        GROUP BY pid
38472431326SMichael Große        ';
38572431326SMichael Große        $db = $this->getDb();
38672431326SMichael Große        $res = $db->query($query);
387790ca788SAndreas Gohr        return array_map(
388790ca788SAndreas Gohr            function ($i) {
389790ca788SAndreas Gohr                return explode(',', $i);
390790ca788SAndreas Gohr            },
391790ca788SAndreas Gohr            array_column($db->res2arr($res), 'tags', 'pid')
392790ca788SAndreas Gohr        );
39372431326SMichael Große    }
39472431326SMichael Große
39572431326SMichael Große    /**
3968a1a3846SAndreas Gohr     * Renames a tag
3978a1a3846SAndreas Gohr     *
3988a1a3846SAndreas Gohr     * @param string $formerTagName
3998a1a3846SAndreas Gohr     * @param string $newTagName
4008a1a3846SAndreas Gohr     */
401872edc7cSRené Corinth    public function renameTag($formerTagName, $newTagName) {
402872edc7cSRené Corinth
403872edc7cSRené Corinth        if (empty($formerTagName) || empty($newTagName)) {
4048a1a3846SAndreas Gohr            msg($this->getLang("admin enter tag names"), -1);
405ca455b8eSMichael Große
4068a1a3846SAndreas Gohr            return;
407872edc7cSRené Corinth        }
408872edc7cSRené Corinth
409872edc7cSRené Corinth        $db = $this->getDb();
410872edc7cSRené Corinth
411fb1d0583SAndreas Gohr        $res = $db->query('SELECT pid FROM taggings WHERE CLEANTAG(tag) = ?', $this->cleanTag($formerTagName));
412872edc7cSRené Corinth        $check = $db->res2arr($res);
413872edc7cSRené Corinth
414872edc7cSRené Corinth        if (empty($check)) {
4158a1a3846SAndreas Gohr            msg($this->getLang("admin tag does not exists"), -1);
416ca455b8eSMichael Große
4178a1a3846SAndreas Gohr            return;
418872edc7cSRené Corinth        }
419872edc7cSRené Corinth
420fb1d0583SAndreas Gohr        $res = $db->query("UPDATE taggings SET tag = ? WHERE CLEANTAG(tag) = ?", $newTagName, $this->cleanTag($formerTagName));
421872edc7cSRené Corinth        $db->res2arr($res);
422872edc7cSRené Corinth
423fb1d0583SAndreas Gohr        msg($this->getLang("admin renamed"), 1);
424ca455b8eSMichael Große
4258a1a3846SAndreas Gohr        return;
426872edc7cSRené Corinth    }
427872edc7cSRené Corinth
4288f630140SSzymon Olewniczak    /**
429dd52fd45SSzymon Olewniczak     * Rename or delete a tag for all users
430dd52fd45SSzymon Olewniczak     *
431dd52fd45SSzymon Olewniczak     * @param string $pid
432dd52fd45SSzymon Olewniczak     * @param string $formerTagName
433dd52fd45SSzymon Olewniczak     * @param string $newTagName
434dd52fd45SSzymon Olewniczak     *
435dd52fd45SSzymon Olewniczak     * @return array
436dd52fd45SSzymon Olewniczak     */
437dd52fd45SSzymon Olewniczak    public function modifyPageTag($pid, $formerTagName, $newTagName) {
438dd52fd45SSzymon Olewniczak
439dd52fd45SSzymon Olewniczak        $db = $this->getDb();
440dd52fd45SSzymon Olewniczak
441dd52fd45SSzymon Olewniczak        $res = $db->query('SELECT pid FROM taggings WHERE CLEANTAG(tag) = ? AND pid = ?', $this->cleanTag($formerTagName), $pid);
442dd52fd45SSzymon Olewniczak        $check = $db->res2arr($res);
443dd52fd45SSzymon Olewniczak
444dd52fd45SSzymon Olewniczak        if (empty($check)) {
445dd52fd45SSzymon Olewniczak            return array(true, $this->getLang('admin tag does not exists'));
446dd52fd45SSzymon Olewniczak        }
447dd52fd45SSzymon Olewniczak
448dd52fd45SSzymon Olewniczak        if (empty($newTagName)) {
449dd52fd45SSzymon Olewniczak            $res = $db->query('DELETE FROM taggings WHERE pid = ? AND CLEANTAG(tag) = ?', $pid, $this->cleanTag($formerTagName));
450dd52fd45SSzymon Olewniczak        } else {
451dd52fd45SSzymon Olewniczak            $res = $db->query('UPDATE taggings SET tag = ? WHERE pid = ? AND CLEANTAG(tag) = ?', $newTagName, $pid, $this->cleanTag($formerTagName));
452dd52fd45SSzymon Olewniczak        }
453dd52fd45SSzymon Olewniczak        $db->res2arr($res);
454dd52fd45SSzymon Olewniczak
455dd52fd45SSzymon Olewniczak        return array(false, $this->getLang('admin renamed'));
456dd52fd45SSzymon Olewniczak    }
457dd52fd45SSzymon Olewniczak
458dd52fd45SSzymon Olewniczak    /**
4598f630140SSzymon Olewniczak     * Deletes a tag
4608f630140SSzymon Olewniczak     *
4618f630140SSzymon Olewniczak     * @param array  $tags
46231396860SSzymon Olewniczak     * @param string $namespace current namespace context as in getAllTags()
4638f630140SSzymon Olewniczak     */
46431396860SSzymon Olewniczak    public function deleteTags($tags, $namespace = '') {
465ca455b8eSMichael Große        if (empty($tags)) {
466ca455b8eSMichael Große            return;
467ca455b8eSMichael Große        }
4688f630140SSzymon Olewniczak
46931396860SSzymon Olewniczak        $namespace = cleanId($namespace);
47031396860SSzymon Olewniczak
4711f5991a7SMichael Große        $db = $this->getDB();
4728f630140SSzymon Olewniczak
473de379874SAnna Dabrowska        $queryBody = 'FROM taggings WHERE pid GLOB ? AND (' .
47431396860SSzymon Olewniczak            implode(' OR ', array_fill(0, count($tags), 'CLEANTAG(tag) = ?')) . ')';
47531396860SSzymon Olewniczak        $args = array_map(array($this, 'cleanTag'), $tags);
47631396860SSzymon Olewniczak        array_unshift($args, $this->globNamespace($namespace));
4778f630140SSzymon Olewniczak
478ca455b8eSMichael Große
4791f5991a7SMichael Große        $affectedPagesQuery= 'SELECT DISTINCT pid ' . $queryBody;
4801f5991a7SMichael Große        $resAffectedPages = $db->query($affectedPagesQuery, $args);
4811f5991a7SMichael Große        $numAffectedPages = count($resAffectedPages->fetchAll());
4821f5991a7SMichael Große
4831f5991a7SMichael Große        $deleteQuery = 'DELETE ' . $queryBody;
4841f5991a7SMichael Große        $db->query($deleteQuery, $args);
4851f5991a7SMichael Große
4861f5991a7SMichael Große        msg(sprintf($this->getLang("admin deleted"), count($tags), $numAffectedPages), 1);
4878f630140SSzymon Olewniczak    }
488cd08599fSAnna Dabrowska
489cd08599fSAnna Dabrowska    /**
490cd08599fSAnna Dabrowska     * Updates tags with a new page name
491cd08599fSAnna Dabrowska     *
492cd08599fSAnna Dabrowska     * @param string $oldName
493cd08599fSAnna Dabrowska     * @param string $newName
494cd08599fSAnna Dabrowska     */
495cd08599fSAnna Dabrowska    public function renamePage($oldName, $newName) {
496cd08599fSAnna Dabrowska        $db = $this->getDb();
497cd08599fSAnna Dabrowska        $db->query('UPDATE taggings SET pid = ? WHERE pid = ?', $newName, $oldName);
498cd08599fSAnna Dabrowska    }
499972f6adfSAnna Dabrowska
500972f6adfSAnna Dabrowska    /**
501*1b4b4fa9SAnna Dabrowska     * Extracts tags from search query
502*1b4b4fa9SAnna Dabrowska     *
503*1b4b4fa9SAnna Dabrowska     * @param array $parsedQuery
504*1b4b4fa9SAnna Dabrowska     * @return array
505*1b4b4fa9SAnna Dabrowska     */
506*1b4b4fa9SAnna Dabrowska    public function getTags($parsedQuery)
507*1b4b4fa9SAnna Dabrowska    {
508*1b4b4fa9SAnna Dabrowska        $tags = [];
509*1b4b4fa9SAnna Dabrowska        if (isset($parsedQuery['phrases'][0])) {
510*1b4b4fa9SAnna Dabrowska            $tags = $parsedQuery['phrases'];
511*1b4b4fa9SAnna Dabrowska        } elseif (isset($parsedQuery['and'][0])) {
512*1b4b4fa9SAnna Dabrowska            $tags = $parsedQuery['and'];
513*1b4b4fa9SAnna Dabrowska        } elseif (isset($parsedQuery['tag'])) {
514*1b4b4fa9SAnna Dabrowska            // handle autocomplete call
515*1b4b4fa9SAnna Dabrowska            $tags[] = $parsedQuery['tag'];
516*1b4b4fa9SAnna Dabrowska        }
517*1b4b4fa9SAnna Dabrowska        return $tags;
518*1b4b4fa9SAnna Dabrowska    }
519*1b4b4fa9SAnna Dabrowska
520*1b4b4fa9SAnna Dabrowska    /**
521*1b4b4fa9SAnna Dabrowska     * Returns an SQL query string constructed by the query builder
522*1b4b4fa9SAnna Dabrowska     * from given constraints and options
523972f6adfSAnna Dabrowska     *
524972f6adfSAnna Dabrowska     * @param array $filter
525972f6adfSAnna Dabrowska     * @param string $type
526972f6adfSAnna Dabrowska     * @param int $limit
527*1b4b4fa9SAnna Dabrowska     *
528972f6adfSAnna Dabrowska     * @return string
529972f6adfSAnna Dabrowska     */
530*1b4b4fa9SAnna Dabrowska    protected function buildQuery(&$filter, $type, $limit)
531972f6adfSAnna Dabrowska    {
532*1b4b4fa9SAnna Dabrowska        global $INPUT;
533972f6adfSAnna Dabrowska
534*1b4b4fa9SAnna Dabrowska        // search form passes a dummy filter, parsing the actual query instead
535*1b4b4fa9SAnna Dabrowska        if (!$filter) {
536*1b4b4fa9SAnna Dabrowska            global $QUERY;
537*1b4b4fa9SAnna Dabrowska            $filter = ft_queryParser(new Doku_Indexer(), $QUERY);
538972f6adfSAnna Dabrowska        }
539972f6adfSAnna Dabrowska
540*1b4b4fa9SAnna Dabrowska        /** @var helper_plugin_tagging_querybuilder $queryBuilder */
541*1b4b4fa9SAnna Dabrowska        $queryBuilder = new helper_plugin_tagging_querybuilder();
542*1b4b4fa9SAnna Dabrowska        $queryBuilder->setLimit($limit);
543972f6adfSAnna Dabrowska
544*1b4b4fa9SAnna Dabrowska        // if tags are extracted directly form query, they have to be added to the filter,
545*1b4b4fa9SAnna Dabrowska        // which is the only source of parameters
546*1b4b4fa9SAnna Dabrowska        $tags = $this->getTags($filter);
547*1b4b4fa9SAnna Dabrowska        if (!isset($filter['tag'])) $filter['tag'] = $tags;
548*1b4b4fa9SAnna Dabrowska        $queryBuilder->setTags($tags);
549972f6adfSAnna Dabrowska
550*1b4b4fa9SAnna Dabrowska        // remove no longer needed items from an overblown query-based filter
551*1b4b4fa9SAnna Dabrowska        $filter = array_intersect_key($filter, array_flip(self::KNOWN_FILTERS));
552*1b4b4fa9SAnna Dabrowska
553*1b4b4fa9SAnna Dabrowska        $queryBuilder->setLogicalAnd($INPUT->has('taggings') && $INPUT->str('taggings') === 'and');
554*1b4b4fa9SAnna Dabrowska        if (isset($filter['ns'])) $queryBuilder->includeNS($filter['ns']);
555*1b4b4fa9SAnna Dabrowska        if (isset($filter['notns'])) $queryBuilder->excludeNS($filter['notns']);
556*1b4b4fa9SAnna Dabrowska        if (isset($filter['tagger'])) $queryBuilder->setTagger($filter['tagger']);
557*1b4b4fa9SAnna Dabrowska        if (isset($filter['pid'])) $queryBuilder->setPid($filter['pid']);
558*1b4b4fa9SAnna Dabrowska
559*1b4b4fa9SAnna Dabrowska        $queryBuilder->setField($type);
560*1b4b4fa9SAnna Dabrowska
561*1b4b4fa9SAnna Dabrowska        return $queryBuilder->getQuery();
562972f6adfSAnna Dabrowska    }
563972f6adfSAnna Dabrowska
564*1b4b4fa9SAnna Dabrowska    /**
565*1b4b4fa9SAnna Dabrowska     * Converts namespaces into a wildcard form suitable for SQL queries
566*1b4b4fa9SAnna Dabrowska     *
567*1b4b4fa9SAnna Dabrowska     * @param array $item
568*1b4b4fa9SAnna Dabrowska     * @return array
569*1b4b4fa9SAnna Dabrowska     */
570*1b4b4fa9SAnna Dabrowska    protected function formatNS(array $item)
571*1b4b4fa9SAnna Dabrowska    {
572*1b4b4fa9SAnna Dabrowska        return array_map(function($ns) {
573*1b4b4fa9SAnna Dabrowska            if (substr($ns, -1) !== ':') {
574*1b4b4fa9SAnna Dabrowska                $ns .= ':';
575972f6adfSAnna Dabrowska            }
576*1b4b4fa9SAnna Dabrowska            return $ns . '*';
577*1b4b4fa9SAnna Dabrowska        }, $item);
578972f6adfSAnna Dabrowska    }
579cd08599fSAnna Dabrowska}
580