1f61105deSAdrian Lang<?php 2ec4796e4SAnna Dabrowska 3*df43a7beSAndreas Gohruse dokuwiki\Extension\Plugin; 4*df43a7beSAndreas Gohruse dokuwiki\Utf8\PhpString; 5ec4796e4SAnna Dabrowskause dokuwiki\Form\Form; 6*df43a7beSAndreas Gohruse dokuwiki\Search\Indexer; 7ec4796e4SAnna Dabrowska 8aa627deeSAndreas Gohr/** 9aa627deeSAndreas Gohr * Tagging Plugin (hlper component) 10aa627deeSAndreas Gohr * 11aa627deeSAndreas Gohr * @license GPL 2 12aa627deeSAndreas Gohr */ 13*df43a7beSAndreas Gohrclass helper_plugin_tagging extends Plugin 14*df43a7beSAndreas Gohr{ 15289f50bdSAndreas Gohr /** 16b12334e1SAndreas Gohr * Gives access to the database 17b12334e1SAndreas Gohr * 18b12334e1SAndreas Gohr * Initializes the SQLite helper and register the CLEANTAG function 19b12334e1SAndreas Gohr * 20b12334e1SAndreas Gohr * @return helper_plugin_sqlite|bool false if initialization fails 21289f50bdSAndreas Gohr */ 22*df43a7beSAndreas Gohr public function getDB() 23*df43a7beSAndreas Gohr { 24302a38efSAndreas Gohr static $db = null; 25aa627deeSAndreas Gohr if ($db !== null) { 26f61105deSAdrian Lang return $db; 27f61105deSAdrian Lang } 28f61105deSAdrian Lang 29302a38efSAndreas Gohr /** @var helper_plugin_sqlite $db */ 30f61105deSAdrian Lang $db = plugin_load('helper', 'sqlite'); 31aa627deeSAndreas Gohr if ($db === null) { 32f61105deSAdrian Lang msg('The tagging plugin needs the sqlite plugin', -1); 33ca455b8eSMichael Große 34f61105deSAdrian Lang return false; 35f61105deSAdrian Lang } 36aa627deeSAndreas Gohr $db->init('tagging', __DIR__ . '/db/'); 37*df43a7beSAndreas Gohr $db->create_function('CLEANTAG', [$this, 'cleanTag'], 1); 38*df43a7beSAndreas Gohr $db->create_function( 39*df43a7beSAndreas Gohr 'GROUP_SORT', 407e05e623SSzymon Olewniczak function ($group, $newDelimiter) { 4140b94b1aSAnna Dabrowska $ex = array_filter(explode(',', $group)); 427e05e623SSzymon Olewniczak sort($ex); 43ca455b8eSMichael Große 447e05e623SSzymon Olewniczak return implode($newDelimiter, $ex); 45*df43a7beSAndreas Gohr }, 46*df43a7beSAndreas Gohr 2 47*df43a7beSAndreas Gohr ); 4840b94b1aSAnna Dabrowska $db->create_function('GET_NS', 'getNS', 1); 49ca455b8eSMichael Große 50f61105deSAdrian Lang return $db; 51f61105deSAdrian Lang } 52f61105deSAdrian Lang 53302a38efSAndreas Gohr /** 542ace74f4SAndreas Gohr * Return the user to use for accessing tags 552ace74f4SAndreas Gohr * 562ace74f4SAndreas Gohr * Handles the singleuser mode by returning 'auto' as user. Returnes false when no user is logged in. 572ace74f4SAndreas Gohr * 582ace74f4SAndreas Gohr * @return bool|string 592ace74f4SAndreas Gohr */ 60*df43a7beSAndreas Gohr public function getUser() 61*df43a7beSAndreas Gohr { 620cfde7e9SMichael Große if (!isset($_SERVER['REMOTE_USER'])) { 630cfde7e9SMichael Große return false; 640cfde7e9SMichael Große } 650cfde7e9SMichael Große if ($this->getConf('singleusermode')) { 660cfde7e9SMichael Große return 'auto'; 670cfde7e9SMichael Große } 68ca455b8eSMichael Große 692ace74f4SAndreas Gohr return $_SERVER['REMOTE_USER']; 702ace74f4SAndreas Gohr } 712ace74f4SAndreas Gohr 722ace74f4SAndreas Gohr /** 73e4443e5cSAnna Dabrowska * If plugin elasticsearch is installed, inform it that we have just made changes 74e4443e5cSAnna Dabrowska * to some data relevant to a page. The page should be re-indexed. 75e4443e5cSAnna Dabrowska * 76e4443e5cSAnna Dabrowska * @param string $id 77e4443e5cSAnna Dabrowska */ 78e4443e5cSAnna Dabrowska public function updateElasticState($id) 79e4443e5cSAnna Dabrowska { 80e4443e5cSAnna Dabrowska /** @var \helper_plugin_elasticsearch_plugins $elasticHelper */ 81e4443e5cSAnna Dabrowska $elasticHelper = plugin_load('helper', 'elasticsearch_plugins'); 82e4443e5cSAnna Dabrowska if ($elasticHelper) { 83e4443e5cSAnna Dabrowska $elasticHelper->updateRefreshState($id); 84e4443e5cSAnna Dabrowska } 85e4443e5cSAnna Dabrowska } 86e4443e5cSAnna Dabrowska 87e4443e5cSAnna Dabrowska /** 88302a38efSAndreas Gohr * Canonicalizes the tag to its lower case nospace form 89302a38efSAndreas Gohr * 90302a38efSAndreas Gohr * @param $tag 910cfde7e9SMichael Große * 92302a38efSAndreas Gohr * @return string 93302a38efSAndreas Gohr */ 94*df43a7beSAndreas Gohr public function cleanTag($tag) 95*df43a7beSAndreas Gohr { 96*df43a7beSAndreas Gohr $tag = str_replace([' ', '-', '_', '#'], '', $tag); 97*df43a7beSAndreas Gohr return PhpString::strtolower($tag); 98302a38efSAndreas Gohr } 99302a38efSAndreas Gohr 10056d82720SAndreas Gohr /** 10131396860SSzymon Olewniczak * Canonicalizes the namespace, remove the first colon and add glob 10231396860SSzymon Olewniczak * 10331396860SSzymon Olewniczak * @param $namespace 10431396860SSzymon Olewniczak * 10531396860SSzymon Olewniczak * @return string 10631396860SSzymon Olewniczak */ 107*df43a7beSAndreas Gohr public function globNamespace($namespace) 108*df43a7beSAndreas Gohr { 109de379874SAnna Dabrowska return cleanId($namespace) . '*'; 11031396860SSzymon Olewniczak } 11131396860SSzymon Olewniczak 11231396860SSzymon Olewniczak /** 11356d82720SAndreas Gohr * Create or Update tags of a page 11456d82720SAndreas Gohr * 11556d82720SAndreas Gohr * Uses the translation plugin to store the language of a page (if available) 11656d82720SAndreas Gohr * 11756d82720SAndreas Gohr * @param string $id The page ID 11856d82720SAndreas Gohr * @param string $user 11956d82720SAndreas Gohr * @param array $tags 1200cfde7e9SMichael Große * 12156d82720SAndreas Gohr * @return bool|SQLiteResult 12256d82720SAndreas Gohr */ 123*df43a7beSAndreas Gohr public function replaceTags($id, $user, $tags) 124*df43a7beSAndreas Gohr { 12556d82720SAndreas Gohr global $conf; 12656d82720SAndreas Gohr /** @var helper_plugin_translation $trans */ 12756d82720SAndreas Gohr $trans = plugin_load('helper', 'translation'); 12856d82720SAndreas Gohr if ($trans) { 12956d82720SAndreas Gohr $lang = $trans->realLC($trans->getLangPart($id)); 13056d82720SAndreas Gohr } else { 13156d82720SAndreas Gohr $lang = $conf['lang']; 13256d82720SAndreas Gohr } 13356d82720SAndreas Gohr 134f61105deSAdrian Lang $db = $this->getDB(); 135f61105deSAdrian Lang $db->query('BEGIN TRANSACTION'); 136*df43a7beSAndreas Gohr 137*df43a7beSAndreas Gohr $queries = [['DELETE FROM taggings WHERE pid = ? AND tagger = ?', $id, $user]]; 138f61105deSAdrian Lang foreach ($tags as $tag) { 139*df43a7beSAndreas Gohr $queries[] = ['INSERT INTO taggings (pid, tagger, tag, lang) VALUES(?, ?, ?, ?)', $id, $user, $tag, $lang]; 140f61105deSAdrian Lang } 141f61105deSAdrian Lang 142f61105deSAdrian Lang foreach ($queries as $query) { 143*df43a7beSAndreas Gohr if (!call_user_func_array([$db, 'query'], $query)) { 144f61105deSAdrian Lang $db->query('ROLLBACK TRANSACTION'); 145ca455b8eSMichael Große 146f61105deSAdrian Lang return false; 147f61105deSAdrian Lang } 148f61105deSAdrian Lang } 149ca455b8eSMichael Große 150f61105deSAdrian Lang return $db->query('COMMIT TRANSACTION'); 151f61105deSAdrian Lang } 152f61105deSAdrian Lang 1530a518a11SAndreas Gohr /** 154b12334e1SAndreas Gohr * Get a list of Tags or Pages matching search criteria 1550a518a11SAndreas Gohr * 156b12334e1SAndreas Gohr * @param array $filter What to search for array('field' => 'searchterm') 157b12334e1SAndreas Gohr * @param string $type What field to return 'tag'|'pid' 158077ff864SAndreas Gohr * @param int $limit Limit to this many results, 0 for all 1590cfde7e9SMichael Große * 1600a518a11SAndreas Gohr * @return array associative array in form of value => count 1610a518a11SAndreas Gohr */ 162*df43a7beSAndreas Gohr public function findItems($filter, $type, $limit = 0) 163*df43a7beSAndreas Gohr { 164*df43a7beSAndreas Gohr $queryBuilder = new helper_plugin_tagging_querybuilder(); 1651b4b4fa9SAnna Dabrowska 1664a7da0a5SAnna Dabrowska $queryBuilder->setField($type); 1674a7da0a5SAnna Dabrowska $queryBuilder->setLimit($limit); 168739c5360SAnna Dabrowska $queryBuilder->setTags($this->extractFromQuery($filter)); 1694a7da0a5SAnna Dabrowska if (isset($filter['ns'])) $queryBuilder->includeNS($filter['ns']); 1704a7da0a5SAnna Dabrowska if (isset($filter['notns'])) $queryBuilder->excludeNS($filter['notns']); 1714a7da0a5SAnna Dabrowska if (isset($filter['tagger'])) $queryBuilder->setTagger($filter['tagger']); 1724a7da0a5SAnna Dabrowska if (isset($filter['pid'])) $queryBuilder->setPid($filter['pid']); 173b12334e1SAndreas Gohr 1744a7da0a5SAnna Dabrowska return $this->queryDb($queryBuilder->getQuery()); 175f61105deSAdrian Lang } 176f61105deSAdrian Lang 177b12334e1SAndreas Gohr /** 178302a38efSAndreas Gohr * Constructs the URL to search for a tag 179302a38efSAndreas Gohr * 1805540f91dSAndreas Gohr * @param string $tag 1815540f91dSAndreas Gohr * @param string $ns 1820cfde7e9SMichael Große * 183302a38efSAndreas Gohr * @return string 184302a38efSAndreas Gohr */ 185*df43a7beSAndreas Gohr public function getTagSearchURL($tag, $ns = '') 186*df43a7beSAndreas Gohr { 187a99fe09cSAnna Dabrowska $ret = '?do=search&sf=1&q=' . rawurlencode('#' . $this->cleanTag($tag)); 1880cfde7e9SMichael Große if ($ns) { 1890cfde7e9SMichael Große $ret .= rawurlencode(' @' . $ns); 1900cfde7e9SMichael Große } 1915540f91dSAndreas Gohr 1925540f91dSAndreas Gohr return $ret; 193f61105deSAdrian Lang } 194f61105deSAdrian Lang 1955540f91dSAndreas Gohr /** 1965540f91dSAndreas Gohr * Calculates the size levels for the given list of clouds 1975540f91dSAndreas Gohr * 1985540f91dSAndreas Gohr * Automatically determines sensible tresholds 1995540f91dSAndreas Gohr * 2005540f91dSAndreas Gohr * @param array $tags list of tags => count 2015540f91dSAndreas Gohr * @param int $levels 2020cfde7e9SMichael Große * 203*df43a7beSAndreas Gohr * @return array 2045540f91dSAndreas Gohr */ 205*df43a7beSAndreas Gohr public function cloudData($tags, $levels = 10) 206*df43a7beSAndreas Gohr { 207f61105deSAdrian Lang $min = min($tags); 208f61105deSAdrian Lang $max = max($tags); 209f61105deSAdrian Lang 210f61105deSAdrian Lang // calculate tresholds 211*df43a7beSAndreas Gohr $tresholds = []; 212f61105deSAdrian Lang for ($i = 0; $i <= $levels; $i++) { 213*df43a7beSAndreas Gohr $tresholds[$i] = ($max - $min + 1) ** ($i / $levels) + $min - 1; 214f61105deSAdrian Lang } 215f61105deSAdrian Lang 216f61105deSAdrian Lang // assign weights 217f61105deSAdrian Lang foreach ($tags as $tag => $cnt) { 218f61105deSAdrian Lang foreach ($tresholds as $tresh => $val) { 219f61105deSAdrian Lang if ($cnt <= $val) { 220f61105deSAdrian Lang $tags[$tag] = $tresh; 221f61105deSAdrian Lang break; 222f61105deSAdrian Lang } 223f61105deSAdrian Lang $tags[$tag] = $levels; 224f61105deSAdrian Lang } 225f61105deSAdrian Lang } 226ca455b8eSMichael Große 227f61105deSAdrian Lang return $tags; 228f61105deSAdrian Lang } 229f61105deSAdrian Lang 2305540f91dSAndreas Gohr /** 2315540f91dSAndreas Gohr * Display a tag cloud 2325540f91dSAndreas Gohr * 2335540f91dSAndreas Gohr * @param array $tags list of tags => count 2345540f91dSAndreas Gohr * @param string $type 'tag' 2355540f91dSAndreas Gohr * @param Callable $func The function to print the link (gets tag and ns) 2365540f91dSAndreas Gohr * @param bool $wrap wrap cloud in UL tags? 2375540f91dSAndreas Gohr * @param bool $return returnn HTML instead of printing? 2385540f91dSAndreas Gohr * @param string $ns Add this namespace to search links 2390cfde7e9SMichael Große * 2405540f91dSAndreas Gohr * @return string 2415540f91dSAndreas Gohr */ 242*df43a7beSAndreas Gohr public function html_cloud($tags, $type, $func, $wrap = true, $return = false, $ns = '') 243*df43a7beSAndreas Gohr { 244a66f6715SAndreas Gohr global $INFO; 245a66f6715SAndreas Gohr 246a66f6715SAndreas Gohr $hidden_str = $this->getConf('hiddenprefix'); 247a66f6715SAndreas Gohr $hidden_len = strlen($hidden_str); 248a66f6715SAndreas Gohr 249f61105deSAdrian Lang $ret = ''; 2500cfde7e9SMichael Große if ($wrap) { 2510cfde7e9SMichael Große $ret .= '<ul class="tagging_cloud clearfix">'; 2520cfde7e9SMichael Große } 253f61105deSAdrian Lang if (count($tags) === 0) { 254f61105deSAdrian Lang // Produce valid XHTML (ul needs a child) 255f61105deSAdrian Lang $this->setupLocale(); 256f61105deSAdrian Lang $ret .= '<li><div class="li">' . $this->lang['js']['no' . $type . 's'] . '</div></li>'; 257f61105deSAdrian Lang } else { 258f61105deSAdrian Lang $tags = $this->cloudData($tags); 259f61105deSAdrian Lang foreach ($tags as $val => $size) { 260a66f6715SAndreas Gohr // skip hidden tags for users that can't edit 261*df43a7beSAndreas Gohr if ( 262*df43a7beSAndreas Gohr $type === 'tag' && 263*df43a7beSAndreas Gohr $hidden_len && 264*df43a7beSAndreas Gohr substr($val, 0, $hidden_len) == $hidden_str && 265a66f6715SAndreas Gohr !($this->getUser() && $INFO['writable']) 266a66f6715SAndreas Gohr ) { 267a66f6715SAndreas Gohr continue; 268a66f6715SAndreas Gohr } 269a66f6715SAndreas Gohr 270f61105deSAdrian Lang $ret .= '<li class="t' . $size . '"><div class="li">'; 2715540f91dSAndreas Gohr $ret .= call_user_func($func, $val, $ns); 272f61105deSAdrian Lang $ret .= '</div></li>'; 273f61105deSAdrian Lang } 274f61105deSAdrian Lang } 2750cfde7e9SMichael Große if ($wrap) { 2760cfde7e9SMichael Große $ret .= '</ul>'; 2770cfde7e9SMichael Große } 2780cfde7e9SMichael Große if ($return) { 2790cfde7e9SMichael Große return $ret; 2800cfde7e9SMichael Große } 281f61105deSAdrian Lang echo $ret; 282ca455b8eSMichael Große 2835540f91dSAndreas Gohr return ''; 284f61105deSAdrian Lang } 285f61105deSAdrian Lang 2865540f91dSAndreas Gohr /** 2870b6fad27Ssandos187 * Display a List of Page Links 2880b6fad27Ssandos187 * 2890b6fad27Ssandos187 * @param array $pids list of pids => count 2900b6fad27Ssandos187 * @return string 2910b6fad27Ssandos187 */ 292*df43a7beSAndreas Gohr public function html_page_list($pids) 293*df43a7beSAndreas Gohr { 2940b6fad27Ssandos187 $ret = '<div class="search_quickresult">'; 2950b6fad27Ssandos187 $ret .= '<ul class="search_quickhits">'; 2960b6fad27Ssandos187 2970b6fad27Ssandos187 if (count($pids) === 0) { 2980b6fad27Ssandos187 // Produce valid XHTML (ul needs a child) 2990b6fad27Ssandos187 $ret .= '<li><div class="li">' . $this->lang['js']['nopages'] . '</div></li>'; 3000b6fad27Ssandos187 } else { 301bdf1ecf0SAnna Dabrowska foreach (array_keys($pids) as $val) { 3020b6fad27Ssandos187 $ret .= '<li><div class="li">'; 303db3ab356SAnna Dabrowska $ret .= html_wikilink(":$val"); 3040b6fad27Ssandos187 $ret .= '</div></li>'; 3050b6fad27Ssandos187 } 3060b6fad27Ssandos187 } 3070b6fad27Ssandos187 3080b6fad27Ssandos187 $ret .= '</ul>'; 3090b6fad27Ssandos187 $ret .= '</div>'; 3100b6fad27Ssandos187 $ret .= '<div class="clearer"></div>'; 3110b6fad27Ssandos187 3120b6fad27Ssandos187 return $ret; 3130b6fad27Ssandos187 } 3140b6fad27Ssandos187 3150b6fad27Ssandos187 /** 3165540f91dSAndreas Gohr * Get the link to a search for the given tag 3175540f91dSAndreas Gohr * 3185540f91dSAndreas Gohr * @param string $tag search for this tag 3195540f91dSAndreas Gohr * @param string $ns limit search to this namespace 3200cfde7e9SMichael Große * 3215540f91dSAndreas Gohr * @return string 3225540f91dSAndreas Gohr */ 323*df43a7beSAndreas Gohr protected function linkToSearch($tag, $ns = '') 324*df43a7beSAndreas Gohr { 3255540f91dSAndreas Gohr return '<a href="' . hsc($this->getTagSearchURL($tag, $ns)) . '">' . $tag . '</a>'; 326f61105deSAdrian Lang } 327f61105deSAdrian Lang 328fb1d0583SAndreas Gohr /** 329fb1d0583SAndreas Gohr * Display the Tags for the current page and prepare the tag editing form 3303496cc8aSAndreas Gohr * 3313496cc8aSAndreas Gohr * @param bool $print Should the HTML be printed or returned? 3320cfde7e9SMichael Große * 3333496cc8aSAndreas Gohr * @return string 334fb1d0583SAndreas Gohr */ 335*df43a7beSAndreas Gohr public function tpl_tags($print = true) 336*df43a7beSAndreas Gohr { 337f61105deSAdrian Lang global $INFO; 338f61105deSAdrian Lang global $lang; 3393bf0e2f1SMichael Große 340*df43a7beSAndreas Gohr $filter = ['pid' => $INFO['id']]; 3413bf0e2f1SMichael Große if ($this->getConf('singleusermode')) { 3423bf0e2f1SMichael Große $filter['tagger'] = 'auto'; 3433bf0e2f1SMichael Große } 3443bf0e2f1SMichael Große 3453bf0e2f1SMichael Große $tags = $this->findItems($filter, 'tag'); 3463496cc8aSAndreas Gohr 347*df43a7beSAndreas Gohr $ret = '<div class="plugin_tagging_edit">'; 348*df43a7beSAndreas Gohr $ret .= $this->html_cloud($tags, 'tag', [$this, 'linkToSearch'], true, true); 349f61105deSAdrian Lang 3502ace74f4SAndreas Gohr if ($this->getUser() && $INFO['writable']) { 351f61105deSAdrian Lang $lang['btn_tagging_edit'] = $lang['btn_secedit']; 352e5b42768SSzymon Olewniczak $ret .= '<div id="tagging__edit_buttons_group">'; 353*df43a7beSAndreas Gohr $ret .= html_btn('tagging_edit', $INFO['id'], '', []); 354dd52fd45SSzymon Olewniczak if (auth_isadmin()) { 35526f61833SAnna Dabrowska $ret .= '<label>' 35626f61833SAnna Dabrowska . $this->getLang('toggle admin mode') 35726f61833SAnna Dabrowska . '<input type="checkbox" id="tagging__edit_toggle_admin" /></label>'; 358dd52fd45SSzymon Olewniczak } 359e5b42768SSzymon Olewniczak $ret .= '</div>'; 360*df43a7beSAndreas Gohr $form = new Form(); 3612819ffcdSSzymon Olewniczak $form->id('tagging__edit'); 3622819ffcdSSzymon Olewniczak $form->setHiddenField('tagging[id]', $INFO['id']); 3632819ffcdSSzymon Olewniczak $form->setHiddenField('call', 'plugin_tagging_save'); 364*df43a7beSAndreas Gohr $tags = $this->findItems(['pid' => $INFO['id'], 'tagger' => $this->getUser()], 'tag'); 36526f61833SAnna Dabrowska $form->addTextarea('tagging[tags]') 36626f61833SAnna Dabrowska ->val(implode(', ', array_keys($tags))) 36726f61833SAnna Dabrowska ->addClass('edit') 36826f61833SAnna Dabrowska ->attr('rows', 4); 369cf52ec2dSSzymon Olewniczak $form->addButton('', $lang['btn_save'])->id('tagging__edit_save'); 370cf52ec2dSSzymon Olewniczak $form->addButton('', $lang['btn_cancel'])->id('tagging__edit_cancel'); 3712819ffcdSSzymon Olewniczak $ret .= $form->toHTML(); 372f61105deSAdrian Lang } 3733496cc8aSAndreas Gohr $ret .= '</div>'; 3743496cc8aSAndreas Gohr 3750cfde7e9SMichael Große if ($print) { 3760cfde7e9SMichael Große echo $ret; 3770cfde7e9SMichael Große } 378ca455b8eSMichael Große 3793496cc8aSAndreas Gohr return $ret; 380f61105deSAdrian Lang } 381872edc7cSRené Corinth 3828a1a3846SAndreas Gohr /** 383a99b66c1SSzymon Olewniczak * @param string $namespace empty for entire wiki 384a99b66c1SSzymon Olewniczak * 38540b94b1aSAnna Dabrowska * @param string $order_by 38640b94b1aSAnna Dabrowska * @param bool $desc 38740b94b1aSAnna Dabrowska * @param array $filters 3888a1a3846SAndreas Gohr * @return array 3898a1a3846SAndreas Gohr */ 390*df43a7beSAndreas Gohr public function getAllTags($namespace = '', $order_by = 'tid', $desc = false, $filters = []) 391*df43a7beSAndreas Gohr { 392*df43a7beSAndreas Gohr $order_fields = ['pid', 'tid', 'taggers', 'ns', 'count']; 393f0084ee1SSzymon Olewniczak if (!in_array($order_by, $order_fields)) { 394f0084ee1SSzymon Olewniczak msg('cannot sort by ' . $order_by . ' field does not exists', -1); 395f0084ee1SSzymon Olewniczak $order_by = 'tag'; 396f0084ee1SSzymon Olewniczak } 397872edc7cSRené Corinth 398*df43a7beSAndreas Gohr [$having, $params] = $this->getFilterSql($filters); 39940b94b1aSAnna Dabrowska 400a2246f69SAnna Dabrowska $db = $this->getDB(); 401872edc7cSRené Corinth 402f0084ee1SSzymon Olewniczak $query = 'SELECT "pid", 403ca455b8eSMichael Große CLEANTAG("tag") AS "tid", 404f0084ee1SSzymon Olewniczak GROUP_SORT(GROUP_CONCAT("tagger"), \', \') AS "taggers", 40540b94b1aSAnna Dabrowska GROUP_SORT(GROUP_CONCAT(GET_NS("pid")), \', \') AS "ns", 40689ed97adSAnna Dabrowska GROUP_SORT(GROUP_CONCAT("pid"), \', \') AS "pids", 407193a767dSSzymon Olewniczak COUNT(*) AS "count" 40857e45304SSzymon Olewniczak FROM "taggings" 4094227fca4SAnna Dabrowska WHERE "pid" GLOB ? AND GETACCESSLEVEL(pid) >= ' . AUTH_READ 4104227fca4SAnna Dabrowska . ' GROUP BY "tid"'; 41140b94b1aSAnna Dabrowska $query .= $having; 41240b94b1aSAnna Dabrowska $query .= 'ORDER BY ' . $order_by; 413ca455b8eSMichael Große if ($desc) { 414ca455b8eSMichael Große $query .= ' DESC'; 415ca455b8eSMichael Große } 416cb469644SSzymon Olewniczak 41740b94b1aSAnna Dabrowska array_unshift($params, $this->globNamespace($namespace)); 41840b94b1aSAnna Dabrowska $res = $db->query($query, $params); 419872edc7cSRené Corinth 4207e05e623SSzymon Olewniczak return $db->res2arr($res); 421872edc7cSRené Corinth } 422872edc7cSRené Corinth 4238a1a3846SAndreas Gohr /** 42472431326SMichael Große * Get all pages with tags and their tags 42572431326SMichael Große * 426790ca788SAndreas Gohr * @return array ['pid' => ['tag1','tag2','tag3']] 42772431326SMichael Große */ 428*df43a7beSAndreas Gohr public function getAllTagsByPage() 429*df43a7beSAndreas Gohr { 43072431326SMichael Große $query = ' 43172431326SMichael Große SELECT pid, GROUP_CONCAT(tag) AS tags 43272431326SMichael Große FROM taggings 43372431326SMichael Große GROUP BY pid 43472431326SMichael Große '; 43572431326SMichael Große $db = $this->getDb(); 43672431326SMichael Große $res = $db->query($query); 437790ca788SAndreas Gohr return array_map( 438*df43a7beSAndreas Gohr fn($i) => explode(',', $i), 439790ca788SAndreas Gohr array_column($db->res2arr($res), 'tags', 'pid') 440790ca788SAndreas Gohr ); 44172431326SMichael Große } 44272431326SMichael Große 44372431326SMichael Große /** 4448a1a3846SAndreas Gohr * Renames a tag 4458a1a3846SAndreas Gohr * 4468a1a3846SAndreas Gohr * @param string $formerTagName 4474227fca4SAnna Dabrowska * @param string $newTagNames 4488a1a3846SAndreas Gohr */ 449*df43a7beSAndreas Gohr public function renameTag($formerTagName, $newTagNames) 450*df43a7beSAndreas Gohr { 451872edc7cSRené Corinth 4524227fca4SAnna Dabrowska if (empty($formerTagName) || empty($newTagNames)) { 4538a1a3846SAndreas Gohr msg($this->getLang("admin enter tag names"), -1); 4548a1a3846SAndreas Gohr return; 455872edc7cSRené Corinth } 456872edc7cSRené Corinth 457870d77ddSAnna Dabrowska $keepFormerTag = false; 458870d77ddSAnna Dabrowska 4594227fca4SAnna Dabrowska // enable splitting tags on rename 460*df43a7beSAndreas Gohr $newTagNames = array_map(fn($tag) => $this->cleanTag($tag), explode(',', $newTagNames)); 4614227fca4SAnna Dabrowska 4624227fca4SAnna Dabrowska $db = $this->getDB(); 463872edc7cSRené Corinth 4644227fca4SAnna Dabrowska // non-admins can rename only their own tags 4654227fca4SAnna Dabrowska if (!auth_isadmin()) { 4664227fca4SAnna Dabrowska $queryTagger = ' AND tagger = ?'; 4674227fca4SAnna Dabrowska $tagger = $this->getUser(); 4684227fca4SAnna Dabrowska } else { 4694227fca4SAnna Dabrowska $queryTagger = ''; 4704227fca4SAnna Dabrowska $tagger = ''; 4714227fca4SAnna Dabrowska } 4724227fca4SAnna Dabrowska 4730ec63874SAnna Dabrowska $insertQuery = 'INSERT INTO taggings '; 4740ec63874SAnna Dabrowska $insertQuery .= 'SELECT pid, ?, tagger, lang FROM taggings'; 4750ec63874SAnna Dabrowska $where = ' WHERE CLEANTAG(tag) = ?'; 4760ec63874SAnna Dabrowska $where .= ' AND GETACCESSLEVEL(pid) >= ' . AUTH_EDIT; 4770ec63874SAnna Dabrowska $where .= $queryTagger; 4780ec63874SAnna Dabrowska 4790ec63874SAnna Dabrowska $db->query('BEGIN TRANSACTION'); 4800ec63874SAnna Dabrowska 4810ec63874SAnna Dabrowska // insert new tags first 4820ec63874SAnna Dabrowska foreach ($newTagNames as $newTag) { 483870d77ddSAnna Dabrowska if ($newTag === $this->cleanTag($formerTagName)) { 484870d77ddSAnna Dabrowska $keepFormerTag = true; 485870d77ddSAnna Dabrowska continue; 486870d77ddSAnna Dabrowska } 487870d77ddSAnna Dabrowska $params = [$newTag, $this->cleanTag($formerTagName)]; 488*df43a7beSAndreas Gohr if ($tagger) $params[] = $tagger; 4890ec63874SAnna Dabrowska $res = $db->query($insertQuery . $where, $params); 4900ec63874SAnna Dabrowska if ($res === false) { 4910ec63874SAnna Dabrowska $db->query('ROLLBACK TRANSACTION'); 4920ec63874SAnna Dabrowska return; 4934227fca4SAnna Dabrowska } 4940ec63874SAnna Dabrowska $db->res_close($res); 4950ec63874SAnna Dabrowska } 4960ec63874SAnna Dabrowska 497870d77ddSAnna Dabrowska // finally delete the renamed tags 498870d77ddSAnna Dabrowska if (!$keepFormerTag) { 4990ec63874SAnna Dabrowska $deleteQuery = 'DELETE FROM taggings'; 5000ec63874SAnna Dabrowska $params = [$this->cleanTag($formerTagName)]; 501*df43a7beSAndreas Gohr if ($tagger) $params[] = $tagger; 5020ec63874SAnna Dabrowska if ($db->query($deleteQuery . $where, $params) === false) { 5030ec63874SAnna Dabrowska $db->query('ROLLBACK TRANSACTION'); 5040ec63874SAnna Dabrowska return; 5050ec63874SAnna Dabrowska } 506870d77ddSAnna Dabrowska } 5070ec63874SAnna Dabrowska 5080ec63874SAnna Dabrowska $db->query('COMMIT TRANSACTION'); 509872edc7cSRené Corinth 510fb1d0583SAndreas Gohr msg($this->getLang("admin renamed"), 1); 511872edc7cSRené Corinth } 512872edc7cSRené Corinth 5138f630140SSzymon Olewniczak /** 514dd52fd45SSzymon Olewniczak * Rename or delete a tag for all users 515dd52fd45SSzymon Olewniczak * 516dd52fd45SSzymon Olewniczak * @param string $pid 517dd52fd45SSzymon Olewniczak * @param string $formerTagName 518dd52fd45SSzymon Olewniczak * @param string $newTagName 519dd52fd45SSzymon Olewniczak * 520dd52fd45SSzymon Olewniczak * @return array 521dd52fd45SSzymon Olewniczak */ 522*df43a7beSAndreas Gohr public function modifyPageTag($pid, $formerTagName, $newTagName) 523*df43a7beSAndreas Gohr { 524dd52fd45SSzymon Olewniczak 525dd52fd45SSzymon Olewniczak $db = $this->getDb(); 526dd52fd45SSzymon Olewniczak 52726f61833SAnna Dabrowska $res = $db->query( 52826f61833SAnna Dabrowska 'SELECT pid FROM taggings WHERE CLEANTAG(tag) = ? AND pid = ?', 52926f61833SAnna Dabrowska $this->cleanTag($formerTagName), 53026f61833SAnna Dabrowska $pid 53126f61833SAnna Dabrowska ); 532dd52fd45SSzymon Olewniczak $check = $db->res2arr($res); 533dd52fd45SSzymon Olewniczak 534dd52fd45SSzymon Olewniczak if (empty($check)) { 535*df43a7beSAndreas Gohr return [true, $this->getLang('admin tag does not exists')]; 536dd52fd45SSzymon Olewniczak } 537dd52fd45SSzymon Olewniczak 538dd52fd45SSzymon Olewniczak if (empty($newTagName)) { 53926f61833SAnna Dabrowska $res = $db->query( 54026f61833SAnna Dabrowska 'DELETE FROM taggings WHERE pid = ? AND CLEANTAG(tag) = ?', 54126f61833SAnna Dabrowska $pid, 54226f61833SAnna Dabrowska $this->cleanTag($formerTagName) 54326f61833SAnna Dabrowska ); 544dd52fd45SSzymon Olewniczak } else { 54526f61833SAnna Dabrowska $res = $db->query( 54626f61833SAnna Dabrowska 'UPDATE taggings SET tag = ? WHERE pid = ? AND CLEANTAG(tag) = ?', 54726f61833SAnna Dabrowska $newTagName, 54826f61833SAnna Dabrowska $pid, 54926f61833SAnna Dabrowska $this->cleanTag($formerTagName) 55026f61833SAnna Dabrowska ); 551dd52fd45SSzymon Olewniczak } 552dd52fd45SSzymon Olewniczak $db->res2arr($res); 553dd52fd45SSzymon Olewniczak 554*df43a7beSAndreas Gohr return [false, $this->getLang('admin renamed')]; 555dd52fd45SSzymon Olewniczak } 556dd52fd45SSzymon Olewniczak 557dd52fd45SSzymon Olewniczak /** 5588f630140SSzymon Olewniczak * Deletes a tag 5598f630140SSzymon Olewniczak * 5608f630140SSzymon Olewniczak * @param array $tags 56131396860SSzymon Olewniczak * @param string $namespace current namespace context as in getAllTags() 5628f630140SSzymon Olewniczak */ 563*df43a7beSAndreas Gohr public function deleteTags($tags, $namespace = '') 564*df43a7beSAndreas Gohr { 565ca455b8eSMichael Große if (empty($tags)) { 566ca455b8eSMichael Große return; 567ca455b8eSMichael Große } 5688f630140SSzymon Olewniczak 56931396860SSzymon Olewniczak $namespace = cleanId($namespace); 57031396860SSzymon Olewniczak 5711f5991a7SMichael Große $db = $this->getDB(); 5728f630140SSzymon Olewniczak 573de379874SAnna Dabrowska $queryBody = 'FROM taggings WHERE pid GLOB ? AND (' . 57431396860SSzymon Olewniczak implode(' OR ', array_fill(0, count($tags), 'CLEANTAG(tag) = ?')) . ')'; 575*df43a7beSAndreas Gohr $args = array_map([$this, 'cleanTag'], $tags); 57631396860SSzymon Olewniczak array_unshift($args, $this->globNamespace($namespace)); 5778f630140SSzymon Olewniczak 5784227fca4SAnna Dabrowska // non-admins can delete only their own tags 5794227fca4SAnna Dabrowska if (!auth_isadmin()) { 5804227fca4SAnna Dabrowska $queryBody .= ' AND tagger = ?'; 581*df43a7beSAndreas Gohr $args[] = $this->getUser(); 5824227fca4SAnna Dabrowska } 583ca455b8eSMichael Große 5841f5991a7SMichael Große $affectedPagesQuery = 'SELECT DISTINCT pid ' . $queryBody; 5851f5991a7SMichael Große $resAffectedPages = $db->query($affectedPagesQuery, $args); 5861f5991a7SMichael Große $numAffectedPages = count($resAffectedPages->fetchAll()); 5871f5991a7SMichael Große 5881f5991a7SMichael Große $deleteQuery = 'DELETE ' . $queryBody; 5891f5991a7SMichael Große $db->query($deleteQuery, $args); 5901f5991a7SMichael Große 5911f5991a7SMichael Große msg(sprintf($this->getLang("admin deleted"), count($tags), $numAffectedPages), 1); 5928f630140SSzymon Olewniczak } 593cd08599fSAnna Dabrowska 594cd08599fSAnna Dabrowska /** 595ec4796e4SAnna Dabrowska * Delete taggings of nonexistent pages 596ec4796e4SAnna Dabrowska */ 597ec4796e4SAnna Dabrowska public function deleteInvalidTaggings() 598ec4796e4SAnna Dabrowska { 599ec4796e4SAnna Dabrowska $db = $this->getDB(); 600ec4796e4SAnna Dabrowska $query = 'DELETE FROM "taggings" 6011815f49fSAnna Dabrowska WHERE NOT PAGEEXISTS(pid) 602ec4796e4SAnna Dabrowska '; 603ec4796e4SAnna Dabrowska $res = $db->query($query); 604ec4796e4SAnna Dabrowska $db->res_close($res); 605ec4796e4SAnna Dabrowska } 606ec4796e4SAnna Dabrowska 607ec4796e4SAnna Dabrowska /** 608cd08599fSAnna Dabrowska * Updates tags with a new page name 609cd08599fSAnna Dabrowska * 610cd08599fSAnna Dabrowska * @param string $oldName 611cd08599fSAnna Dabrowska * @param string $newName 612cd08599fSAnna Dabrowska */ 613*df43a7beSAndreas Gohr public function renamePage($oldName, $newName) 614*df43a7beSAndreas Gohr { 615f6568bcbSAnna Dabrowska $db = $this->getDB(); 616cd08599fSAnna Dabrowska $db->query('UPDATE taggings SET pid = ? WHERE pid = ?', $newName, $oldName); 617cd08599fSAnna Dabrowska } 618972f6adfSAnna Dabrowska 619972f6adfSAnna Dabrowska /** 6201b4b4fa9SAnna Dabrowska * Extracts tags from search query 6211b4b4fa9SAnna Dabrowska * 6221b4b4fa9SAnna Dabrowska * @param array $parsedQuery 6231b4b4fa9SAnna Dabrowska * @return array 6241b4b4fa9SAnna Dabrowska */ 625739c5360SAnna Dabrowska public function extractFromQuery($parsedQuery) 6261b4b4fa9SAnna Dabrowska { 6271b4b4fa9SAnna Dabrowska $tags = []; 6281b4b4fa9SAnna Dabrowska if (isset($parsedQuery['phrases'][0])) { 6291b4b4fa9SAnna Dabrowska $tags = $parsedQuery['phrases']; 6301b4b4fa9SAnna Dabrowska } elseif (isset($parsedQuery['and'][0])) { 6311b4b4fa9SAnna Dabrowska $tags = $parsedQuery['and']; 6321b4b4fa9SAnna Dabrowska } elseif (isset($parsedQuery['tag'])) { 6331b4b4fa9SAnna Dabrowska // handle autocomplete call 6341b4b4fa9SAnna Dabrowska $tags[] = $parsedQuery['tag']; 6351b4b4fa9SAnna Dabrowska } 6361b4b4fa9SAnna Dabrowska return $tags; 6371b4b4fa9SAnna Dabrowska } 6381b4b4fa9SAnna Dabrowska 6391b4b4fa9SAnna Dabrowska /** 6404a7da0a5SAnna Dabrowska * Search for tagged pages 641972f6adfSAnna Dabrowska * 642739c5360SAnna Dabrowska * @param array $tagFiler 6434a7da0a5SAnna Dabrowska * @return array 644972f6adfSAnna Dabrowska */ 645739c5360SAnna Dabrowska public function searchPages($tagFiler) 646972f6adfSAnna Dabrowska { 6471b4b4fa9SAnna Dabrowska global $INPUT; 6481b4b4fa9SAnna Dabrowska global $QUERY; 649*df43a7beSAndreas Gohr $parsedQuery = ft_queryParser(new Indexer(), $QUERY); 650972f6adfSAnna Dabrowska 651*df43a7beSAndreas Gohr $queryBuilder = new helper_plugin_tagging_querybuilder(); 652972f6adfSAnna Dabrowska 6534a7da0a5SAnna Dabrowska $queryBuilder->setField('pid'); 654cbe7b4baSAnna Dabrowska $queryBuilder->setTags($tagFiler); 655f014dfc9SAnna Dabrowska $queryBuilder->setLogicalAnd($INPUT->str('tagging-logic') === 'and'); 6564a7da0a5SAnna Dabrowska if (isset($parsedQuery['ns'])) $queryBuilder->includeNS($parsedQuery['ns']); 6574a7da0a5SAnna Dabrowska if (isset($parsedQuery['notns'])) $queryBuilder->excludeNS($parsedQuery['notns']); 6584a7da0a5SAnna Dabrowska if (isset($parsedQuery['tagger'])) $queryBuilder->setTagger($parsedQuery['tagger']); 6594a7da0a5SAnna Dabrowska if (isset($parsedQuery['pid'])) $queryBuilder->setPid($parsedQuery['pid']); 660972f6adfSAnna Dabrowska 6614a7da0a5SAnna Dabrowska return $this->queryDb($queryBuilder->getPages()); 662972f6adfSAnna Dabrowska } 663972f6adfSAnna Dabrowska 6641b4b4fa9SAnna Dabrowska /** 6654227fca4SAnna Dabrowska * Syntax to allow users to manage tags on regular pages, respects ACLs 6664227fca4SAnna Dabrowska * @param string $ns 6674227fca4SAnna Dabrowska * @return string 6684227fca4SAnna Dabrowska */ 6694227fca4SAnna Dabrowska public function manageTags($ns) 6704227fca4SAnna Dabrowska { 6714227fca4SAnna Dabrowska global $INPUT; 6724227fca4SAnna Dabrowska 673f6568bcbSAnna Dabrowska $this->setDefaultSort(); 6744227fca4SAnna Dabrowska 6754227fca4SAnna Dabrowska // initially set namespace filter to what is defined in syntax 6764227fca4SAnna Dabrowska if ($ns && !$INPUT->has('tagging__filters')) { 6774227fca4SAnna Dabrowska $INPUT->set('tagging__filters', ['ns' => $ns]); 6784227fca4SAnna Dabrowska } 6794227fca4SAnna Dabrowska 6804227fca4SAnna Dabrowska return $this->html_table(); 6814227fca4SAnna Dabrowska } 6824227fca4SAnna Dabrowska 6834227fca4SAnna Dabrowska /** 684a2246f69SAnna Dabrowska * HTML list of tagged pages 685a2246f69SAnna Dabrowska * 686a2246f69SAnna Dabrowska * @param string $tid 687a2246f69SAnna Dabrowska * @return string 688a2246f69SAnna Dabrowska */ 689a2246f69SAnna Dabrowska public function getPagesHtml($tid) 690a2246f69SAnna Dabrowska { 691a2246f69SAnna Dabrowska $html = ''; 692a2246f69SAnna Dabrowska 693a2246f69SAnna Dabrowska $db = $this->getDB(); 69431bddc5fSAnna Dabrowska $sql = 'SELECT pid from taggings where CLEANTAG(tag) = CLEANTAG(?)'; 695a2246f69SAnna Dabrowska $res = $db->query($sql, $tid); 696a2246f69SAnna Dabrowska $pages = $db->res2arr($res); 697a2246f69SAnna Dabrowska 698a2246f69SAnna Dabrowska if ($pages) { 699a2246f69SAnna Dabrowska $html .= '<ul>'; 700a2246f69SAnna Dabrowska foreach ($pages as $page) { 701a2246f69SAnna Dabrowska $pid = $page['pid']; 702a2246f69SAnna Dabrowska $html .= '<li><a href="' . wl($pid) . '" target="_blank">' . $pid . '</li>'; 703a2246f69SAnna Dabrowska } 704a2246f69SAnna Dabrowska $html .= '</ul>'; 705a2246f69SAnna Dabrowska } 706a2246f69SAnna Dabrowska 707a2246f69SAnna Dabrowska return $html; 708a2246f69SAnna Dabrowska } 709a2246f69SAnna Dabrowska 710a2246f69SAnna Dabrowska /** 71189ed97adSAnna Dabrowska * Display tag management table 71289ed97adSAnna Dabrowska */ 713*df43a7beSAndreas Gohr public function html_table() 714*df43a7beSAndreas Gohr { 71589ed97adSAnna Dabrowska global $ID, $INPUT; 71689ed97adSAnna Dabrowska 717*df43a7beSAndreas Gohr $headers = [ 718*df43a7beSAndreas Gohr ['value' => $this->getLang('admin tag'), 'sort_by' => 'tid'], 719*df43a7beSAndreas Gohr ['value' => $this->getLang('admin occurrence'), 'sort_by' => 'count'] 720*df43a7beSAndreas Gohr ]; 72126c4179dSAnna Dabrowska 72226c4179dSAnna Dabrowska if (!$this->conf['hidens']) { 723*df43a7beSAndreas Gohr $headers[] = ['value' => $this->getLang('admin namespaces'), 'sort_by' => 'ns']; 72426c4179dSAnna Dabrowska } 725*df43a7beSAndreas Gohr $headers[] = ['value' => $this->getLang('admin taggers'), 'sort_by' => 'taggers']; 726*df43a7beSAndreas Gohr $headers[] = ['value' => $this->getLang('admin actions'), 'sort_by' => false]; 72789ed97adSAnna Dabrowska 728f6568bcbSAnna Dabrowska $sort = explode(',', $this->getParam('sort')); 72989ed97adSAnna Dabrowska $order_by = $sort[0]; 73089ed97adSAnna Dabrowska $desc = false; 73189ed97adSAnna Dabrowska if (isset($sort[1]) && $sort[1] === 'desc') { 73289ed97adSAnna Dabrowska $desc = true; 73389ed97adSAnna Dabrowska } 73489ed97adSAnna Dabrowska $filters = $INPUT->arr('tagging__filters'); 73589ed97adSAnna Dabrowska 73689ed97adSAnna Dabrowska $tags = $this->getAllTags($INPUT->str('filter'), $order_by, $desc, $filters); 73789ed97adSAnna Dabrowska 738*df43a7beSAndreas Gohr $form = new Form(); 739a2246f69SAnna Dabrowska // required in admin mode 74089ed97adSAnna Dabrowska $form->setHiddenField('page', 'tagging'); 74189ed97adSAnna Dabrowska $form->setHiddenField('id', $ID); 742f6568bcbSAnna Dabrowska $form->setHiddenField('[tagging]sort', $this->getParam('sort')); 74389ed97adSAnna Dabrowska 74489ed97adSAnna Dabrowska /** 74589ed97adSAnna Dabrowska * Actions dialog 74689ed97adSAnna Dabrowska */ 74789ed97adSAnna Dabrowska $form->addTagOpen('div')->id('tagging__action-dialog')->attr('style', "display:none;"); 74889ed97adSAnna Dabrowska $form->addTagClose('div'); 74989ed97adSAnna Dabrowska 75089ed97adSAnna Dabrowska /** 75189ed97adSAnna Dabrowska * Tag pages dialog 75289ed97adSAnna Dabrowska */ 75389ed97adSAnna Dabrowska $form->addTagOpen('div')->id('tagging__taggedpages-dialog')->attr('style', "display:none;"); 75489ed97adSAnna Dabrowska $form->addTagClose('div'); 75589ed97adSAnna Dabrowska 75689ed97adSAnna Dabrowska /** 75789ed97adSAnna Dabrowska * Tag management table 75889ed97adSAnna Dabrowska */ 75989ed97adSAnna Dabrowska $form->addTagOpen('table')->addClass('inline plugin_tagging'); 76089ed97adSAnna Dabrowska 761*df43a7beSAndreas Gohr $nscol = $this->conf['hidens'] ? '' : '<col class="wide-col" />'; 762a305ec34SAnna Dabrowska $form->addHTML( 763a305ec34SAnna Dabrowska '<colgroup> 764*df43a7beSAndreas Gohr <col /> 765*df43a7beSAndreas Gohr <col class="narrow-col" />' 76626c4179dSAnna Dabrowska . $nscol . 767*df43a7beSAndreas Gohr '<col /> 768*df43a7beSAndreas Gohr <col class="narrow-col" /> 769a305ec34SAnna Dabrowska </colgroup>' 770a305ec34SAnna Dabrowska ); 771a305ec34SAnna Dabrowska 77289ed97adSAnna Dabrowska /** 77389ed97adSAnna Dabrowska * Table headers 77489ed97adSAnna Dabrowska */ 77589ed97adSAnna Dabrowska $form->addTagOpen('tr'); 77689ed97adSAnna Dabrowska foreach ($headers as $header) { 77789ed97adSAnna Dabrowska $form->addTagOpen('th'); 77889ed97adSAnna Dabrowska if ($header['sort_by'] !== false) { 77989ed97adSAnna Dabrowska $param = $header['sort_by']; 78089ed97adSAnna Dabrowska $icon = 'arrow-both'; 78189ed97adSAnna Dabrowska $title = $this->getLang('admin sort ascending'); 78289ed97adSAnna Dabrowska if ($header['sort_by'] === $order_by) { 78389ed97adSAnna Dabrowska if ($desc === false) { 78489ed97adSAnna Dabrowska $icon = 'arrow-up'; 78589ed97adSAnna Dabrowska $title = $this->getLang('admin sort descending'); 78689ed97adSAnna Dabrowska $param .= ',desc'; 78789ed97adSAnna Dabrowska } else { 78889ed97adSAnna Dabrowska $icon = 'arrow-down'; 78989ed97adSAnna Dabrowska } 79089ed97adSAnna Dabrowska } 79126f61833SAnna Dabrowska $form->addButtonHTML( 792f6568bcbSAnna Dabrowska "tagging[sort]", 793*df43a7beSAndreas Gohr $header['value'] . ' ' . inlineSVG(__DIR__ . "/images/$icon.svg") 794*df43a7beSAndreas Gohr ) 79589ed97adSAnna Dabrowska ->addClass('plugin_tagging sort_button') 796f6568bcbSAnna Dabrowska ->attr('title', $title) 797f6568bcbSAnna Dabrowska ->val($param); 79889ed97adSAnna Dabrowska } else { 79989ed97adSAnna Dabrowska $form->addHTML($header['value']); 80089ed97adSAnna Dabrowska } 80189ed97adSAnna Dabrowska $form->addTagClose('th'); 80289ed97adSAnna Dabrowska } 80389ed97adSAnna Dabrowska $form->addTagClose('tr'); 80489ed97adSAnna Dabrowska 80589ed97adSAnna Dabrowska /** 80689ed97adSAnna Dabrowska * Table filters for all sortable columns 80789ed97adSAnna Dabrowska */ 80889ed97adSAnna Dabrowska $form->addTagOpen('tr'); 80989ed97adSAnna Dabrowska foreach ($headers as $header) { 81089ed97adSAnna Dabrowska $form->addTagOpen('th'); 81189ed97adSAnna Dabrowska if ($header['sort_by'] !== false) { 81289ed97adSAnna Dabrowska $field = $header['sort_by']; 8137c96ae87SAnna Dabrowska $input = $form->addTextInput("tagging__filters[$field]"); 814a305ec34SAnna Dabrowska $input->addClass('full-col'); 81589ed97adSAnna Dabrowska } 81689ed97adSAnna Dabrowska $form->addTagClose('th'); 81789ed97adSAnna Dabrowska } 81889ed97adSAnna Dabrowska $form->addTagClose('tr'); 81989ed97adSAnna Dabrowska 82089ed97adSAnna Dabrowska 82189ed97adSAnna Dabrowska foreach ($tags as $taginfo) { 82289ed97adSAnna Dabrowska $tagname = $taginfo['tid']; 82389ed97adSAnna Dabrowska $taggers = $taginfo['taggers']; 82489ed97adSAnna Dabrowska $ns = $taginfo['ns']; 82589ed97adSAnna Dabrowska $pids = explode(',', $taginfo['pids']); 82689ed97adSAnna Dabrowska 82789ed97adSAnna Dabrowska $form->addTagOpen('tr'); 82826f61833SAnna Dabrowska $form->addHTML('<td>'); 829a2246f69SAnna Dabrowska $form->addHTML('<a class="tagslist" href="#" data-tid="' . $taginfo['tid'] . '">'); 83026f61833SAnna Dabrowska $form->addHTML(hsc($tagname) . '</a>'); 83126f61833SAnna Dabrowska $form->addHTML('</td>'); 83289ed97adSAnna Dabrowska $form->addHTML('<td>' . $taginfo['count'] . '</td>'); 83326c4179dSAnna Dabrowska if (!$this->conf['hidens']) { 83489ed97adSAnna Dabrowska $form->addHTML('<td>' . hsc($ns) . '</td>'); 83526c4179dSAnna Dabrowska } 83689ed97adSAnna Dabrowska $form->addHTML('<td>' . hsc($taggers) . '</td>'); 83789ed97adSAnna Dabrowska 83889ed97adSAnna Dabrowska /** 83989ed97adSAnna Dabrowska * action buttons 84089ed97adSAnna Dabrowska */ 84189ed97adSAnna Dabrowska $form->addHTML('<td>'); 84289ed97adSAnna Dabrowska 84389ed97adSAnna Dabrowska // check ACLs 84489ed97adSAnna Dabrowska $userEdit = false; 84589ed97adSAnna Dabrowska foreach ($pids as $pid) { 846749c70e5SAndreas Gohr if (auth_quickaclcheck($pid) >= AUTH_EDIT) { 84789ed97adSAnna Dabrowska $userEdit = true; 848*df43a7beSAndreas Gohr break; 84989ed97adSAnna Dabrowska } 85089ed97adSAnna Dabrowska } 85189ed97adSAnna Dabrowska 85289ed97adSAnna Dabrowska if ($userEdit) { 85326f61833SAnna Dabrowska $form->addButtonHTML( 854f6568bcbSAnna Dabrowska 'tagging[actions][rename][' . $taginfo['tid'] . ']', 855*df43a7beSAndreas Gohr inlineSVG(__DIR__ . '/images/edit.svg') 856*df43a7beSAndreas Gohr ) 85726f61833SAnna Dabrowska ->addClass('plugin_tagging action_button') 85826f61833SAnna Dabrowska ->attr('data-action', 'rename') 85926f61833SAnna Dabrowska ->attr('data-tid', $taginfo['tid']); 86026f61833SAnna Dabrowska $form->addButtonHTML( 861f6568bcbSAnna Dabrowska 'tagging[actions][delete][' . $taginfo['tid'] . ']', 862*df43a7beSAndreas Gohr inlineSVG(__DIR__ . '/images/delete.svg') 863*df43a7beSAndreas Gohr ) 86426f61833SAnna Dabrowska ->addClass('plugin_tagging action_button') 86526f61833SAnna Dabrowska ->attr('data-action', 'delete') 86626f61833SAnna Dabrowska ->attr('data-tid', $taginfo['tid']); 86789ed97adSAnna Dabrowska } 86889ed97adSAnna Dabrowska 86989ed97adSAnna Dabrowska $form->addHTML('</td>'); 87089ed97adSAnna Dabrowska $form->addTagClose('tr'); 87189ed97adSAnna Dabrowska } 87289ed97adSAnna Dabrowska 87389ed97adSAnna Dabrowska $form->addTagClose('table'); 8740b033188SAnna Dabrowska return '<div class="table">' . $form->toHTML() . '</div>'; 87589ed97adSAnna Dabrowska } 87689ed97adSAnna Dabrowska 87789ed97adSAnna Dabrowska /** 878ec4796e4SAnna Dabrowska * Display tag cleaner 879ec4796e4SAnna Dabrowska * 880ec4796e4SAnna Dabrowska * @return string 881ec4796e4SAnna Dabrowska */ 882ec4796e4SAnna Dabrowska public function html_clean() 883ec4796e4SAnna Dabrowska { 884ec4796e4SAnna Dabrowska $invalid = $this->getInvalidTaggings(); 885ec4796e4SAnna Dabrowska 886ec4796e4SAnna Dabrowska if (!$invalid) { 887ec4796e4SAnna Dabrowska return '<p><strong>' . $this->getLang('admin no invalid') . '</strong></p>'; 888ec4796e4SAnna Dabrowska } 889ec4796e4SAnna Dabrowska 890ec4796e4SAnna Dabrowska $form = new Form(); 891ec4796e4SAnna Dabrowska $form->setHiddenField('do', 'admin'); 892ec4796e4SAnna Dabrowska $form->setHiddenField('page', $this->getPluginName()); 893ec4796e4SAnna Dabrowska $form->addButton('cmd[clean]', $this->getLang('admin clean')); 894ec4796e4SAnna Dabrowska 895ec4796e4SAnna Dabrowska $html = $form->toHTML(); 896ec4796e4SAnna Dabrowska 897ec4796e4SAnna Dabrowska $html .= '<div class="table"><table class="inline plugin_tagging">'; 898ec4796e4SAnna Dabrowska $html .= '<thead><tr><th>' . 899ec4796e4SAnna Dabrowska $this->getLang('admin nonexistent page') . 900ec4796e4SAnna Dabrowska '</th><th>' . 901ec4796e4SAnna Dabrowska $this->getLang('admin tags') . 902ec4796e4SAnna Dabrowska '</th></tr></thead><tbody>'; 903ec4796e4SAnna Dabrowska 904ec4796e4SAnna Dabrowska foreach ($invalid as $row) { 905ec4796e4SAnna Dabrowska $html .= '<tr><td>' . $row['pid'] . '</td><td>' . $row['tags'] . '</td></tr>'; 906ec4796e4SAnna Dabrowska } 907ec4796e4SAnna Dabrowska 908ec4796e4SAnna Dabrowska $html .= '</tbody></table></div>'; 909ec4796e4SAnna Dabrowska 910ec4796e4SAnna Dabrowska return $html; 911ec4796e4SAnna Dabrowska } 912ec4796e4SAnna Dabrowska 913ec4796e4SAnna Dabrowska /** 914f6568bcbSAnna Dabrowska * Returns all tagging parameters from the query string 915f6568bcbSAnna Dabrowska * 916f6568bcbSAnna Dabrowska * @return mixed 917f6568bcbSAnna Dabrowska */ 918f6568bcbSAnna Dabrowska public function getParams() 919f6568bcbSAnna Dabrowska { 920f6568bcbSAnna Dabrowska global $INPUT; 921f6568bcbSAnna Dabrowska return $INPUT->param('tagging', []); 922f6568bcbSAnna Dabrowska } 923f6568bcbSAnna Dabrowska 924f6568bcbSAnna Dabrowska /** 925f6568bcbSAnna Dabrowska * Get a tagging parameter, empty string if not set 926f6568bcbSAnna Dabrowska * 927f6568bcbSAnna Dabrowska * @param string $name 928*df43a7beSAndreas Gohr * @return string 929f6568bcbSAnna Dabrowska */ 930f6568bcbSAnna Dabrowska public function getParam($name) 931f6568bcbSAnna Dabrowska { 932f6568bcbSAnna Dabrowska $params = $this->getParams(); 933f6568bcbSAnna Dabrowska if ($params) { 934f6568bcbSAnna Dabrowska return $params[$name] ?: ''; 935f6568bcbSAnna Dabrowska } 936*df43a7beSAndreas Gohr return ''; 937f6568bcbSAnna Dabrowska } 938f6568bcbSAnna Dabrowska 939f6568bcbSAnna Dabrowska /** 940f6568bcbSAnna Dabrowska * Sets a tagging parameter 941f6568bcbSAnna Dabrowska * 942f6568bcbSAnna Dabrowska * @param string $name 943f6568bcbSAnna Dabrowska * @param string|array $value 944f6568bcbSAnna Dabrowska */ 945f6568bcbSAnna Dabrowska public function setParam($name, $value) 946f6568bcbSAnna Dabrowska { 947f6568bcbSAnna Dabrowska global $INPUT; 948f6568bcbSAnna Dabrowska $params = $this->getParams(); 949f6568bcbSAnna Dabrowska $params = array_merge($params, [$name => $value]); 950*df43a7beSAndreas Gohr 951f6568bcbSAnna Dabrowska $INPUT->set('tagging', $params); 952f6568bcbSAnna Dabrowska } 953f6568bcbSAnna Dabrowska 954f6568bcbSAnna Dabrowska /** 955f6568bcbSAnna Dabrowska * Default sorting by tag id 956f6568bcbSAnna Dabrowska */ 957f6568bcbSAnna Dabrowska public function setDefaultSort() 958f6568bcbSAnna Dabrowska { 959f6568bcbSAnna Dabrowska if (!$this->getParam('sort')) { 960f6568bcbSAnna Dabrowska $this->setParam('sort', 'tid'); 961f6568bcbSAnna Dabrowska } 962f6568bcbSAnna Dabrowska } 963f6568bcbSAnna Dabrowska 964f6568bcbSAnna Dabrowska /** 9654a7da0a5SAnna Dabrowska * Executes the query and returns the results as array 9661b4b4fa9SAnna Dabrowska * 96799122157SAnna Dabrowska * @param array $query 9681b4b4fa9SAnna Dabrowska * @return array 9691b4b4fa9SAnna Dabrowska */ 9704a7da0a5SAnna Dabrowska protected function queryDb($query) 9711b4b4fa9SAnna Dabrowska { 9724a7da0a5SAnna Dabrowska $db = $this->getDB(); 9734a7da0a5SAnna Dabrowska if (!$db) { 9744a7da0a5SAnna Dabrowska return []; 975972f6adfSAnna Dabrowska } 9764a7da0a5SAnna Dabrowska 97799122157SAnna Dabrowska $res = $db->query($query[0], $query[1]); 9784a7da0a5SAnna Dabrowska $res = $db->res2arr($res); 9794a7da0a5SAnna Dabrowska 9804a7da0a5SAnna Dabrowska $ret = []; 9814a7da0a5SAnna Dabrowska foreach ($res as $row) { 9824a7da0a5SAnna Dabrowska $ret[$row['item']] = $row['cnt']; 9834a7da0a5SAnna Dabrowska } 9844a7da0a5SAnna Dabrowska return $ret; 985972f6adfSAnna Dabrowska } 98640b94b1aSAnna Dabrowska 98740b94b1aSAnna Dabrowska /** 98840b94b1aSAnna Dabrowska * Construct the HAVING part of the search query 98940b94b1aSAnna Dabrowska * 99040b94b1aSAnna Dabrowska * @param array $filters 99140b94b1aSAnna Dabrowska * @return array 99240b94b1aSAnna Dabrowska */ 99340b94b1aSAnna Dabrowska protected function getFilterSql($filters) 99440b94b1aSAnna Dabrowska { 99540b94b1aSAnna Dabrowska $having = ''; 99640b94b1aSAnna Dabrowska $parts = []; 99740b94b1aSAnna Dabrowska $params = []; 99840b94b1aSAnna Dabrowska $filters = array_filter($filters); 999*df43a7beSAndreas Gohr if ($filters !== []) { 100040b94b1aSAnna Dabrowska $having = ' HAVING '; 100140b94b1aSAnna Dabrowska foreach ($filters as $filter => $value) { 100240b94b1aSAnna Dabrowska $parts[] = " $filter LIKE ? "; 100340b94b1aSAnna Dabrowska $params[] = "%$value%"; 100440b94b1aSAnna Dabrowska } 100540b94b1aSAnna Dabrowska $having .= implode(' AND ', $parts); 100640b94b1aSAnna Dabrowska } 100740b94b1aSAnna Dabrowska return [$having, $params]; 100840b94b1aSAnna Dabrowska } 1009ec4796e4SAnna Dabrowska 1010ec4796e4SAnna Dabrowska /** 1011ec4796e4SAnna Dabrowska * Returns taggings of nonexistent pages 1012ec4796e4SAnna Dabrowska * 1013ec4796e4SAnna Dabrowska * @return array 1014ec4796e4SAnna Dabrowska */ 1015ec4796e4SAnna Dabrowska protected function getInvalidTaggings() 1016ec4796e4SAnna Dabrowska { 1017ec4796e4SAnna Dabrowska $db = $this->getDB(); 1018ec4796e4SAnna Dabrowska $query = 'SELECT "pid", 1019ec4796e4SAnna Dabrowska GROUP_CONCAT(CLEANTAG("tag")) AS "tags" 1020ec4796e4SAnna Dabrowska FROM "taggings" 1021f24ac95eSAnna Dabrowska WHERE NOT PAGEEXISTS(pid) 1022ec4796e4SAnna Dabrowska GROUP BY pid 1023ec4796e4SAnna Dabrowska '; 1024ec4796e4SAnna Dabrowska $res = $db->query($query); 1025ec4796e4SAnna Dabrowska return $db->res2arr($res); 1026ec4796e4SAnna Dabrowska } 1027cd08599fSAnna Dabrowska} 1028