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