1f61105deSAdrian Lang<?php 2ec4796e4SAnna Dabrowska 3df43a7beSAndreas Gohruse dokuwiki\Extension\Plugin; 4df43a7beSAndreas Gohruse dokuwiki\Utf8\PhpString; 5ec4796e4SAnna Dabrowskause dokuwiki\Form\Form; 6df43a7beSAndreas Gohruse dokuwiki\Search\Indexer; 755d5df8cSAnna Dabrowskause dokuwiki\File\PageResolver; 8*a6b932a0SAnna Dabrowskause dokuwiki\plugin\sqlite\SQLiteDB; 9ec4796e4SAnna Dabrowska 10aa627deeSAndreas Gohr/** 11*a6b932a0SAnna Dabrowska * Tagging Plugin (helper component) 12aa627deeSAndreas Gohr * 13aa627deeSAndreas Gohr * @license GPL 2 14aa627deeSAndreas Gohr */ 15df43a7beSAndreas Gohrclass helper_plugin_tagging extends Plugin 16df43a7beSAndreas Gohr{ 17289f50bdSAndreas Gohr /** 18b12334e1SAndreas Gohr * Gives access to the database 19*a6b932a0SAnna Dabrowska * Initializes SQLite and registers custom functions 20b12334e1SAndreas Gohr * 21*a6b932a0SAnna Dabrowska * @return SQLiteDB|null 22*a6b932a0SAnna Dabrowska * @throws Exception 23289f50bdSAndreas Gohr */ 24df43a7beSAndreas Gohr public function getDB() 25df43a7beSAndreas Gohr { 26302a38efSAndreas Gohr static $db = null; 27aa627deeSAndreas Gohr if ($db !== null) { 28f61105deSAdrian Lang return $db; 29f61105deSAdrian Lang } 30f61105deSAdrian Lang 31ca455b8eSMichael Große 32*a6b932a0SAnna Dabrowska if (!class_exists(SQLiteDB::class)) throw new Exception('SQLite Plugin missing'); 33*a6b932a0SAnna Dabrowska $db = new SQLiteDB('tagging', __DIR__ . '/db/'); 34*a6b932a0SAnna Dabrowska 35*a6b932a0SAnna Dabrowska $db->getPdo()->sqliteCreateFunction('CLEANTAG', [$this, 'cleanTag'], 1); 36*a6b932a0SAnna Dabrowska $db->getPdo()->sqliteCreateFunction( 37df43a7beSAndreas Gohr 'GROUP_SORT', 387e05e623SSzymon Olewniczak function ($group, $newDelimiter) { 3940b94b1aSAnna Dabrowska $ex = array_filter(explode(',', $group)); 407e05e623SSzymon Olewniczak sort($ex); 41ca455b8eSMichael Große 427e05e623SSzymon Olewniczak return implode($newDelimiter, $ex); 43df43a7beSAndreas Gohr }, 44df43a7beSAndreas Gohr 2 45df43a7beSAndreas Gohr ); 46*a6b932a0SAnna Dabrowska $db->getPdo()->sqliteCreateFunction('GET_NS', 'getNS', 1); 47ca455b8eSMichael Große 48f61105deSAdrian Lang return $db; 49f61105deSAdrian Lang } 50f61105deSAdrian Lang 51302a38efSAndreas Gohr /** 522ace74f4SAndreas Gohr * Return the user to use for accessing tags 532ace74f4SAndreas Gohr * 542ace74f4SAndreas Gohr * Handles the singleuser mode by returning 'auto' as user. Returnes false when no user is logged in. 552ace74f4SAndreas Gohr * 562ace74f4SAndreas Gohr * @return bool|string 572ace74f4SAndreas Gohr */ 58df43a7beSAndreas Gohr public function getUser() 59df43a7beSAndreas Gohr { 600cfde7e9SMichael Große if (!isset($_SERVER['REMOTE_USER'])) { 610cfde7e9SMichael Große return false; 620cfde7e9SMichael Große } 630cfde7e9SMichael Große if ($this->getConf('singleusermode')) { 640cfde7e9SMichael Große return 'auto'; 650cfde7e9SMichael Große } 66ca455b8eSMichael Große 672ace74f4SAndreas Gohr return $_SERVER['REMOTE_USER']; 682ace74f4SAndreas Gohr } 692ace74f4SAndreas Gohr 702ace74f4SAndreas Gohr /** 71e4443e5cSAnna Dabrowska * If plugin elasticsearch is installed, inform it that we have just made changes 72e4443e5cSAnna Dabrowska * to some data relevant to a page. The page should be re-indexed. 73e4443e5cSAnna Dabrowska * 74e4443e5cSAnna Dabrowska * @param string $id 75e4443e5cSAnna Dabrowska */ 76e4443e5cSAnna Dabrowska public function updateElasticState($id) 77e4443e5cSAnna Dabrowska { 78e4443e5cSAnna Dabrowska /** @var \helper_plugin_elasticsearch_plugins $elasticHelper */ 79e4443e5cSAnna Dabrowska $elasticHelper = plugin_load('helper', 'elasticsearch_plugins'); 80e4443e5cSAnna Dabrowska if ($elasticHelper) { 81e4443e5cSAnna Dabrowska $elasticHelper->updateRefreshState($id); 82e4443e5cSAnna Dabrowska } 83e4443e5cSAnna Dabrowska } 84e4443e5cSAnna Dabrowska 85e4443e5cSAnna Dabrowska /** 8655d5df8cSAnna Dabrowska * Resolve the ns filter 8755d5df8cSAnna Dabrowska * 8855d5df8cSAnna Dabrowska * @param array $data 8955d5df8cSAnna Dabrowska * @return string 9055d5df8cSAnna Dabrowska */ 9155d5df8cSAnna Dabrowska public function resolveNs(array $data) 9255d5df8cSAnna Dabrowska { 9355d5df8cSAnna Dabrowska if (!isset($data['ns'])) { 9455d5df8cSAnna Dabrowska $data['ns'] = '.'; 9555d5df8cSAnna Dabrowska } 9655d5df8cSAnna Dabrowska if ($data['ns'] === '*') { 9755d5df8cSAnna Dabrowska $data['ns'] = ''; 9855d5df8cSAnna Dabrowska } 9955d5df8cSAnna Dabrowska 10055d5df8cSAnna Dabrowska global $ID; 10155d5df8cSAnna Dabrowska $resolver = new PageResolver($ID); 10255d5df8cSAnna Dabrowska $data['ns'] = getNS($resolver->resolveId($data['ns'] . ':fakeIdNotResolvable')) ?: ':'; 10355d5df8cSAnna Dabrowska 10455d5df8cSAnna Dabrowska return $data['ns']; 10555d5df8cSAnna Dabrowska } 10655d5df8cSAnna Dabrowska 10755d5df8cSAnna Dabrowska /** 108302a38efSAndreas Gohr * Canonicalizes the tag to its lower case nospace form 109302a38efSAndreas Gohr * 110302a38efSAndreas Gohr * @param $tag 1110cfde7e9SMichael Große * 112302a38efSAndreas Gohr * @return string 113302a38efSAndreas Gohr */ 114df43a7beSAndreas Gohr public function cleanTag($tag) 115df43a7beSAndreas Gohr { 116df43a7beSAndreas Gohr $tag = str_replace([' ', '-', '_', '#'], '', $tag); 117df43a7beSAndreas Gohr return PhpString::strtolower($tag); 118302a38efSAndreas Gohr } 119302a38efSAndreas Gohr 12056d82720SAndreas Gohr /** 12131396860SSzymon Olewniczak * Canonicalizes the namespace, remove the first colon and add glob 12231396860SSzymon Olewniczak * 12331396860SSzymon Olewniczak * @param $namespace 12431396860SSzymon Olewniczak * 12531396860SSzymon Olewniczak * @return string 12631396860SSzymon Olewniczak */ 127df43a7beSAndreas Gohr public function globNamespace($namespace) 128df43a7beSAndreas Gohr { 129*a6b932a0SAnna Dabrowska return cleanID($namespace) . '*'; 13031396860SSzymon Olewniczak } 13131396860SSzymon Olewniczak 13231396860SSzymon Olewniczak /** 13356d82720SAndreas Gohr * Create or Update tags of a page 13456d82720SAndreas Gohr * 13556d82720SAndreas Gohr * Uses the translation plugin to store the language of a page (if available) 13656d82720SAndreas Gohr * 13756d82720SAndreas Gohr * @param string $id The page ID 13856d82720SAndreas Gohr * @param string $user 13956d82720SAndreas Gohr * @param array $tags 1400cfde7e9SMichael Große * 14156d82720SAndreas Gohr */ 142df43a7beSAndreas Gohr public function replaceTags($id, $user, $tags) 143df43a7beSAndreas Gohr { 14456d82720SAndreas Gohr global $conf; 14556d82720SAndreas Gohr /** @var helper_plugin_translation $trans */ 14656d82720SAndreas Gohr $trans = plugin_load('helper', 'translation'); 14756d82720SAndreas Gohr if ($trans) { 14856d82720SAndreas Gohr $lang = $trans->realLC($trans->getLangPart($id)); 14956d82720SAndreas Gohr } else { 15056d82720SAndreas Gohr $lang = $conf['lang']; 15156d82720SAndreas Gohr } 15256d82720SAndreas Gohr 153f61105deSAdrian Lang $db = $this->getDB(); 154f61105deSAdrian Lang $db->query('BEGIN TRANSACTION'); 155df43a7beSAndreas Gohr 156df43a7beSAndreas Gohr $queries = [['DELETE FROM taggings WHERE pid = ? AND tagger = ?', $id, $user]]; 157f61105deSAdrian Lang foreach ($tags as $tag) { 158df43a7beSAndreas Gohr $queries[] = ['INSERT INTO taggings (pid, tagger, tag, lang) VALUES(?, ?, ?, ?)', $id, $user, $tag, $lang]; 159f61105deSAdrian Lang } 160f61105deSAdrian Lang 161f61105deSAdrian Lang foreach ($queries as $query) { 162*a6b932a0SAnna Dabrowska try { 163*a6b932a0SAnna Dabrowska call_user_func_array([$db, 'exec'], $query); 164*a6b932a0SAnna Dabrowska } catch (\PDOException $e) { 165*a6b932a0SAnna Dabrowska \dokuwiki\Logger::error("Tagging: replacing tag failed - " . $e->getMessage()); 166f61105deSAdrian Lang $db->query('ROLLBACK TRANSACTION'); 167*a6b932a0SAnna Dabrowska return; 168f61105deSAdrian Lang } 169f61105deSAdrian Lang } 170ca455b8eSMichael Große 171*a6b932a0SAnna Dabrowska $db->query('COMMIT TRANSACTION'); 172f61105deSAdrian Lang } 173f61105deSAdrian Lang 1740a518a11SAndreas Gohr /** 175b12334e1SAndreas Gohr * Get a list of Tags or Pages matching search criteria 1760a518a11SAndreas Gohr * 177b12334e1SAndreas Gohr * @param array $filter What to search for array('field' => 'searchterm') 178b12334e1SAndreas Gohr * @param string $type What field to return 'tag'|'pid' 179077ff864SAndreas Gohr * @param int $limit Limit to this many results, 0 for all 1800cfde7e9SMichael Große * 1810a518a11SAndreas Gohr * @return array associative array in form of value => count 1820a518a11SAndreas Gohr */ 183df43a7beSAndreas Gohr public function findItems($filter, $type, $limit = 0) 184df43a7beSAndreas Gohr { 185df43a7beSAndreas Gohr $queryBuilder = new helper_plugin_tagging_querybuilder(); 1861b4b4fa9SAnna Dabrowska 1874a7da0a5SAnna Dabrowska $queryBuilder->setField($type); 1884a7da0a5SAnna Dabrowska $queryBuilder->setLimit($limit); 189739c5360SAnna Dabrowska $queryBuilder->setTags($this->extractFromQuery($filter)); 1904a7da0a5SAnna Dabrowska if (isset($filter['ns'])) $queryBuilder->includeNS($filter['ns']); 1914a7da0a5SAnna Dabrowska if (isset($filter['notns'])) $queryBuilder->excludeNS($filter['notns']); 1924a7da0a5SAnna Dabrowska if (isset($filter['tagger'])) $queryBuilder->setTagger($filter['tagger']); 1934a7da0a5SAnna Dabrowska if (isset($filter['pid'])) $queryBuilder->setPid($filter['pid']); 194b12334e1SAndreas Gohr 1954a7da0a5SAnna Dabrowska return $this->queryDb($queryBuilder->getQuery()); 196f61105deSAdrian Lang } 197f61105deSAdrian Lang 198b12334e1SAndreas Gohr /** 199302a38efSAndreas Gohr * Constructs the URL to search for a tag 200302a38efSAndreas Gohr * 2015540f91dSAndreas Gohr * @param string $tag 2025540f91dSAndreas Gohr * @param string $ns 2030cfde7e9SMichael Große * 204302a38efSAndreas Gohr * @return string 205302a38efSAndreas Gohr */ 206df43a7beSAndreas Gohr public function getTagSearchURL($tag, $ns = '') 207df43a7beSAndreas Gohr { 208a99fe09cSAnna Dabrowska $ret = '?do=search&sf=1&q=' . rawurlencode('#' . $this->cleanTag($tag)); 20955d5df8cSAnna Dabrowska if ($ns && $ns !== ':') { 2100cfde7e9SMichael Große $ret .= rawurlencode(' @' . $ns); 2110cfde7e9SMichael Große } 2125540f91dSAndreas Gohr 2135540f91dSAndreas Gohr return $ret; 214f61105deSAdrian Lang } 215f61105deSAdrian Lang 2165540f91dSAndreas Gohr /** 2175540f91dSAndreas Gohr * Calculates the size levels for the given list of clouds 2185540f91dSAndreas Gohr * 2195540f91dSAndreas Gohr * Automatically determines sensible tresholds 2205540f91dSAndreas Gohr * 2215540f91dSAndreas Gohr * @param array $tags list of tags => count 2225540f91dSAndreas Gohr * @param int $levels 2230cfde7e9SMichael Große * 224df43a7beSAndreas Gohr * @return array 2255540f91dSAndreas Gohr */ 226df43a7beSAndreas Gohr public function cloudData($tags, $levels = 10) 227df43a7beSAndreas Gohr { 228f61105deSAdrian Lang $min = min($tags); 229f61105deSAdrian Lang $max = max($tags); 230f61105deSAdrian Lang 231f61105deSAdrian Lang // calculate tresholds 232df43a7beSAndreas Gohr $tresholds = []; 233f61105deSAdrian Lang for ($i = 0; $i <= $levels; $i++) { 234df43a7beSAndreas Gohr $tresholds[$i] = ($max - $min + 1) ** ($i / $levels) + $min - 1; 235f61105deSAdrian Lang } 236f61105deSAdrian Lang 237f61105deSAdrian Lang // assign weights 238f61105deSAdrian Lang foreach ($tags as $tag => $cnt) { 239f61105deSAdrian Lang foreach ($tresholds as $tresh => $val) { 240f61105deSAdrian Lang if ($cnt <= $val) { 241f61105deSAdrian Lang $tags[$tag] = $tresh; 242f61105deSAdrian Lang break; 243f61105deSAdrian Lang } 244f61105deSAdrian Lang $tags[$tag] = $levels; 245f61105deSAdrian Lang } 246f61105deSAdrian Lang } 247ca455b8eSMichael Große 248f61105deSAdrian Lang return $tags; 249f61105deSAdrian Lang } 250f61105deSAdrian Lang 2515540f91dSAndreas Gohr /** 2525540f91dSAndreas Gohr * Display a tag cloud 2535540f91dSAndreas Gohr * 2545540f91dSAndreas Gohr * @param array $tags list of tags => count 2555540f91dSAndreas Gohr * @param string $type 'tag' 2565540f91dSAndreas Gohr * @param Callable $func The function to print the link (gets tag and ns) 2575540f91dSAndreas Gohr * @param bool $wrap wrap cloud in UL tags? 2585540f91dSAndreas Gohr * @param bool $return returnn HTML instead of printing? 2595540f91dSAndreas Gohr * @param string $ns Add this namespace to search links 2600cfde7e9SMichael Große * 2615540f91dSAndreas Gohr * @return string 2625540f91dSAndreas Gohr */ 263df43a7beSAndreas Gohr public function html_cloud($tags, $type, $func, $wrap = true, $return = false, $ns = '') 264df43a7beSAndreas Gohr { 265a66f6715SAndreas Gohr global $INFO; 266a66f6715SAndreas Gohr 267a66f6715SAndreas Gohr $hidden_str = $this->getConf('hiddenprefix'); 268a66f6715SAndreas Gohr $hidden_len = strlen($hidden_str); 269a66f6715SAndreas Gohr 270f61105deSAdrian Lang $ret = ''; 2710cfde7e9SMichael Große if ($wrap) { 2720cfde7e9SMichael Große $ret .= '<ul class="tagging_cloud clearfix">'; 2730cfde7e9SMichael Große } 274f61105deSAdrian Lang if (count($tags) === 0) { 275f61105deSAdrian Lang // Produce valid XHTML (ul needs a child) 276f61105deSAdrian Lang $this->setupLocale(); 277f61105deSAdrian Lang $ret .= '<li><div class="li">' . $this->lang['js']['no' . $type . 's'] . '</div></li>'; 278f61105deSAdrian Lang } else { 279f61105deSAdrian Lang $tags = $this->cloudData($tags); 280f61105deSAdrian Lang foreach ($tags as $val => $size) { 281a66f6715SAndreas Gohr // skip hidden tags for users that can't edit 282df43a7beSAndreas Gohr if ( 283df43a7beSAndreas Gohr $type === 'tag' && 284df43a7beSAndreas Gohr $hidden_len && 285df43a7beSAndreas Gohr substr($val, 0, $hidden_len) == $hidden_str && 286a66f6715SAndreas Gohr !($this->getUser() && $INFO['writable']) 287a66f6715SAndreas Gohr ) { 288a66f6715SAndreas Gohr continue; 289a66f6715SAndreas Gohr } 290a66f6715SAndreas Gohr 291f61105deSAdrian Lang $ret .= '<li class="t' . $size . '"><div class="li">'; 2925540f91dSAndreas Gohr $ret .= call_user_func($func, $val, $ns); 293f61105deSAdrian Lang $ret .= '</div></li>'; 294f61105deSAdrian Lang } 295f61105deSAdrian Lang } 2960cfde7e9SMichael Große if ($wrap) { 2970cfde7e9SMichael Große $ret .= '</ul>'; 2980cfde7e9SMichael Große } 2990cfde7e9SMichael Große if ($return) { 3000cfde7e9SMichael Große return $ret; 3010cfde7e9SMichael Große } 302f61105deSAdrian Lang echo $ret; 303ca455b8eSMichael Große 3045540f91dSAndreas Gohr return ''; 305f61105deSAdrian Lang } 306f61105deSAdrian Lang 3075540f91dSAndreas Gohr /** 3080b6fad27Ssandos187 * Display a List of Page Links 3090b6fad27Ssandos187 * 3100b6fad27Ssandos187 * @param array $pids list of pids => count 3110b6fad27Ssandos187 * @return string 3120b6fad27Ssandos187 */ 313df43a7beSAndreas Gohr public function html_page_list($pids) 314df43a7beSAndreas Gohr { 3150b6fad27Ssandos187 $ret = '<div class="search_quickresult">'; 3160b6fad27Ssandos187 $ret .= '<ul class="search_quickhits">'; 3170b6fad27Ssandos187 3180b6fad27Ssandos187 if (count($pids) === 0) { 3190b6fad27Ssandos187 // Produce valid XHTML (ul needs a child) 3200b6fad27Ssandos187 $ret .= '<li><div class="li">' . $this->lang['js']['nopages'] . '</div></li>'; 3210b6fad27Ssandos187 } else { 322bdf1ecf0SAnna Dabrowska foreach (array_keys($pids) as $val) { 3230b6fad27Ssandos187 $ret .= '<li><div class="li">'; 324db3ab356SAnna Dabrowska $ret .= html_wikilink(":$val"); 3250b6fad27Ssandos187 $ret .= '</div></li>'; 3260b6fad27Ssandos187 } 3270b6fad27Ssandos187 } 3280b6fad27Ssandos187 3290b6fad27Ssandos187 $ret .= '</ul>'; 3300b6fad27Ssandos187 $ret .= '</div>'; 3310b6fad27Ssandos187 $ret .= '<div class="clearer"></div>'; 3320b6fad27Ssandos187 3330b6fad27Ssandos187 return $ret; 3340b6fad27Ssandos187 } 3350b6fad27Ssandos187 3360b6fad27Ssandos187 /** 3375540f91dSAndreas Gohr * Get the link to a search for the given tag 3385540f91dSAndreas Gohr * 3395540f91dSAndreas Gohr * @param string $tag search for this tag 3405540f91dSAndreas Gohr * @param string $ns limit search to this namespace 3410cfde7e9SMichael Große * 3425540f91dSAndreas Gohr * @return string 3435540f91dSAndreas Gohr */ 344df43a7beSAndreas Gohr protected function linkToSearch($tag, $ns = '') 345df43a7beSAndreas Gohr { 3465540f91dSAndreas Gohr return '<a href="' . hsc($this->getTagSearchURL($tag, $ns)) . '">' . $tag . '</a>'; 347f61105deSAdrian Lang } 348f61105deSAdrian Lang 349fb1d0583SAndreas Gohr /** 350fb1d0583SAndreas Gohr * Display the Tags for the current page and prepare the tag editing form 3513496cc8aSAndreas Gohr * 3523496cc8aSAndreas Gohr * @param bool $print Should the HTML be printed or returned? 3530cfde7e9SMichael Große * 3543496cc8aSAndreas Gohr * @return string 355fb1d0583SAndreas Gohr */ 356df43a7beSAndreas Gohr public function tpl_tags($print = true) 357df43a7beSAndreas Gohr { 358f61105deSAdrian Lang global $INFO; 359f61105deSAdrian Lang global $lang; 3603bf0e2f1SMichael Große 361df43a7beSAndreas Gohr $filter = ['pid' => $INFO['id']]; 3623bf0e2f1SMichael Große if ($this->getConf('singleusermode')) { 3633bf0e2f1SMichael Große $filter['tagger'] = 'auto'; 3643bf0e2f1SMichael Große } 3653bf0e2f1SMichael Große 3663bf0e2f1SMichael Große $tags = $this->findItems($filter, 'tag'); 3673496cc8aSAndreas Gohr 368df43a7beSAndreas Gohr $ret = '<div class="plugin_tagging_edit">'; 369df43a7beSAndreas Gohr $ret .= $this->html_cloud($tags, 'tag', [$this, 'linkToSearch'], true, true); 370f61105deSAdrian Lang 3712ace74f4SAndreas Gohr if ($this->getUser() && $INFO['writable']) { 372f61105deSAdrian Lang $lang['btn_tagging_edit'] = $lang['btn_secedit']; 373e5b42768SSzymon Olewniczak $ret .= '<div id="tagging__edit_buttons_group">'; 374df43a7beSAndreas Gohr $ret .= html_btn('tagging_edit', $INFO['id'], '', []); 375dd52fd45SSzymon Olewniczak if (auth_isadmin()) { 37626f61833SAnna Dabrowska $ret .= '<label>' 37726f61833SAnna Dabrowska . $this->getLang('toggle admin mode') 37826f61833SAnna Dabrowska . '<input type="checkbox" id="tagging__edit_toggle_admin" /></label>'; 379dd52fd45SSzymon Olewniczak } 380e5b42768SSzymon Olewniczak $ret .= '</div>'; 381df43a7beSAndreas Gohr $form = new Form(); 3822819ffcdSSzymon Olewniczak $form->id('tagging__edit'); 3832819ffcdSSzymon Olewniczak $form->setHiddenField('tagging[id]', $INFO['id']); 3842819ffcdSSzymon Olewniczak $form->setHiddenField('call', 'plugin_tagging_save'); 385df43a7beSAndreas Gohr $tags = $this->findItems(['pid' => $INFO['id'], 'tagger' => $this->getUser()], 'tag'); 38626f61833SAnna Dabrowska $form->addTextarea('tagging[tags]') 38726f61833SAnna Dabrowska ->val(implode(', ', array_keys($tags))) 38826f61833SAnna Dabrowska ->addClass('edit') 38926f61833SAnna Dabrowska ->attr('rows', 4); 390cf52ec2dSSzymon Olewniczak $form->addButton('', $lang['btn_save'])->id('tagging__edit_save'); 391cf52ec2dSSzymon Olewniczak $form->addButton('', $lang['btn_cancel'])->id('tagging__edit_cancel'); 3922819ffcdSSzymon Olewniczak $ret .= $form->toHTML(); 393f61105deSAdrian Lang } 3943496cc8aSAndreas Gohr $ret .= '</div>'; 3953496cc8aSAndreas Gohr 3960cfde7e9SMichael Große if ($print) { 3970cfde7e9SMichael Große echo $ret; 3980cfde7e9SMichael Große } 399ca455b8eSMichael Große 4003496cc8aSAndreas Gohr return $ret; 401f61105deSAdrian Lang } 402872edc7cSRené Corinth 4038a1a3846SAndreas Gohr /** 404a99b66c1SSzymon Olewniczak * @param string $namespace empty for entire wiki 405a99b66c1SSzymon Olewniczak * 40640b94b1aSAnna Dabrowska * @param string $order_by 40740b94b1aSAnna Dabrowska * @param bool $desc 40840b94b1aSAnna Dabrowska * @param array $filters 4098a1a3846SAndreas Gohr * @return array 4108a1a3846SAndreas Gohr */ 411df43a7beSAndreas Gohr public function getAllTags($namespace = '', $order_by = 'tid', $desc = false, $filters = []) 412df43a7beSAndreas Gohr { 413df43a7beSAndreas Gohr $order_fields = ['pid', 'tid', 'taggers', 'ns', 'count']; 414f0084ee1SSzymon Olewniczak if (!in_array($order_by, $order_fields)) { 415f0084ee1SSzymon Olewniczak msg('cannot sort by ' . $order_by . ' field does not exists', -1); 416f0084ee1SSzymon Olewniczak $order_by = 'tag'; 417f0084ee1SSzymon Olewniczak } 418872edc7cSRené Corinth 419df43a7beSAndreas Gohr [$having, $params] = $this->getFilterSql($filters); 42040b94b1aSAnna Dabrowska 421a2246f69SAnna Dabrowska $db = $this->getDB(); 422872edc7cSRené Corinth 423f0084ee1SSzymon Olewniczak $query = 'SELECT "pid", 424ca455b8eSMichael Große CLEANTAG("tag") AS "tid", 425f0084ee1SSzymon Olewniczak GROUP_SORT(GROUP_CONCAT("tagger"), \', \') AS "taggers", 42640b94b1aSAnna Dabrowska GROUP_SORT(GROUP_CONCAT(GET_NS("pid")), \', \') AS "ns", 42789ed97adSAnna Dabrowska GROUP_SORT(GROUP_CONCAT("pid"), \', \') AS "pids", 428193a767dSSzymon Olewniczak COUNT(*) AS "count" 42957e45304SSzymon Olewniczak FROM "taggings" 4304227fca4SAnna Dabrowska WHERE "pid" GLOB ? AND GETACCESSLEVEL(pid) >= ' . AUTH_READ 4314227fca4SAnna Dabrowska . ' GROUP BY "tid"'; 43240b94b1aSAnna Dabrowska $query .= $having; 43340b94b1aSAnna Dabrowska $query .= 'ORDER BY ' . $order_by; 434ca455b8eSMichael Große if ($desc) { 435ca455b8eSMichael Große $query .= ' DESC'; 436ca455b8eSMichael Große } 437cb469644SSzymon Olewniczak 43840b94b1aSAnna Dabrowska array_unshift($params, $this->globNamespace($namespace)); 439*a6b932a0SAnna Dabrowska return $db->queryAll($query, $params); 440872edc7cSRené Corinth } 441872edc7cSRené Corinth 4428a1a3846SAndreas Gohr /** 44372431326SMichael Große * Get all pages with tags and their tags 44472431326SMichael Große * 445790ca788SAndreas Gohr * @return array ['pid' => ['tag1','tag2','tag3']] 44672431326SMichael Große */ 447df43a7beSAndreas Gohr public function getAllTagsByPage() 448df43a7beSAndreas Gohr { 44972431326SMichael Große $query = ' 45072431326SMichael Große SELECT pid, GROUP_CONCAT(tag) AS tags 45172431326SMichael Große FROM taggings 45272431326SMichael Große GROUP BY pid 45372431326SMichael Große '; 454*a6b932a0SAnna Dabrowska $db = $this->getDB(); 455790ca788SAndreas Gohr return array_map( 456df43a7beSAndreas Gohr fn($i) => explode(',', $i), 457*a6b932a0SAnna Dabrowska array_column($db->queryAll($query), 'tags', 'pid') 458790ca788SAndreas Gohr ); 45972431326SMichael Große } 46072431326SMichael Große 46172431326SMichael Große /** 4628a1a3846SAndreas Gohr * Renames a tag 4638a1a3846SAndreas Gohr * 4648a1a3846SAndreas Gohr * @param string $formerTagName 4654227fca4SAnna Dabrowska * @param string $newTagNames 4668a1a3846SAndreas Gohr */ 467df43a7beSAndreas Gohr public function renameTag($formerTagName, $newTagNames) 468df43a7beSAndreas Gohr { 469872edc7cSRené Corinth 4704227fca4SAnna Dabrowska if (empty($formerTagName) || empty($newTagNames)) { 4718a1a3846SAndreas Gohr msg($this->getLang("admin enter tag names"), -1); 4728a1a3846SAndreas Gohr return; 473872edc7cSRené Corinth } 474872edc7cSRené Corinth 475870d77ddSAnna Dabrowska $keepFormerTag = false; 476870d77ddSAnna Dabrowska 4774227fca4SAnna Dabrowska // enable splitting tags on rename 478df43a7beSAndreas Gohr $newTagNames = array_map(fn($tag) => $this->cleanTag($tag), explode(',', $newTagNames)); 4794227fca4SAnna Dabrowska 4804227fca4SAnna Dabrowska $db = $this->getDB(); 481872edc7cSRené Corinth 4824227fca4SAnna Dabrowska // non-admins can rename only their own tags 4834227fca4SAnna Dabrowska if (!auth_isadmin()) { 4844227fca4SAnna Dabrowska $queryTagger = ' AND tagger = ?'; 4854227fca4SAnna Dabrowska $tagger = $this->getUser(); 4864227fca4SAnna Dabrowska } else { 4874227fca4SAnna Dabrowska $queryTagger = ''; 4884227fca4SAnna Dabrowska $tagger = ''; 4894227fca4SAnna Dabrowska } 4904227fca4SAnna Dabrowska 4910ec63874SAnna Dabrowska $insertQuery = 'INSERT INTO taggings '; 4920ec63874SAnna Dabrowska $insertQuery .= 'SELECT pid, ?, tagger, lang FROM taggings'; 4930ec63874SAnna Dabrowska $where = ' WHERE CLEANTAG(tag) = ?'; 4940ec63874SAnna Dabrowska $where .= ' AND GETACCESSLEVEL(pid) >= ' . AUTH_EDIT; 4950ec63874SAnna Dabrowska $where .= $queryTagger; 4960ec63874SAnna Dabrowska 4970ec63874SAnna Dabrowska $db->query('BEGIN TRANSACTION'); 4980ec63874SAnna Dabrowska 4990ec63874SAnna Dabrowska // insert new tags first 5000ec63874SAnna Dabrowska foreach ($newTagNames as $newTag) { 501870d77ddSAnna Dabrowska if ($newTag === $this->cleanTag($formerTagName)) { 502870d77ddSAnna Dabrowska $keepFormerTag = true; 503870d77ddSAnna Dabrowska continue; 504870d77ddSAnna Dabrowska } 505870d77ddSAnna Dabrowska $params = [$newTag, $this->cleanTag($formerTagName)]; 506df43a7beSAndreas Gohr if ($tagger) $params[] = $tagger; 507*a6b932a0SAnna Dabrowska 508*a6b932a0SAnna Dabrowska try { 509*a6b932a0SAnna Dabrowska $db->exec($insertQuery . $where, $params); 510*a6b932a0SAnna Dabrowska } catch (\PDOException $e) { 511*a6b932a0SAnna Dabrowska \dokuwiki\Logger::error("Tagging: renaming tag $formerTagName failed - " . $e->getMessage()); 5120ec63874SAnna Dabrowska $db->query('ROLLBACK TRANSACTION'); 5130ec63874SAnna Dabrowska return; 5144227fca4SAnna Dabrowska } 5150ec63874SAnna Dabrowska } 5160ec63874SAnna Dabrowska 517870d77ddSAnna Dabrowska // finally delete the renamed tags 518870d77ddSAnna Dabrowska if (!$keepFormerTag) { 5190ec63874SAnna Dabrowska $deleteQuery = 'DELETE FROM taggings'; 5200ec63874SAnna Dabrowska $params = [$this->cleanTag($formerTagName)]; 521df43a7beSAndreas Gohr if ($tagger) $params[] = $tagger; 522*a6b932a0SAnna Dabrowska try { 523*a6b932a0SAnna Dabrowska $db->exec($deleteQuery . $where, $params); 524*a6b932a0SAnna Dabrowska } catch (\PDOException $e) { 525*a6b932a0SAnna Dabrowska \dokuwiki\Logger::error("Tagging: renaming tag $formerTagName failed - " . $e->getMessage()); 5260ec63874SAnna Dabrowska $db->query('ROLLBACK TRANSACTION'); 5270ec63874SAnna Dabrowska return; 5280ec63874SAnna Dabrowska } 529870d77ddSAnna Dabrowska } 5300ec63874SAnna Dabrowska 5310ec63874SAnna Dabrowska $db->query('COMMIT TRANSACTION'); 532872edc7cSRené Corinth 533fb1d0583SAndreas Gohr msg($this->getLang("admin renamed"), 1); 534872edc7cSRené Corinth } 535872edc7cSRené Corinth 5368f630140SSzymon Olewniczak /** 537dd52fd45SSzymon Olewniczak * Rename or delete a tag for all users 538dd52fd45SSzymon Olewniczak * 539dd52fd45SSzymon Olewniczak * @param string $pid 540dd52fd45SSzymon Olewniczak * @param string $formerTagName 541dd52fd45SSzymon Olewniczak * @param string $newTagName 542dd52fd45SSzymon Olewniczak * 543dd52fd45SSzymon Olewniczak * @return array 544dd52fd45SSzymon Olewniczak */ 545df43a7beSAndreas Gohr public function modifyPageTag($pid, $formerTagName, $newTagName) 546df43a7beSAndreas Gohr { 547*a6b932a0SAnna Dabrowska $db = $this->getDB(); 548dd52fd45SSzymon Olewniczak 549*a6b932a0SAnna Dabrowska $check = $db->queryAll( 55026f61833SAnna Dabrowska 'SELECT pid FROM taggings WHERE CLEANTAG(tag) = ? AND pid = ?', 55126f61833SAnna Dabrowska $this->cleanTag($formerTagName), 55226f61833SAnna Dabrowska $pid 55326f61833SAnna Dabrowska ); 554dd52fd45SSzymon Olewniczak 555dd52fd45SSzymon Olewniczak if (empty($check)) { 556df43a7beSAndreas Gohr return [true, $this->getLang('admin tag does not exists')]; 557dd52fd45SSzymon Olewniczak } 558dd52fd45SSzymon Olewniczak 559dd52fd45SSzymon Olewniczak if (empty($newTagName)) { 560*a6b932a0SAnna Dabrowska $db->query( 56126f61833SAnna Dabrowska 'DELETE FROM taggings WHERE pid = ? AND CLEANTAG(tag) = ?', 56226f61833SAnna Dabrowska $pid, 56326f61833SAnna Dabrowska $this->cleanTag($formerTagName) 56426f61833SAnna Dabrowska ); 565dd52fd45SSzymon Olewniczak } else { 566*a6b932a0SAnna Dabrowska $db->query( 56726f61833SAnna Dabrowska 'UPDATE taggings SET tag = ? WHERE pid = ? AND CLEANTAG(tag) = ?', 56826f61833SAnna Dabrowska $newTagName, 56926f61833SAnna Dabrowska $pid, 57026f61833SAnna Dabrowska $this->cleanTag($formerTagName) 57126f61833SAnna Dabrowska ); 572dd52fd45SSzymon Olewniczak } 573dd52fd45SSzymon Olewniczak 574df43a7beSAndreas Gohr return [false, $this->getLang('admin renamed')]; 575dd52fd45SSzymon Olewniczak } 576dd52fd45SSzymon Olewniczak 577dd52fd45SSzymon Olewniczak /** 5788f630140SSzymon Olewniczak * Deletes a tag 5798f630140SSzymon Olewniczak * 5808f630140SSzymon Olewniczak * @param array $tags 58131396860SSzymon Olewniczak * @param string $namespace current namespace context as in getAllTags() 5828f630140SSzymon Olewniczak */ 583df43a7beSAndreas Gohr public function deleteTags($tags, $namespace = '') 584df43a7beSAndreas Gohr { 585ca455b8eSMichael Große if (empty($tags)) { 586ca455b8eSMichael Große return; 587ca455b8eSMichael Große } 5888f630140SSzymon Olewniczak 589*a6b932a0SAnna Dabrowska $namespace = cleanID($namespace); 59031396860SSzymon Olewniczak 5911f5991a7SMichael Große $db = $this->getDB(); 5928f630140SSzymon Olewniczak 593de379874SAnna Dabrowska $queryBody = 'FROM taggings WHERE pid GLOB ? AND (' . 59431396860SSzymon Olewniczak implode(' OR ', array_fill(0, count($tags), 'CLEANTAG(tag) = ?')) . ')'; 595df43a7beSAndreas Gohr $args = array_map([$this, 'cleanTag'], $tags); 59631396860SSzymon Olewniczak array_unshift($args, $this->globNamespace($namespace)); 5978f630140SSzymon Olewniczak 5984227fca4SAnna Dabrowska // non-admins can delete only their own tags 5994227fca4SAnna Dabrowska if (!auth_isadmin()) { 6004227fca4SAnna Dabrowska $queryBody .= ' AND tagger = ?'; 601df43a7beSAndreas Gohr $args[] = $this->getUser(); 6024227fca4SAnna Dabrowska } 603ca455b8eSMichael Große 6041f5991a7SMichael Große $affectedPagesQuery = 'SELECT DISTINCT pid ' . $queryBody; 605*a6b932a0SAnna Dabrowska $numAffectedPages = $db->exec($affectedPagesQuery, $args); 6061f5991a7SMichael Große 6071f5991a7SMichael Große $deleteQuery = 'DELETE ' . $queryBody; 6081f5991a7SMichael Große $db->query($deleteQuery, $args); 6091f5991a7SMichael Große 6101f5991a7SMichael Große msg(sprintf($this->getLang("admin deleted"), count($tags), $numAffectedPages), 1); 6118f630140SSzymon Olewniczak } 612cd08599fSAnna Dabrowska 613cd08599fSAnna Dabrowska /** 614ec4796e4SAnna Dabrowska * Delete taggings of nonexistent pages 615ec4796e4SAnna Dabrowska */ 616ec4796e4SAnna Dabrowska public function deleteInvalidTaggings() 617ec4796e4SAnna Dabrowska { 618ec4796e4SAnna Dabrowska $db = $this->getDB(); 619ec4796e4SAnna Dabrowska $query = 'DELETE FROM "taggings" 6201815f49fSAnna Dabrowska WHERE NOT PAGEEXISTS(pid) 621ec4796e4SAnna Dabrowska '; 622*a6b932a0SAnna Dabrowska $db->query($query); 623ec4796e4SAnna Dabrowska } 624ec4796e4SAnna Dabrowska 625ec4796e4SAnna Dabrowska /** 626cd08599fSAnna Dabrowska * Updates tags with a new page name 627cd08599fSAnna Dabrowska * 628cd08599fSAnna Dabrowska * @param string $oldName 629cd08599fSAnna Dabrowska * @param string $newName 630cd08599fSAnna Dabrowska */ 631df43a7beSAndreas Gohr public function renamePage($oldName, $newName) 632df43a7beSAndreas Gohr { 633f6568bcbSAnna Dabrowska $db = $this->getDB(); 634cd08599fSAnna Dabrowska $db->query('UPDATE taggings SET pid = ? WHERE pid = ?', $newName, $oldName); 635cd08599fSAnna Dabrowska } 636972f6adfSAnna Dabrowska 637972f6adfSAnna Dabrowska /** 6381b4b4fa9SAnna Dabrowska * Extracts tags from search query 6391b4b4fa9SAnna Dabrowska * 6401b4b4fa9SAnna Dabrowska * @param array $parsedQuery 6411b4b4fa9SAnna Dabrowska * @return array 6421b4b4fa9SAnna Dabrowska */ 643739c5360SAnna Dabrowska public function extractFromQuery($parsedQuery) 6441b4b4fa9SAnna Dabrowska { 6451b4b4fa9SAnna Dabrowska $tags = []; 6461b4b4fa9SAnna Dabrowska if (isset($parsedQuery['phrases'][0])) { 6471b4b4fa9SAnna Dabrowska $tags = $parsedQuery['phrases']; 6481b4b4fa9SAnna Dabrowska } elseif (isset($parsedQuery['and'][0])) { 6491b4b4fa9SAnna Dabrowska $tags = $parsedQuery['and']; 6501b4b4fa9SAnna Dabrowska } elseif (isset($parsedQuery['tag'])) { 6511b4b4fa9SAnna Dabrowska // handle autocomplete call 6521b4b4fa9SAnna Dabrowska $tags[] = $parsedQuery['tag']; 6531b4b4fa9SAnna Dabrowska } 6541b4b4fa9SAnna Dabrowska return $tags; 6551b4b4fa9SAnna Dabrowska } 6561b4b4fa9SAnna Dabrowska 6571b4b4fa9SAnna Dabrowska /** 6584a7da0a5SAnna Dabrowska * Search for tagged pages 659972f6adfSAnna Dabrowska * 660739c5360SAnna Dabrowska * @param array $tagFiler 6614a7da0a5SAnna Dabrowska * @return array 662972f6adfSAnna Dabrowska */ 663739c5360SAnna Dabrowska public function searchPages($tagFiler) 664972f6adfSAnna Dabrowska { 6651b4b4fa9SAnna Dabrowska global $INPUT; 6661b4b4fa9SAnna Dabrowska global $QUERY; 667df43a7beSAndreas Gohr $parsedQuery = ft_queryParser(new Indexer(), $QUERY); 668972f6adfSAnna Dabrowska 669df43a7beSAndreas Gohr $queryBuilder = new helper_plugin_tagging_querybuilder(); 670972f6adfSAnna Dabrowska 6714a7da0a5SAnna Dabrowska $queryBuilder->setField('pid'); 672cbe7b4baSAnna Dabrowska $queryBuilder->setTags($tagFiler); 673f014dfc9SAnna Dabrowska $queryBuilder->setLogicalAnd($INPUT->str('tagging-logic') === 'and'); 6744a7da0a5SAnna Dabrowska if (isset($parsedQuery['ns'])) $queryBuilder->includeNS($parsedQuery['ns']); 6754a7da0a5SAnna Dabrowska if (isset($parsedQuery['notns'])) $queryBuilder->excludeNS($parsedQuery['notns']); 6764a7da0a5SAnna Dabrowska if (isset($parsedQuery['tagger'])) $queryBuilder->setTagger($parsedQuery['tagger']); 6774a7da0a5SAnna Dabrowska if (isset($parsedQuery['pid'])) $queryBuilder->setPid($parsedQuery['pid']); 678972f6adfSAnna Dabrowska 6794a7da0a5SAnna Dabrowska return $this->queryDb($queryBuilder->getPages()); 680972f6adfSAnna Dabrowska } 681972f6adfSAnna Dabrowska 6821b4b4fa9SAnna Dabrowska /** 6834227fca4SAnna Dabrowska * Syntax to allow users to manage tags on regular pages, respects ACLs 6844227fca4SAnna Dabrowska * @param string $ns 6854227fca4SAnna Dabrowska * @return string 6864227fca4SAnna Dabrowska */ 6874227fca4SAnna Dabrowska public function manageTags($ns) 6884227fca4SAnna Dabrowska { 6894227fca4SAnna Dabrowska global $INPUT; 6904227fca4SAnna Dabrowska 691f6568bcbSAnna Dabrowska $this->setDefaultSort(); 6924227fca4SAnna Dabrowska 6934227fca4SAnna Dabrowska // initially set namespace filter to what is defined in syntax 6944227fca4SAnna Dabrowska if ($ns && !$INPUT->has('tagging__filters')) { 6954227fca4SAnna Dabrowska $INPUT->set('tagging__filters', ['ns' => $ns]); 6964227fca4SAnna Dabrowska } 6974227fca4SAnna Dabrowska 6984227fca4SAnna Dabrowska return $this->html_table(); 6994227fca4SAnna Dabrowska } 7004227fca4SAnna Dabrowska 7014227fca4SAnna Dabrowska /** 702a2246f69SAnna Dabrowska * HTML list of tagged pages 703a2246f69SAnna Dabrowska * 704a2246f69SAnna Dabrowska * @param string $tid 705a2246f69SAnna Dabrowska * @return string 706a2246f69SAnna Dabrowska */ 707a2246f69SAnna Dabrowska public function getPagesHtml($tid) 708a2246f69SAnna Dabrowska { 709a2246f69SAnna Dabrowska $html = ''; 710a2246f69SAnna Dabrowska 711a2246f69SAnna Dabrowska $db = $this->getDB(); 71231bddc5fSAnna Dabrowska $sql = 'SELECT pid from taggings where CLEANTAG(tag) = CLEANTAG(?)'; 713*a6b932a0SAnna Dabrowska $pages = $db->queryAll($sql, $tid); 714a2246f69SAnna Dabrowska 715a2246f69SAnna Dabrowska if ($pages) { 716a2246f69SAnna Dabrowska $html .= '<ul>'; 717a2246f69SAnna Dabrowska foreach ($pages as $page) { 718a2246f69SAnna Dabrowska $pid = $page['pid']; 719a2246f69SAnna Dabrowska $html .= '<li><a href="' . wl($pid) . '" target="_blank">' . $pid . '</li>'; 720a2246f69SAnna Dabrowska } 721a2246f69SAnna Dabrowska $html .= '</ul>'; 722a2246f69SAnna Dabrowska } 723a2246f69SAnna Dabrowska 724a2246f69SAnna Dabrowska return $html; 725a2246f69SAnna Dabrowska } 726a2246f69SAnna Dabrowska 727a2246f69SAnna Dabrowska /** 72889ed97adSAnna Dabrowska * Display tag management table 72989ed97adSAnna Dabrowska */ 730df43a7beSAndreas Gohr public function html_table() 731df43a7beSAndreas Gohr { 73289ed97adSAnna Dabrowska global $ID, $INPUT; 73389ed97adSAnna Dabrowska 734df43a7beSAndreas Gohr $headers = [ 735df43a7beSAndreas Gohr ['value' => $this->getLang('admin tag'), 'sort_by' => 'tid'], 736df43a7beSAndreas Gohr ['value' => $this->getLang('admin occurrence'), 'sort_by' => 'count'] 737df43a7beSAndreas Gohr ]; 73826c4179dSAnna Dabrowska 73926c4179dSAnna Dabrowska if (!$this->conf['hidens']) { 740df43a7beSAndreas Gohr $headers[] = ['value' => $this->getLang('admin namespaces'), 'sort_by' => 'ns']; 74126c4179dSAnna Dabrowska } 742df43a7beSAndreas Gohr $headers[] = ['value' => $this->getLang('admin taggers'), 'sort_by' => 'taggers']; 743df43a7beSAndreas Gohr $headers[] = ['value' => $this->getLang('admin actions'), 'sort_by' => false]; 74489ed97adSAnna Dabrowska 745f6568bcbSAnna Dabrowska $sort = explode(',', $this->getParam('sort')); 74689ed97adSAnna Dabrowska $order_by = $sort[0]; 74789ed97adSAnna Dabrowska $desc = false; 74889ed97adSAnna Dabrowska if (isset($sort[1]) && $sort[1] === 'desc') { 74989ed97adSAnna Dabrowska $desc = true; 75089ed97adSAnna Dabrowska } 75189ed97adSAnna Dabrowska $filters = $INPUT->arr('tagging__filters'); 75289ed97adSAnna Dabrowska 75389ed97adSAnna Dabrowska $tags = $this->getAllTags($INPUT->str('filter'), $order_by, $desc, $filters); 75489ed97adSAnna Dabrowska 755df43a7beSAndreas Gohr $form = new Form(); 756a2246f69SAnna Dabrowska // required in admin mode 75789ed97adSAnna Dabrowska $form->setHiddenField('page', 'tagging'); 75889ed97adSAnna Dabrowska $form->setHiddenField('id', $ID); 759f6568bcbSAnna Dabrowska $form->setHiddenField('[tagging]sort', $this->getParam('sort')); 76089ed97adSAnna Dabrowska 76189ed97adSAnna Dabrowska /** 76289ed97adSAnna Dabrowska * Actions dialog 76389ed97adSAnna Dabrowska */ 76489ed97adSAnna Dabrowska $form->addTagOpen('div')->id('tagging__action-dialog')->attr('style', "display:none;"); 76589ed97adSAnna Dabrowska $form->addTagClose('div'); 76689ed97adSAnna Dabrowska 76789ed97adSAnna Dabrowska /** 76889ed97adSAnna Dabrowska * Tag pages dialog 76989ed97adSAnna Dabrowska */ 77089ed97adSAnna Dabrowska $form->addTagOpen('div')->id('tagging__taggedpages-dialog')->attr('style', "display:none;"); 77189ed97adSAnna Dabrowska $form->addTagClose('div'); 77289ed97adSAnna Dabrowska 77389ed97adSAnna Dabrowska /** 77489ed97adSAnna Dabrowska * Tag management table 77589ed97adSAnna Dabrowska */ 77689ed97adSAnna Dabrowska $form->addTagOpen('table')->addClass('inline plugin_tagging'); 77789ed97adSAnna Dabrowska 778df43a7beSAndreas Gohr $nscol = $this->conf['hidens'] ? '' : '<col class="wide-col" />'; 779a305ec34SAnna Dabrowska $form->addHTML( 780a305ec34SAnna Dabrowska '<colgroup> 781df43a7beSAndreas Gohr <col /> 782df43a7beSAndreas Gohr <col class="narrow-col" />' 78326c4179dSAnna Dabrowska . $nscol . 784df43a7beSAndreas Gohr '<col /> 785df43a7beSAndreas Gohr <col class="narrow-col" /> 786a305ec34SAnna Dabrowska </colgroup>' 787a305ec34SAnna Dabrowska ); 788a305ec34SAnna Dabrowska 78989ed97adSAnna Dabrowska /** 79089ed97adSAnna Dabrowska * Table headers 79189ed97adSAnna Dabrowska */ 79289ed97adSAnna Dabrowska $form->addTagOpen('tr'); 79389ed97adSAnna Dabrowska foreach ($headers as $header) { 79489ed97adSAnna Dabrowska $form->addTagOpen('th'); 79589ed97adSAnna Dabrowska if ($header['sort_by'] !== false) { 79689ed97adSAnna Dabrowska $param = $header['sort_by']; 79789ed97adSAnna Dabrowska $icon = 'arrow-both'; 79889ed97adSAnna Dabrowska $title = $this->getLang('admin sort ascending'); 79989ed97adSAnna Dabrowska if ($header['sort_by'] === $order_by) { 80089ed97adSAnna Dabrowska if ($desc === false) { 80189ed97adSAnna Dabrowska $icon = 'arrow-up'; 80289ed97adSAnna Dabrowska $title = $this->getLang('admin sort descending'); 80389ed97adSAnna Dabrowska $param .= ',desc'; 80489ed97adSAnna Dabrowska } else { 80589ed97adSAnna Dabrowska $icon = 'arrow-down'; 80689ed97adSAnna Dabrowska } 80789ed97adSAnna Dabrowska } 80826f61833SAnna Dabrowska $form->addButtonHTML( 809f6568bcbSAnna Dabrowska "tagging[sort]", 810df43a7beSAndreas Gohr $header['value'] . ' ' . inlineSVG(__DIR__ . "/images/$icon.svg") 811df43a7beSAndreas Gohr ) 81289ed97adSAnna Dabrowska ->addClass('plugin_tagging sort_button') 813f6568bcbSAnna Dabrowska ->attr('title', $title) 814f6568bcbSAnna Dabrowska ->val($param); 81589ed97adSAnna Dabrowska } else { 81689ed97adSAnna Dabrowska $form->addHTML($header['value']); 81789ed97adSAnna Dabrowska } 81889ed97adSAnna Dabrowska $form->addTagClose('th'); 81989ed97adSAnna Dabrowska } 82089ed97adSAnna Dabrowska $form->addTagClose('tr'); 82189ed97adSAnna Dabrowska 82289ed97adSAnna Dabrowska /** 82389ed97adSAnna Dabrowska * Table filters for all sortable columns 82489ed97adSAnna Dabrowska */ 82589ed97adSAnna Dabrowska $form->addTagOpen('tr'); 82689ed97adSAnna Dabrowska foreach ($headers as $header) { 82789ed97adSAnna Dabrowska $form->addTagOpen('th'); 82889ed97adSAnna Dabrowska if ($header['sort_by'] !== false) { 82989ed97adSAnna Dabrowska $field = $header['sort_by']; 8307c96ae87SAnna Dabrowska $input = $form->addTextInput("tagging__filters[$field]"); 831a305ec34SAnna Dabrowska $input->addClass('full-col'); 83289ed97adSAnna Dabrowska } 83389ed97adSAnna Dabrowska $form->addTagClose('th'); 83489ed97adSAnna Dabrowska } 83589ed97adSAnna Dabrowska $form->addTagClose('tr'); 83689ed97adSAnna Dabrowska 83789ed97adSAnna Dabrowska 83889ed97adSAnna Dabrowska foreach ($tags as $taginfo) { 83989ed97adSAnna Dabrowska $tagname = $taginfo['tid']; 84089ed97adSAnna Dabrowska $taggers = $taginfo['taggers']; 84189ed97adSAnna Dabrowska $ns = $taginfo['ns']; 84289ed97adSAnna Dabrowska $pids = explode(',', $taginfo['pids']); 84389ed97adSAnna Dabrowska 84489ed97adSAnna Dabrowska $form->addTagOpen('tr'); 84526f61833SAnna Dabrowska $form->addHTML('<td>'); 846a2246f69SAnna Dabrowska $form->addHTML('<a class="tagslist" href="#" data-tid="' . $taginfo['tid'] . '">'); 84726f61833SAnna Dabrowska $form->addHTML(hsc($tagname) . '</a>'); 84826f61833SAnna Dabrowska $form->addHTML('</td>'); 84989ed97adSAnna Dabrowska $form->addHTML('<td>' . $taginfo['count'] . '</td>'); 85026c4179dSAnna Dabrowska if (!$this->conf['hidens']) { 85189ed97adSAnna Dabrowska $form->addHTML('<td>' . hsc($ns) . '</td>'); 85226c4179dSAnna Dabrowska } 85389ed97adSAnna Dabrowska $form->addHTML('<td>' . hsc($taggers) . '</td>'); 85489ed97adSAnna Dabrowska 85589ed97adSAnna Dabrowska /** 85689ed97adSAnna Dabrowska * action buttons 85789ed97adSAnna Dabrowska */ 85889ed97adSAnna Dabrowska $form->addHTML('<td>'); 85989ed97adSAnna Dabrowska 86089ed97adSAnna Dabrowska // check ACLs 86189ed97adSAnna Dabrowska $userEdit = false; 86289ed97adSAnna Dabrowska foreach ($pids as $pid) { 863749c70e5SAndreas Gohr if (auth_quickaclcheck($pid) >= AUTH_EDIT) { 86489ed97adSAnna Dabrowska $userEdit = true; 865df43a7beSAndreas Gohr break; 86689ed97adSAnna Dabrowska } 86789ed97adSAnna Dabrowska } 86889ed97adSAnna Dabrowska 86989ed97adSAnna Dabrowska if ($userEdit) { 87026f61833SAnna Dabrowska $form->addButtonHTML( 871f6568bcbSAnna Dabrowska 'tagging[actions][rename][' . $taginfo['tid'] . ']', 872df43a7beSAndreas Gohr inlineSVG(__DIR__ . '/images/edit.svg') 873df43a7beSAndreas Gohr ) 87426f61833SAnna Dabrowska ->addClass('plugin_tagging action_button') 87526f61833SAnna Dabrowska ->attr('data-action', 'rename') 87626f61833SAnna Dabrowska ->attr('data-tid', $taginfo['tid']); 87726f61833SAnna Dabrowska $form->addButtonHTML( 878f6568bcbSAnna Dabrowska 'tagging[actions][delete][' . $taginfo['tid'] . ']', 879df43a7beSAndreas Gohr inlineSVG(__DIR__ . '/images/delete.svg') 880df43a7beSAndreas Gohr ) 88126f61833SAnna Dabrowska ->addClass('plugin_tagging action_button') 88226f61833SAnna Dabrowska ->attr('data-action', 'delete') 88326f61833SAnna Dabrowska ->attr('data-tid', $taginfo['tid']); 88489ed97adSAnna Dabrowska } 88589ed97adSAnna Dabrowska 88689ed97adSAnna Dabrowska $form->addHTML('</td>'); 88789ed97adSAnna Dabrowska $form->addTagClose('tr'); 88889ed97adSAnna Dabrowska } 88989ed97adSAnna Dabrowska 89089ed97adSAnna Dabrowska $form->addTagClose('table'); 8910b033188SAnna Dabrowska return '<div class="table">' . $form->toHTML() . '</div>'; 89289ed97adSAnna Dabrowska } 89389ed97adSAnna Dabrowska 89489ed97adSAnna Dabrowska /** 895ec4796e4SAnna Dabrowska * Display tag cleaner 896ec4796e4SAnna Dabrowska * 897ec4796e4SAnna Dabrowska * @return string 898ec4796e4SAnna Dabrowska */ 899ec4796e4SAnna Dabrowska public function html_clean() 900ec4796e4SAnna Dabrowska { 901ec4796e4SAnna Dabrowska $invalid = $this->getInvalidTaggings(); 902ec4796e4SAnna Dabrowska 903ec4796e4SAnna Dabrowska if (!$invalid) { 904ec4796e4SAnna Dabrowska return '<p><strong>' . $this->getLang('admin no invalid') . '</strong></p>'; 905ec4796e4SAnna Dabrowska } 906ec4796e4SAnna Dabrowska 907ec4796e4SAnna Dabrowska $form = new Form(); 908ec4796e4SAnna Dabrowska $form->setHiddenField('do', 'admin'); 909ec4796e4SAnna Dabrowska $form->setHiddenField('page', $this->getPluginName()); 910ec4796e4SAnna Dabrowska $form->addButton('cmd[clean]', $this->getLang('admin clean')); 911ec4796e4SAnna Dabrowska 912ec4796e4SAnna Dabrowska $html = $form->toHTML(); 913ec4796e4SAnna Dabrowska 914ec4796e4SAnna Dabrowska $html .= '<div class="table"><table class="inline plugin_tagging">'; 915ec4796e4SAnna Dabrowska $html .= '<thead><tr><th>' . 916ec4796e4SAnna Dabrowska $this->getLang('admin nonexistent page') . 917ec4796e4SAnna Dabrowska '</th><th>' . 918ec4796e4SAnna Dabrowska $this->getLang('admin tags') . 919ec4796e4SAnna Dabrowska '</th></tr></thead><tbody>'; 920ec4796e4SAnna Dabrowska 921ec4796e4SAnna Dabrowska foreach ($invalid as $row) { 922ec4796e4SAnna Dabrowska $html .= '<tr><td>' . $row['pid'] . '</td><td>' . $row['tags'] . '</td></tr>'; 923ec4796e4SAnna Dabrowska } 924ec4796e4SAnna Dabrowska 925ec4796e4SAnna Dabrowska $html .= '</tbody></table></div>'; 926ec4796e4SAnna Dabrowska 927ec4796e4SAnna Dabrowska return $html; 928ec4796e4SAnna Dabrowska } 929ec4796e4SAnna Dabrowska 930ec4796e4SAnna Dabrowska /** 931f6568bcbSAnna Dabrowska * Returns all tagging parameters from the query string 932f6568bcbSAnna Dabrowska * 933f6568bcbSAnna Dabrowska * @return mixed 934f6568bcbSAnna Dabrowska */ 935f6568bcbSAnna Dabrowska public function getParams() 936f6568bcbSAnna Dabrowska { 937f6568bcbSAnna Dabrowska global $INPUT; 938f6568bcbSAnna Dabrowska return $INPUT->param('tagging', []); 939f6568bcbSAnna Dabrowska } 940f6568bcbSAnna Dabrowska 941f6568bcbSAnna Dabrowska /** 942f6568bcbSAnna Dabrowska * Get a tagging parameter, empty string if not set 943f6568bcbSAnna Dabrowska * 944f6568bcbSAnna Dabrowska * @param string $name 945df43a7beSAndreas Gohr * @return string 946f6568bcbSAnna Dabrowska */ 947f6568bcbSAnna Dabrowska public function getParam($name) 948f6568bcbSAnna Dabrowska { 949f6568bcbSAnna Dabrowska $params = $this->getParams(); 950f6568bcbSAnna Dabrowska if ($params) { 951f6568bcbSAnna Dabrowska return $params[$name] ?: ''; 952f6568bcbSAnna Dabrowska } 953df43a7beSAndreas Gohr return ''; 954f6568bcbSAnna Dabrowska } 955f6568bcbSAnna Dabrowska 956f6568bcbSAnna Dabrowska /** 957f6568bcbSAnna Dabrowska * Sets a tagging parameter 958f6568bcbSAnna Dabrowska * 959f6568bcbSAnna Dabrowska * @param string $name 960f6568bcbSAnna Dabrowska * @param string|array $value 961f6568bcbSAnna Dabrowska */ 962f6568bcbSAnna Dabrowska public function setParam($name, $value) 963f6568bcbSAnna Dabrowska { 964f6568bcbSAnna Dabrowska global $INPUT; 965f6568bcbSAnna Dabrowska $params = $this->getParams(); 966f6568bcbSAnna Dabrowska $params = array_merge($params, [$name => $value]); 967df43a7beSAndreas Gohr 968f6568bcbSAnna Dabrowska $INPUT->set('tagging', $params); 969f6568bcbSAnna Dabrowska } 970f6568bcbSAnna Dabrowska 971f6568bcbSAnna Dabrowska /** 972f6568bcbSAnna Dabrowska * Default sorting by tag id 973f6568bcbSAnna Dabrowska */ 974f6568bcbSAnna Dabrowska public function setDefaultSort() 975f6568bcbSAnna Dabrowska { 976f6568bcbSAnna Dabrowska if (!$this->getParam('sort')) { 977f6568bcbSAnna Dabrowska $this->setParam('sort', 'tid'); 978f6568bcbSAnna Dabrowska } 979f6568bcbSAnna Dabrowska } 980f6568bcbSAnna Dabrowska 981f6568bcbSAnna Dabrowska /** 9824a7da0a5SAnna Dabrowska * Executes the query and returns the results as array 9831b4b4fa9SAnna Dabrowska * 98499122157SAnna Dabrowska * @param array $query 9851b4b4fa9SAnna Dabrowska * @return array 9861b4b4fa9SAnna Dabrowska */ 9874a7da0a5SAnna Dabrowska protected function queryDb($query) 9881b4b4fa9SAnna Dabrowska { 9894a7da0a5SAnna Dabrowska $db = $this->getDB(); 9904a7da0a5SAnna Dabrowska if (!$db) { 9914a7da0a5SAnna Dabrowska return []; 992972f6adfSAnna Dabrowska } 9934a7da0a5SAnna Dabrowska 994*a6b932a0SAnna Dabrowska $res = $db->queryAll($query[0], $query[1]); 9954a7da0a5SAnna Dabrowska 9964a7da0a5SAnna Dabrowska $ret = []; 9974a7da0a5SAnna Dabrowska foreach ($res as $row) { 9984a7da0a5SAnna Dabrowska $ret[$row['item']] = $row['cnt']; 9994a7da0a5SAnna Dabrowska } 10004a7da0a5SAnna Dabrowska return $ret; 1001972f6adfSAnna Dabrowska } 100240b94b1aSAnna Dabrowska 100340b94b1aSAnna Dabrowska /** 100440b94b1aSAnna Dabrowska * Construct the HAVING part of the search query 100540b94b1aSAnna Dabrowska * 100640b94b1aSAnna Dabrowska * @param array $filters 100740b94b1aSAnna Dabrowska * @return array 100840b94b1aSAnna Dabrowska */ 100940b94b1aSAnna Dabrowska protected function getFilterSql($filters) 101040b94b1aSAnna Dabrowska { 101140b94b1aSAnna Dabrowska $having = ''; 101240b94b1aSAnna Dabrowska $parts = []; 101340b94b1aSAnna Dabrowska $params = []; 101440b94b1aSAnna Dabrowska $filters = array_filter($filters); 1015df43a7beSAndreas Gohr if ($filters !== []) { 101640b94b1aSAnna Dabrowska $having = ' HAVING '; 101740b94b1aSAnna Dabrowska foreach ($filters as $filter => $value) { 101840b94b1aSAnna Dabrowska $parts[] = " $filter LIKE ? "; 101940b94b1aSAnna Dabrowska $params[] = "%$value%"; 102040b94b1aSAnna Dabrowska } 102140b94b1aSAnna Dabrowska $having .= implode(' AND ', $parts); 102240b94b1aSAnna Dabrowska } 102340b94b1aSAnna Dabrowska return [$having, $params]; 102440b94b1aSAnna Dabrowska } 1025ec4796e4SAnna Dabrowska 1026ec4796e4SAnna Dabrowska /** 1027ec4796e4SAnna Dabrowska * Returns taggings of nonexistent pages 1028ec4796e4SAnna Dabrowska * 1029ec4796e4SAnna Dabrowska * @return array 1030ec4796e4SAnna Dabrowska */ 1031ec4796e4SAnna Dabrowska protected function getInvalidTaggings() 1032ec4796e4SAnna Dabrowska { 1033ec4796e4SAnna Dabrowska $db = $this->getDB(); 1034ec4796e4SAnna Dabrowska $query = 'SELECT "pid", 1035ec4796e4SAnna Dabrowska GROUP_CONCAT(CLEANTAG("tag")) AS "tags" 1036ec4796e4SAnna Dabrowska FROM "taggings" 1037f24ac95eSAnna Dabrowska WHERE NOT PAGEEXISTS(pid) 1038ec4796e4SAnna Dabrowska GROUP BY pid 1039ec4796e4SAnna Dabrowska '; 1040*a6b932a0SAnna Dabrowska 1041*a6b932a0SAnna Dabrowska return $db->queryAll($query); 1042ec4796e4SAnna Dabrowska } 1043cd08599fSAnna Dabrowska} 1044