1f61105deSAdrian Lang<?php 2ec4796e4SAnna Dabrowska 3df43a7beSAndreas Gohruse dokuwiki\Extension\Plugin; 4*d9c03ba9SAnna Dabrowskause dokuwiki\Logger; 5df43a7beSAndreas Gohruse dokuwiki\Utf8\PhpString; 6ec4796e4SAnna Dabrowskause dokuwiki\Form\Form; 7df43a7beSAndreas Gohruse dokuwiki\Search\Indexer; 855d5df8cSAnna Dabrowskause dokuwiki\File\PageResolver; 9a6b932a0SAnna Dabrowskause dokuwiki\plugin\sqlite\SQLiteDB; 10ec4796e4SAnna Dabrowska 11aa627deeSAndreas Gohr/** 12a6b932a0SAnna Dabrowska * Tagging Plugin (helper component) 13aa627deeSAndreas Gohr * 14aa627deeSAndreas Gohr * @license GPL 2 15aa627deeSAndreas Gohr */ 16df43a7beSAndreas Gohrclass helper_plugin_tagging extends Plugin 17df43a7beSAndreas Gohr{ 18289f50bdSAndreas Gohr /** 19b12334e1SAndreas Gohr * Gives access to the database 20a6b932a0SAnna Dabrowska * Initializes SQLite and registers custom functions 21b12334e1SAndreas Gohr * 22a6b932a0SAnna Dabrowska * @return SQLiteDB|null 23a6b932a0SAnna Dabrowska * @throws Exception 24289f50bdSAndreas Gohr */ 25df43a7beSAndreas Gohr public function getDB() 26df43a7beSAndreas Gohr { 27302a38efSAndreas Gohr static $db = null; 28aa627deeSAndreas Gohr if ($db !== null) { 29f61105deSAdrian Lang return $db; 30f61105deSAdrian Lang } 31f61105deSAdrian Lang 32ca455b8eSMichael Große 33a6b932a0SAnna Dabrowska if (!class_exists(SQLiteDB::class)) throw new Exception('SQLite Plugin missing'); 34a6b932a0SAnna Dabrowska $db = new SQLiteDB('tagging', __DIR__ . '/db/'); 35a6b932a0SAnna Dabrowska 36a6b932a0SAnna Dabrowska $db->getPdo()->sqliteCreateFunction('CLEANTAG', [$this, 'cleanTag'], 1); 37a6b932a0SAnna Dabrowska $db->getPdo()->sqliteCreateFunction( 38df43a7beSAndreas Gohr 'GROUP_SORT', 397e05e623SSzymon Olewniczak function ($group, $newDelimiter) { 4040b94b1aSAnna Dabrowska $ex = array_filter(explode(',', $group)); 417e05e623SSzymon Olewniczak sort($ex); 42ca455b8eSMichael Große 437e05e623SSzymon Olewniczak return implode($newDelimiter, $ex); 44df43a7beSAndreas Gohr }, 45df43a7beSAndreas Gohr 2 46df43a7beSAndreas Gohr ); 47a6b932a0SAnna Dabrowska $db->getPdo()->sqliteCreateFunction('GET_NS', 'getNS', 1); 48ca455b8eSMichael Große 49f61105deSAdrian Lang return $db; 50f61105deSAdrian Lang } 51f61105deSAdrian Lang 52302a38efSAndreas Gohr /** 532ace74f4SAndreas Gohr * Return the user to use for accessing tags 542ace74f4SAndreas Gohr * 552ace74f4SAndreas Gohr * Handles the singleuser mode by returning 'auto' as user. Returnes false when no user is logged in. 562ace74f4SAndreas Gohr * 572ace74f4SAndreas Gohr * @return bool|string 582ace74f4SAndreas Gohr */ 59df43a7beSAndreas Gohr public function getUser() 60df43a7beSAndreas Gohr { 610cfde7e9SMichael Große if (!isset($_SERVER['REMOTE_USER'])) { 620cfde7e9SMichael Große return false; 630cfde7e9SMichael Große } 640cfde7e9SMichael Große if ($this->getConf('singleusermode')) { 650cfde7e9SMichael Große return 'auto'; 660cfde7e9SMichael Große } 67ca455b8eSMichael Große 682ace74f4SAndreas Gohr return $_SERVER['REMOTE_USER']; 692ace74f4SAndreas Gohr } 702ace74f4SAndreas Gohr 712ace74f4SAndreas Gohr /** 72e4443e5cSAnna Dabrowska * If plugin elasticsearch is installed, inform it that we have just made changes 73e4443e5cSAnna Dabrowska * to some data relevant to a page. The page should be re-indexed. 74e4443e5cSAnna Dabrowska * 75e4443e5cSAnna Dabrowska * @param string $id 76e4443e5cSAnna Dabrowska */ 77e4443e5cSAnna Dabrowska public function updateElasticState($id) 78e4443e5cSAnna Dabrowska { 79e4443e5cSAnna Dabrowska /** @var \helper_plugin_elasticsearch_plugins $elasticHelper */ 80e4443e5cSAnna Dabrowska $elasticHelper = plugin_load('helper', 'elasticsearch_plugins'); 81e4443e5cSAnna Dabrowska if ($elasticHelper) { 82e4443e5cSAnna Dabrowska $elasticHelper->updateRefreshState($id); 83e4443e5cSAnna Dabrowska } 84e4443e5cSAnna Dabrowska } 85e4443e5cSAnna Dabrowska 86e4443e5cSAnna Dabrowska /** 8755d5df8cSAnna Dabrowska * Resolve the ns filter 8855d5df8cSAnna Dabrowska * 8955d5df8cSAnna Dabrowska * @param array $data 9055d5df8cSAnna Dabrowska * @return string 9155d5df8cSAnna Dabrowska */ 9255d5df8cSAnna Dabrowska public function resolveNs(array $data) 9355d5df8cSAnna Dabrowska { 9455d5df8cSAnna Dabrowska if (!isset($data['ns'])) { 9555d5df8cSAnna Dabrowska $data['ns'] = '.'; 9655d5df8cSAnna Dabrowska } 9755d5df8cSAnna Dabrowska if ($data['ns'] === '*') { 9855d5df8cSAnna Dabrowska $data['ns'] = ''; 9955d5df8cSAnna Dabrowska } 10055d5df8cSAnna Dabrowska 10155d5df8cSAnna Dabrowska global $ID; 10255d5df8cSAnna Dabrowska $resolver = new PageResolver($ID); 10355d5df8cSAnna Dabrowska $data['ns'] = getNS($resolver->resolveId($data['ns'] . ':fakeIdNotResolvable')) ?: ':'; 10455d5df8cSAnna Dabrowska 10555d5df8cSAnna Dabrowska return $data['ns']; 10655d5df8cSAnna Dabrowska } 10755d5df8cSAnna Dabrowska 10855d5df8cSAnna Dabrowska /** 109302a38efSAndreas Gohr * Canonicalizes the tag to its lower case nospace form 110302a38efSAndreas Gohr * 111302a38efSAndreas Gohr * @param $tag 1120cfde7e9SMichael Große * 113302a38efSAndreas Gohr * @return string 114302a38efSAndreas Gohr */ 115df43a7beSAndreas Gohr public function cleanTag($tag) 116df43a7beSAndreas Gohr { 117df43a7beSAndreas Gohr $tag = str_replace([' ', '-', '_', '#'], '', $tag); 118df43a7beSAndreas Gohr return PhpString::strtolower($tag); 119302a38efSAndreas Gohr } 120302a38efSAndreas Gohr 12156d82720SAndreas Gohr /** 12231396860SSzymon Olewniczak * Canonicalizes the namespace, remove the first colon and add glob 12331396860SSzymon Olewniczak * 12431396860SSzymon Olewniczak * @param $namespace 12531396860SSzymon Olewniczak * 12631396860SSzymon Olewniczak * @return string 12731396860SSzymon Olewniczak */ 128df43a7beSAndreas Gohr public function globNamespace($namespace) 129df43a7beSAndreas Gohr { 130a6b932a0SAnna Dabrowska return cleanID($namespace) . '*'; 13131396860SSzymon Olewniczak } 13231396860SSzymon Olewniczak 13331396860SSzymon Olewniczak /** 13456d82720SAndreas Gohr * Create or Update tags of a page 13556d82720SAndreas Gohr * 13656d82720SAndreas Gohr * Uses the translation plugin to store the language of a page (if available) 13756d82720SAndreas Gohr * 13856d82720SAndreas Gohr * @param string $id The page ID 13956d82720SAndreas Gohr * @param string $user 14056d82720SAndreas Gohr * @param array $tags 1410cfde7e9SMichael Große * 14256d82720SAndreas Gohr */ 143df43a7beSAndreas Gohr public function replaceTags($id, $user, $tags) 144df43a7beSAndreas Gohr { 14556d82720SAndreas Gohr global $conf; 14656d82720SAndreas Gohr /** @var helper_plugin_translation $trans */ 14756d82720SAndreas Gohr $trans = plugin_load('helper', 'translation'); 14856d82720SAndreas Gohr if ($trans) { 14956d82720SAndreas Gohr $lang = $trans->realLC($trans->getLangPart($id)); 15056d82720SAndreas Gohr } else { 15156d82720SAndreas Gohr $lang = $conf['lang']; 15256d82720SAndreas Gohr } 15356d82720SAndreas Gohr 154f61105deSAdrian Lang $db = $this->getDB(); 155*d9c03ba9SAnna Dabrowska $db->exec('BEGIN TRANSACTION'); 156df43a7beSAndreas Gohr 157df43a7beSAndreas Gohr $queries = [['DELETE FROM taggings WHERE pid = ? AND tagger = ?', $id, $user]]; 158f61105deSAdrian Lang foreach ($tags as $tag) { 159df43a7beSAndreas Gohr $queries[] = ['INSERT INTO taggings (pid, tagger, tag, lang) VALUES(?, ?, ?, ?)', $id, $user, $tag, $lang]; 160f61105deSAdrian Lang } 161f61105deSAdrian Lang 162f61105deSAdrian Lang foreach ($queries as $query) { 163a6b932a0SAnna Dabrowska try { 164a6b932a0SAnna Dabrowska call_user_func_array([$db, 'exec'], $query); 165a6b932a0SAnna Dabrowska } catch (\PDOException $e) { 166*d9c03ba9SAnna Dabrowska Logger::error("Tagging: replacing tag failed - " . $e->getMessage()); 167*d9c03ba9SAnna Dabrowska $db->exec('ROLLBACK TRANSACTION'); 168a6b932a0SAnna Dabrowska return; 169f61105deSAdrian Lang } 170f61105deSAdrian Lang } 171ca455b8eSMichael Große 172*d9c03ba9SAnna Dabrowska $db->exec('COMMIT TRANSACTION'); 173f61105deSAdrian Lang } 174f61105deSAdrian Lang 1750a518a11SAndreas Gohr /** 176b12334e1SAndreas Gohr * Get a list of Tags or Pages matching search criteria 1770a518a11SAndreas Gohr * 178b12334e1SAndreas Gohr * @param array $filter What to search for array('field' => 'searchterm') 179b12334e1SAndreas Gohr * @param string $type What field to return 'tag'|'pid' 180077ff864SAndreas Gohr * @param int $limit Limit to this many results, 0 for all 1810cfde7e9SMichael Große * 1820a518a11SAndreas Gohr * @return array associative array in form of value => count 1830a518a11SAndreas Gohr */ 184df43a7beSAndreas Gohr public function findItems($filter, $type, $limit = 0) 185df43a7beSAndreas Gohr { 186df43a7beSAndreas Gohr $queryBuilder = new helper_plugin_tagging_querybuilder(); 1871b4b4fa9SAnna Dabrowska 1884a7da0a5SAnna Dabrowska $queryBuilder->setField($type); 1894a7da0a5SAnna Dabrowska $queryBuilder->setLimit($limit); 190739c5360SAnna Dabrowska $queryBuilder->setTags($this->extractFromQuery($filter)); 1914a7da0a5SAnna Dabrowska if (isset($filter['ns'])) $queryBuilder->includeNS($filter['ns']); 1924a7da0a5SAnna Dabrowska if (isset($filter['notns'])) $queryBuilder->excludeNS($filter['notns']); 1934a7da0a5SAnna Dabrowska if (isset($filter['tagger'])) $queryBuilder->setTagger($filter['tagger']); 1944a7da0a5SAnna Dabrowska if (isset($filter['pid'])) $queryBuilder->setPid($filter['pid']); 195b12334e1SAndreas Gohr 1964a7da0a5SAnna Dabrowska return $this->queryDb($queryBuilder->getQuery()); 197f61105deSAdrian Lang } 198f61105deSAdrian Lang 199b12334e1SAndreas Gohr /** 200302a38efSAndreas Gohr * Constructs the URL to search for a tag 201302a38efSAndreas Gohr * 2025540f91dSAndreas Gohr * @param string $tag 2035540f91dSAndreas Gohr * @param string $ns 2040cfde7e9SMichael Große * 205302a38efSAndreas Gohr * @return string 206302a38efSAndreas Gohr */ 207df43a7beSAndreas Gohr public function getTagSearchURL($tag, $ns = '') 208df43a7beSAndreas Gohr { 209a99fe09cSAnna Dabrowska $ret = '?do=search&sf=1&q=' . rawurlencode('#' . $this->cleanTag($tag)); 21055d5df8cSAnna Dabrowska if ($ns && $ns !== ':') { 2110cfde7e9SMichael Große $ret .= rawurlencode(' @' . $ns); 2120cfde7e9SMichael Große } 2135540f91dSAndreas Gohr 2145540f91dSAndreas Gohr return $ret; 215f61105deSAdrian Lang } 216f61105deSAdrian Lang 2175540f91dSAndreas Gohr /** 2185540f91dSAndreas Gohr * Calculates the size levels for the given list of clouds 2195540f91dSAndreas Gohr * 2205540f91dSAndreas Gohr * Automatically determines sensible tresholds 2215540f91dSAndreas Gohr * 2225540f91dSAndreas Gohr * @param array $tags list of tags => count 2235540f91dSAndreas Gohr * @param int $levels 2240cfde7e9SMichael Große * 225df43a7beSAndreas Gohr * @return array 2265540f91dSAndreas Gohr */ 227df43a7beSAndreas Gohr public function cloudData($tags, $levels = 10) 228df43a7beSAndreas Gohr { 229f61105deSAdrian Lang $min = min($tags); 230f61105deSAdrian Lang $max = max($tags); 231f61105deSAdrian Lang 232f61105deSAdrian Lang // calculate tresholds 233df43a7beSAndreas Gohr $tresholds = []; 234f61105deSAdrian Lang for ($i = 0; $i <= $levels; $i++) { 235df43a7beSAndreas Gohr $tresholds[$i] = ($max - $min + 1) ** ($i / $levels) + $min - 1; 236f61105deSAdrian Lang } 237f61105deSAdrian Lang 238f61105deSAdrian Lang // assign weights 239f61105deSAdrian Lang foreach ($tags as $tag => $cnt) { 240f61105deSAdrian Lang foreach ($tresholds as $tresh => $val) { 241f61105deSAdrian Lang if ($cnt <= $val) { 242f61105deSAdrian Lang $tags[$tag] = $tresh; 243f61105deSAdrian Lang break; 244f61105deSAdrian Lang } 245f61105deSAdrian Lang $tags[$tag] = $levels; 246f61105deSAdrian Lang } 247f61105deSAdrian Lang } 248ca455b8eSMichael Große 249f61105deSAdrian Lang return $tags; 250f61105deSAdrian Lang } 251f61105deSAdrian Lang 2525540f91dSAndreas Gohr /** 2535540f91dSAndreas Gohr * Display a tag cloud 2545540f91dSAndreas Gohr * 2555540f91dSAndreas Gohr * @param array $tags list of tags => count 2565540f91dSAndreas Gohr * @param string $type 'tag' 2575540f91dSAndreas Gohr * @param Callable $func The function to print the link (gets tag and ns) 2585540f91dSAndreas Gohr * @param bool $wrap wrap cloud in UL tags? 2595540f91dSAndreas Gohr * @param bool $return returnn HTML instead of printing? 2605540f91dSAndreas Gohr * @param string $ns Add this namespace to search links 2610cfde7e9SMichael Große * 2625540f91dSAndreas Gohr * @return string 2635540f91dSAndreas Gohr */ 264df43a7beSAndreas Gohr public function html_cloud($tags, $type, $func, $wrap = true, $return = false, $ns = '') 265df43a7beSAndreas Gohr { 266a66f6715SAndreas Gohr global $INFO; 267a66f6715SAndreas Gohr 268a66f6715SAndreas Gohr $hidden_str = $this->getConf('hiddenprefix'); 269a66f6715SAndreas Gohr $hidden_len = strlen($hidden_str); 270a66f6715SAndreas Gohr 271f61105deSAdrian Lang $ret = ''; 2720cfde7e9SMichael Große if ($wrap) { 2730cfde7e9SMichael Große $ret .= '<ul class="tagging_cloud clearfix">'; 2740cfde7e9SMichael Große } 275f61105deSAdrian Lang if (count($tags) === 0) { 276f61105deSAdrian Lang // Produce valid XHTML (ul needs a child) 277f61105deSAdrian Lang $this->setupLocale(); 278f61105deSAdrian Lang $ret .= '<li><div class="li">' . $this->lang['js']['no' . $type . 's'] . '</div></li>'; 279f61105deSAdrian Lang } else { 280f61105deSAdrian Lang $tags = $this->cloudData($tags); 281f61105deSAdrian Lang foreach ($tags as $val => $size) { 282a66f6715SAndreas Gohr // skip hidden tags for users that can't edit 283df43a7beSAndreas Gohr if ( 284df43a7beSAndreas Gohr $type === 'tag' && 285df43a7beSAndreas Gohr $hidden_len && 286df43a7beSAndreas Gohr substr($val, 0, $hidden_len) == $hidden_str && 287a66f6715SAndreas Gohr !($this->getUser() && $INFO['writable']) 288a66f6715SAndreas Gohr ) { 289a66f6715SAndreas Gohr continue; 290a66f6715SAndreas Gohr } 291a66f6715SAndreas Gohr 292f61105deSAdrian Lang $ret .= '<li class="t' . $size . '"><div class="li">'; 2935540f91dSAndreas Gohr $ret .= call_user_func($func, $val, $ns); 294f61105deSAdrian Lang $ret .= '</div></li>'; 295f61105deSAdrian Lang } 296f61105deSAdrian Lang } 2970cfde7e9SMichael Große if ($wrap) { 2980cfde7e9SMichael Große $ret .= '</ul>'; 2990cfde7e9SMichael Große } 3000cfde7e9SMichael Große if ($return) { 3010cfde7e9SMichael Große return $ret; 3020cfde7e9SMichael Große } 303f61105deSAdrian Lang echo $ret; 304ca455b8eSMichael Große 3055540f91dSAndreas Gohr return ''; 306f61105deSAdrian Lang } 307f61105deSAdrian Lang 3085540f91dSAndreas Gohr /** 3090b6fad27Ssandos187 * Display a List of Page Links 3100b6fad27Ssandos187 * 3110b6fad27Ssandos187 * @param array $pids list of pids => count 3120b6fad27Ssandos187 * @return string 3130b6fad27Ssandos187 */ 314df43a7beSAndreas Gohr public function html_page_list($pids) 315df43a7beSAndreas Gohr { 3160b6fad27Ssandos187 $ret = '<div class="search_quickresult">'; 3170b6fad27Ssandos187 $ret .= '<ul class="search_quickhits">'; 3180b6fad27Ssandos187 3190b6fad27Ssandos187 if (count($pids) === 0) { 3200b6fad27Ssandos187 // Produce valid XHTML (ul needs a child) 3210b6fad27Ssandos187 $ret .= '<li><div class="li">' . $this->lang['js']['nopages'] . '</div></li>'; 3220b6fad27Ssandos187 } else { 323bdf1ecf0SAnna Dabrowska foreach (array_keys($pids) as $val) { 3240b6fad27Ssandos187 $ret .= '<li><div class="li">'; 325db3ab356SAnna Dabrowska $ret .= html_wikilink(":$val"); 3260b6fad27Ssandos187 $ret .= '</div></li>'; 3270b6fad27Ssandos187 } 3280b6fad27Ssandos187 } 3290b6fad27Ssandos187 3300b6fad27Ssandos187 $ret .= '</ul>'; 3310b6fad27Ssandos187 $ret .= '</div>'; 3320b6fad27Ssandos187 $ret .= '<div class="clearer"></div>'; 3330b6fad27Ssandos187 3340b6fad27Ssandos187 return $ret; 3350b6fad27Ssandos187 } 3360b6fad27Ssandos187 3370b6fad27Ssandos187 /** 3385540f91dSAndreas Gohr * Get the link to a search for the given tag 3395540f91dSAndreas Gohr * 3405540f91dSAndreas Gohr * @param string $tag search for this tag 3415540f91dSAndreas Gohr * @param string $ns limit search to this namespace 3420cfde7e9SMichael Große * 3435540f91dSAndreas Gohr * @return string 3445540f91dSAndreas Gohr */ 345df43a7beSAndreas Gohr protected function linkToSearch($tag, $ns = '') 346df43a7beSAndreas Gohr { 3475540f91dSAndreas Gohr return '<a href="' . hsc($this->getTagSearchURL($tag, $ns)) . '">' . $tag . '</a>'; 348f61105deSAdrian Lang } 349f61105deSAdrian Lang 350fb1d0583SAndreas Gohr /** 351fb1d0583SAndreas Gohr * Display the Tags for the current page and prepare the tag editing form 3523496cc8aSAndreas Gohr * 3533496cc8aSAndreas Gohr * @param bool $print Should the HTML be printed or returned? 3540cfde7e9SMichael Große * 3553496cc8aSAndreas Gohr * @return string 356fb1d0583SAndreas Gohr */ 357df43a7beSAndreas Gohr public function tpl_tags($print = true) 358df43a7beSAndreas Gohr { 359f61105deSAdrian Lang global $INFO; 360f61105deSAdrian Lang global $lang; 3613bf0e2f1SMichael Große 362df43a7beSAndreas Gohr $filter = ['pid' => $INFO['id']]; 3633bf0e2f1SMichael Große if ($this->getConf('singleusermode')) { 3643bf0e2f1SMichael Große $filter['tagger'] = 'auto'; 3653bf0e2f1SMichael Große } 3663bf0e2f1SMichael Große 3673bf0e2f1SMichael Große $tags = $this->findItems($filter, 'tag'); 3683496cc8aSAndreas Gohr 369df43a7beSAndreas Gohr $ret = '<div class="plugin_tagging_edit">'; 370df43a7beSAndreas Gohr $ret .= $this->html_cloud($tags, 'tag', [$this, 'linkToSearch'], true, true); 371f61105deSAdrian Lang 3722ace74f4SAndreas Gohr if ($this->getUser() && $INFO['writable']) { 373f61105deSAdrian Lang $lang['btn_tagging_edit'] = $lang['btn_secedit']; 374e5b42768SSzymon Olewniczak $ret .= '<div id="tagging__edit_buttons_group">'; 375df43a7beSAndreas Gohr $ret .= html_btn('tagging_edit', $INFO['id'], '', []); 376dd52fd45SSzymon Olewniczak if (auth_isadmin()) { 37726f61833SAnna Dabrowska $ret .= '<label>' 37826f61833SAnna Dabrowska . $this->getLang('toggle admin mode') 37926f61833SAnna Dabrowska . '<input type="checkbox" id="tagging__edit_toggle_admin" /></label>'; 380dd52fd45SSzymon Olewniczak } 381e5b42768SSzymon Olewniczak $ret .= '</div>'; 382df43a7beSAndreas Gohr $form = new Form(); 3832819ffcdSSzymon Olewniczak $form->id('tagging__edit'); 3842819ffcdSSzymon Olewniczak $form->setHiddenField('tagging[id]', $INFO['id']); 3852819ffcdSSzymon Olewniczak $form->setHiddenField('call', 'plugin_tagging_save'); 386df43a7beSAndreas Gohr $tags = $this->findItems(['pid' => $INFO['id'], 'tagger' => $this->getUser()], 'tag'); 38726f61833SAnna Dabrowska $form->addTextarea('tagging[tags]') 38826f61833SAnna Dabrowska ->val(implode(', ', array_keys($tags))) 38926f61833SAnna Dabrowska ->addClass('edit') 39026f61833SAnna Dabrowska ->attr('rows', 4); 391cf52ec2dSSzymon Olewniczak $form->addButton('', $lang['btn_save'])->id('tagging__edit_save'); 392cf52ec2dSSzymon Olewniczak $form->addButton('', $lang['btn_cancel'])->id('tagging__edit_cancel'); 3932819ffcdSSzymon Olewniczak $ret .= $form->toHTML(); 394f61105deSAdrian Lang } 3953496cc8aSAndreas Gohr $ret .= '</div>'; 3963496cc8aSAndreas Gohr 3970cfde7e9SMichael Große if ($print) { 3980cfde7e9SMichael Große echo $ret; 3990cfde7e9SMichael Große } 400ca455b8eSMichael Große 4013496cc8aSAndreas Gohr return $ret; 402f61105deSAdrian Lang } 403872edc7cSRené Corinth 4048a1a3846SAndreas Gohr /** 405a99b66c1SSzymon Olewniczak * @param string $namespace empty for entire wiki 406a99b66c1SSzymon Olewniczak * 40740b94b1aSAnna Dabrowska * @param string $order_by 40840b94b1aSAnna Dabrowska * @param bool $desc 40940b94b1aSAnna Dabrowska * @param array $filters 4108a1a3846SAndreas Gohr * @return array 4118a1a3846SAndreas Gohr */ 412df43a7beSAndreas Gohr public function getAllTags($namespace = '', $order_by = 'tid', $desc = false, $filters = []) 413df43a7beSAndreas Gohr { 414df43a7beSAndreas Gohr $order_fields = ['pid', 'tid', 'taggers', 'ns', 'count']; 415f0084ee1SSzymon Olewniczak if (!in_array($order_by, $order_fields)) { 416f0084ee1SSzymon Olewniczak msg('cannot sort by ' . $order_by . ' field does not exists', -1); 417f0084ee1SSzymon Olewniczak $order_by = 'tag'; 418f0084ee1SSzymon Olewniczak } 419872edc7cSRené Corinth 420df43a7beSAndreas Gohr [$having, $params] = $this->getFilterSql($filters); 42140b94b1aSAnna Dabrowska 422a2246f69SAnna Dabrowska $db = $this->getDB(); 423872edc7cSRené Corinth 424f0084ee1SSzymon Olewniczak $query = 'SELECT "pid", 425ca455b8eSMichael Große CLEANTAG("tag") AS "tid", 426f0084ee1SSzymon Olewniczak GROUP_SORT(GROUP_CONCAT("tagger"), \', \') AS "taggers", 42740b94b1aSAnna Dabrowska GROUP_SORT(GROUP_CONCAT(GET_NS("pid")), \', \') AS "ns", 42889ed97adSAnna Dabrowska GROUP_SORT(GROUP_CONCAT("pid"), \', \') AS "pids", 429193a767dSSzymon Olewniczak COUNT(*) AS "count" 43057e45304SSzymon Olewniczak FROM "taggings" 4314227fca4SAnna Dabrowska WHERE "pid" GLOB ? AND GETACCESSLEVEL(pid) >= ' . AUTH_READ 4324227fca4SAnna Dabrowska . ' GROUP BY "tid"'; 43340b94b1aSAnna Dabrowska $query .= $having; 43440b94b1aSAnna Dabrowska $query .= 'ORDER BY ' . $order_by; 435ca455b8eSMichael Große if ($desc) { 436ca455b8eSMichael Große $query .= ' DESC'; 437ca455b8eSMichael Große } 438cb469644SSzymon Olewniczak 43940b94b1aSAnna Dabrowska array_unshift($params, $this->globNamespace($namespace)); 440a6b932a0SAnna Dabrowska return $db->queryAll($query, $params); 441872edc7cSRené Corinth } 442872edc7cSRené Corinth 4438a1a3846SAndreas Gohr /** 44472431326SMichael Große * Get all pages with tags and their tags 44572431326SMichael Große * 446790ca788SAndreas Gohr * @return array ['pid' => ['tag1','tag2','tag3']] 44772431326SMichael Große */ 448df43a7beSAndreas Gohr public function getAllTagsByPage() 449df43a7beSAndreas Gohr { 45072431326SMichael Große $query = ' 45172431326SMichael Große SELECT pid, GROUP_CONCAT(tag) AS tags 45272431326SMichael Große FROM taggings 45372431326SMichael Große GROUP BY pid 45472431326SMichael Große '; 455a6b932a0SAnna Dabrowska $db = $this->getDB(); 456790ca788SAndreas Gohr return array_map( 457df43a7beSAndreas Gohr fn($i) => explode(',', $i), 458a6b932a0SAnna Dabrowska array_column($db->queryAll($query), 'tags', 'pid') 459790ca788SAndreas Gohr ); 46072431326SMichael Große } 46172431326SMichael Große 46272431326SMichael Große /** 4638a1a3846SAndreas Gohr * Renames a tag 4648a1a3846SAndreas Gohr * 4658a1a3846SAndreas Gohr * @param string $formerTagName 4664227fca4SAnna Dabrowska * @param string $newTagNames 4678a1a3846SAndreas Gohr */ 468df43a7beSAndreas Gohr public function renameTag($formerTagName, $newTagNames) 469df43a7beSAndreas Gohr { 470872edc7cSRené Corinth 4714227fca4SAnna Dabrowska if (empty($formerTagName) || empty($newTagNames)) { 4728a1a3846SAndreas Gohr msg($this->getLang("admin enter tag names"), -1); 4738a1a3846SAndreas Gohr return; 474872edc7cSRené Corinth } 475872edc7cSRené Corinth 476870d77ddSAnna Dabrowska $keepFormerTag = false; 477870d77ddSAnna Dabrowska 4784227fca4SAnna Dabrowska // enable splitting tags on rename 479df43a7beSAndreas Gohr $newTagNames = array_map(fn($tag) => $this->cleanTag($tag), explode(',', $newTagNames)); 4804227fca4SAnna Dabrowska 4814227fca4SAnna Dabrowska $db = $this->getDB(); 482872edc7cSRené Corinth 4834227fca4SAnna Dabrowska // non-admins can rename only their own tags 4844227fca4SAnna Dabrowska if (!auth_isadmin()) { 4854227fca4SAnna Dabrowska $queryTagger = ' AND tagger = ?'; 4864227fca4SAnna Dabrowska $tagger = $this->getUser(); 4874227fca4SAnna Dabrowska } else { 4884227fca4SAnna Dabrowska $queryTagger = ''; 4894227fca4SAnna Dabrowska $tagger = ''; 4904227fca4SAnna Dabrowska } 4914227fca4SAnna Dabrowska 4920ec63874SAnna Dabrowska $insertQuery = 'INSERT INTO taggings '; 4930ec63874SAnna Dabrowska $insertQuery .= 'SELECT pid, ?, tagger, lang FROM taggings'; 4940ec63874SAnna Dabrowska $where = ' WHERE CLEANTAG(tag) = ?'; 4950ec63874SAnna Dabrowska $where .= ' AND GETACCESSLEVEL(pid) >= ' . AUTH_EDIT; 4960ec63874SAnna Dabrowska $where .= $queryTagger; 4970ec63874SAnna Dabrowska 498*d9c03ba9SAnna Dabrowska $db->exec('BEGIN TRANSACTION'); 4990ec63874SAnna Dabrowska 5000ec63874SAnna Dabrowska // insert new tags first 5010ec63874SAnna Dabrowska foreach ($newTagNames as $newTag) { 502870d77ddSAnna Dabrowska if ($newTag === $this->cleanTag($formerTagName)) { 503870d77ddSAnna Dabrowska $keepFormerTag = true; 504870d77ddSAnna Dabrowska continue; 505870d77ddSAnna Dabrowska } 506870d77ddSAnna Dabrowska $params = [$newTag, $this->cleanTag($formerTagName)]; 507df43a7beSAndreas Gohr if ($tagger) $params[] = $tagger; 508a6b932a0SAnna Dabrowska 509a6b932a0SAnna Dabrowska try { 510a6b932a0SAnna Dabrowska $db->exec($insertQuery . $where, $params); 511a6b932a0SAnna Dabrowska } catch (\PDOException $e) { 512*d9c03ba9SAnna Dabrowska Logger::error("Tagging: renaming tag $formerTagName failed - " . $e->getMessage()); 513*d9c03ba9SAnna Dabrowska $db->exec('ROLLBACK TRANSACTION'); 5140ec63874SAnna Dabrowska return; 5154227fca4SAnna Dabrowska } 5160ec63874SAnna Dabrowska } 5170ec63874SAnna Dabrowska 518870d77ddSAnna Dabrowska // finally delete the renamed tags 519870d77ddSAnna Dabrowska if (!$keepFormerTag) { 5200ec63874SAnna Dabrowska $deleteQuery = 'DELETE FROM taggings'; 5210ec63874SAnna Dabrowska $params = [$this->cleanTag($formerTagName)]; 522df43a7beSAndreas Gohr if ($tagger) $params[] = $tagger; 523a6b932a0SAnna Dabrowska try { 524a6b932a0SAnna Dabrowska $db->exec($deleteQuery . $where, $params); 525a6b932a0SAnna Dabrowska } catch (\PDOException $e) { 526*d9c03ba9SAnna Dabrowska Logger::error("Tagging: renaming tag $formerTagName failed - " . $e->getMessage()); 527*d9c03ba9SAnna Dabrowska $db->exec('ROLLBACK TRANSACTION'); 5280ec63874SAnna Dabrowska return; 5290ec63874SAnna Dabrowska } 530870d77ddSAnna Dabrowska } 5310ec63874SAnna Dabrowska 532*d9c03ba9SAnna Dabrowska $db->exec('COMMIT TRANSACTION'); 533872edc7cSRené Corinth 534fb1d0583SAndreas Gohr msg($this->getLang("admin renamed"), 1); 535872edc7cSRené Corinth } 536872edc7cSRené Corinth 5378f630140SSzymon Olewniczak /** 538dd52fd45SSzymon Olewniczak * Rename or delete a tag for all users 539dd52fd45SSzymon Olewniczak * 540dd52fd45SSzymon Olewniczak * @param string $pid 541dd52fd45SSzymon Olewniczak * @param string $formerTagName 542dd52fd45SSzymon Olewniczak * @param string $newTagName 543dd52fd45SSzymon Olewniczak * 544dd52fd45SSzymon Olewniczak * @return array 545dd52fd45SSzymon Olewniczak */ 546df43a7beSAndreas Gohr public function modifyPageTag($pid, $formerTagName, $newTagName) 547df43a7beSAndreas Gohr { 548a6b932a0SAnna Dabrowska $db = $this->getDB(); 549dd52fd45SSzymon Olewniczak 550a6b932a0SAnna Dabrowska $check = $db->queryAll( 55126f61833SAnna Dabrowska 'SELECT pid FROM taggings WHERE CLEANTAG(tag) = ? AND pid = ?', 55226f61833SAnna Dabrowska $this->cleanTag($formerTagName), 55326f61833SAnna Dabrowska $pid 55426f61833SAnna Dabrowska ); 555dd52fd45SSzymon Olewniczak 556dd52fd45SSzymon Olewniczak if (empty($check)) { 557df43a7beSAndreas Gohr return [true, $this->getLang('admin tag does not exists')]; 558dd52fd45SSzymon Olewniczak } 559dd52fd45SSzymon Olewniczak 560dd52fd45SSzymon Olewniczak if (empty($newTagName)) { 561*d9c03ba9SAnna Dabrowska $db->exec( 56226f61833SAnna Dabrowska 'DELETE FROM taggings WHERE pid = ? AND CLEANTAG(tag) = ?', 56326f61833SAnna Dabrowska $pid, 56426f61833SAnna Dabrowska $this->cleanTag($formerTagName) 56526f61833SAnna Dabrowska ); 566dd52fd45SSzymon Olewniczak } else { 567*d9c03ba9SAnna Dabrowska $db->exec( 56826f61833SAnna Dabrowska 'UPDATE taggings SET tag = ? WHERE pid = ? AND CLEANTAG(tag) = ?', 56926f61833SAnna Dabrowska $newTagName, 57026f61833SAnna Dabrowska $pid, 57126f61833SAnna Dabrowska $this->cleanTag($formerTagName) 57226f61833SAnna Dabrowska ); 573dd52fd45SSzymon Olewniczak } 574dd52fd45SSzymon Olewniczak 575df43a7beSAndreas Gohr return [false, $this->getLang('admin renamed')]; 576dd52fd45SSzymon Olewniczak } 577dd52fd45SSzymon Olewniczak 578dd52fd45SSzymon Olewniczak /** 5798f630140SSzymon Olewniczak * Deletes a tag 5808f630140SSzymon Olewniczak * 5818f630140SSzymon Olewniczak * @param array $tags 58231396860SSzymon Olewniczak * @param string $namespace current namespace context as in getAllTags() 5838f630140SSzymon Olewniczak */ 584df43a7beSAndreas Gohr public function deleteTags($tags, $namespace = '') 585df43a7beSAndreas Gohr { 586ca455b8eSMichael Große if (empty($tags)) { 587ca455b8eSMichael Große return; 588ca455b8eSMichael Große } 5898f630140SSzymon Olewniczak 590a6b932a0SAnna Dabrowska $namespace = cleanID($namespace); 59131396860SSzymon Olewniczak 5921f5991a7SMichael Große $db = $this->getDB(); 5938f630140SSzymon Olewniczak 594de379874SAnna Dabrowska $queryBody = 'FROM taggings WHERE pid GLOB ? AND (' . 59531396860SSzymon Olewniczak implode(' OR ', array_fill(0, count($tags), 'CLEANTAG(tag) = ?')) . ')'; 596df43a7beSAndreas Gohr $args = array_map([$this, 'cleanTag'], $tags); 59731396860SSzymon Olewniczak array_unshift($args, $this->globNamespace($namespace)); 5988f630140SSzymon Olewniczak 5994227fca4SAnna Dabrowska // non-admins can delete only their own tags 6004227fca4SAnna Dabrowska if (!auth_isadmin()) { 6014227fca4SAnna Dabrowska $queryBody .= ' AND tagger = ?'; 602df43a7beSAndreas Gohr $args[] = $this->getUser(); 6034227fca4SAnna Dabrowska } 604ca455b8eSMichael Große 6051f5991a7SMichael Große $affectedPagesQuery = 'SELECT DISTINCT pid ' . $queryBody; 606a6b932a0SAnna Dabrowska $numAffectedPages = $db->exec($affectedPagesQuery, $args); 6071f5991a7SMichael Große 6081f5991a7SMichael Große $deleteQuery = 'DELETE ' . $queryBody; 609*d9c03ba9SAnna Dabrowska $db->exec($deleteQuery, $args); 6101f5991a7SMichael Große 6111f5991a7SMichael Große msg(sprintf($this->getLang("admin deleted"), count($tags), $numAffectedPages), 1); 6128f630140SSzymon Olewniczak } 613cd08599fSAnna Dabrowska 614cd08599fSAnna Dabrowska /** 615ec4796e4SAnna Dabrowska * Delete taggings of nonexistent pages 616ec4796e4SAnna Dabrowska */ 617ec4796e4SAnna Dabrowska public function deleteInvalidTaggings() 618ec4796e4SAnna Dabrowska { 619ec4796e4SAnna Dabrowska $db = $this->getDB(); 620ec4796e4SAnna Dabrowska $query = 'DELETE FROM "taggings" 6211815f49fSAnna Dabrowska WHERE NOT PAGEEXISTS(pid) 622ec4796e4SAnna Dabrowska '; 623*d9c03ba9SAnna Dabrowska $db->exec($query); 624ec4796e4SAnna Dabrowska } 625ec4796e4SAnna Dabrowska 626ec4796e4SAnna Dabrowska /** 627cd08599fSAnna Dabrowska * Updates tags with a new page name 628cd08599fSAnna Dabrowska * 629cd08599fSAnna Dabrowska * @param string $oldName 630cd08599fSAnna Dabrowska * @param string $newName 631cd08599fSAnna Dabrowska */ 632df43a7beSAndreas Gohr public function renamePage($oldName, $newName) 633df43a7beSAndreas Gohr { 634f6568bcbSAnna Dabrowska $db = $this->getDB(); 635*d9c03ba9SAnna Dabrowska $db->exec('UPDATE taggings SET pid = ? WHERE pid = ?', $newName, $oldName); 636cd08599fSAnna Dabrowska } 637972f6adfSAnna Dabrowska 638972f6adfSAnna Dabrowska /** 6391b4b4fa9SAnna Dabrowska * Extracts tags from search query 6401b4b4fa9SAnna Dabrowska * 6411b4b4fa9SAnna Dabrowska * @param array $parsedQuery 6421b4b4fa9SAnna Dabrowska * @return array 6431b4b4fa9SAnna Dabrowska */ 644739c5360SAnna Dabrowska public function extractFromQuery($parsedQuery) 6451b4b4fa9SAnna Dabrowska { 6461b4b4fa9SAnna Dabrowska $tags = []; 6471b4b4fa9SAnna Dabrowska if (isset($parsedQuery['phrases'][0])) { 6481b4b4fa9SAnna Dabrowska $tags = $parsedQuery['phrases']; 6491b4b4fa9SAnna Dabrowska } elseif (isset($parsedQuery['and'][0])) { 6501b4b4fa9SAnna Dabrowska $tags = $parsedQuery['and']; 6511b4b4fa9SAnna Dabrowska } elseif (isset($parsedQuery['tag'])) { 6521b4b4fa9SAnna Dabrowska // handle autocomplete call 6531b4b4fa9SAnna Dabrowska $tags[] = $parsedQuery['tag']; 6541b4b4fa9SAnna Dabrowska } 6551b4b4fa9SAnna Dabrowska return $tags; 6561b4b4fa9SAnna Dabrowska } 6571b4b4fa9SAnna Dabrowska 6581b4b4fa9SAnna Dabrowska /** 6594a7da0a5SAnna Dabrowska * Search for tagged pages 660972f6adfSAnna Dabrowska * 661739c5360SAnna Dabrowska * @param array $tagFiler 6624a7da0a5SAnna Dabrowska * @return array 663972f6adfSAnna Dabrowska */ 664739c5360SAnna Dabrowska public function searchPages($tagFiler) 665972f6adfSAnna Dabrowska { 6661b4b4fa9SAnna Dabrowska global $INPUT; 6671b4b4fa9SAnna Dabrowska global $QUERY; 668df43a7beSAndreas Gohr $parsedQuery = ft_queryParser(new Indexer(), $QUERY); 669972f6adfSAnna Dabrowska 670df43a7beSAndreas Gohr $queryBuilder = new helper_plugin_tagging_querybuilder(); 671972f6adfSAnna Dabrowska 6724a7da0a5SAnna Dabrowska $queryBuilder->setField('pid'); 673cbe7b4baSAnna Dabrowska $queryBuilder->setTags($tagFiler); 674f014dfc9SAnna Dabrowska $queryBuilder->setLogicalAnd($INPUT->str('tagging-logic') === 'and'); 6754a7da0a5SAnna Dabrowska if (isset($parsedQuery['ns'])) $queryBuilder->includeNS($parsedQuery['ns']); 6764a7da0a5SAnna Dabrowska if (isset($parsedQuery['notns'])) $queryBuilder->excludeNS($parsedQuery['notns']); 6774a7da0a5SAnna Dabrowska if (isset($parsedQuery['tagger'])) $queryBuilder->setTagger($parsedQuery['tagger']); 6784a7da0a5SAnna Dabrowska if (isset($parsedQuery['pid'])) $queryBuilder->setPid($parsedQuery['pid']); 679972f6adfSAnna Dabrowska 6804a7da0a5SAnna Dabrowska return $this->queryDb($queryBuilder->getPages()); 681972f6adfSAnna Dabrowska } 682972f6adfSAnna Dabrowska 6831b4b4fa9SAnna Dabrowska /** 6844227fca4SAnna Dabrowska * Syntax to allow users to manage tags on regular pages, respects ACLs 6854227fca4SAnna Dabrowska * @param string $ns 6864227fca4SAnna Dabrowska * @return string 6874227fca4SAnna Dabrowska */ 6884227fca4SAnna Dabrowska public function manageTags($ns) 6894227fca4SAnna Dabrowska { 6904227fca4SAnna Dabrowska global $INPUT; 6914227fca4SAnna Dabrowska 692f6568bcbSAnna Dabrowska $this->setDefaultSort(); 6934227fca4SAnna Dabrowska 6944227fca4SAnna Dabrowska // initially set namespace filter to what is defined in syntax 6954227fca4SAnna Dabrowska if ($ns && !$INPUT->has('tagging__filters')) { 6964227fca4SAnna Dabrowska $INPUT->set('tagging__filters', ['ns' => $ns]); 6974227fca4SAnna Dabrowska } 6984227fca4SAnna Dabrowska 6994227fca4SAnna Dabrowska return $this->html_table(); 7004227fca4SAnna Dabrowska } 7014227fca4SAnna Dabrowska 7024227fca4SAnna Dabrowska /** 703a2246f69SAnna Dabrowska * HTML list of tagged pages 704a2246f69SAnna Dabrowska * 705a2246f69SAnna Dabrowska * @param string $tid 706a2246f69SAnna Dabrowska * @return string 707a2246f69SAnna Dabrowska */ 708a2246f69SAnna Dabrowska public function getPagesHtml($tid) 709a2246f69SAnna Dabrowska { 710a2246f69SAnna Dabrowska $html = ''; 711a2246f69SAnna Dabrowska 712a2246f69SAnna Dabrowska $db = $this->getDB(); 71331bddc5fSAnna Dabrowska $sql = 'SELECT pid from taggings where CLEANTAG(tag) = CLEANTAG(?)'; 714a6b932a0SAnna Dabrowska $pages = $db->queryAll($sql, $tid); 715a2246f69SAnna Dabrowska 716a2246f69SAnna Dabrowska if ($pages) { 717a2246f69SAnna Dabrowska $html .= '<ul>'; 718a2246f69SAnna Dabrowska foreach ($pages as $page) { 719a2246f69SAnna Dabrowska $pid = $page['pid']; 720a2246f69SAnna Dabrowska $html .= '<li><a href="' . wl($pid) . '" target="_blank">' . $pid . '</li>'; 721a2246f69SAnna Dabrowska } 722a2246f69SAnna Dabrowska $html .= '</ul>'; 723a2246f69SAnna Dabrowska } 724a2246f69SAnna Dabrowska 725a2246f69SAnna Dabrowska return $html; 726a2246f69SAnna Dabrowska } 727a2246f69SAnna Dabrowska 728a2246f69SAnna Dabrowska /** 72989ed97adSAnna Dabrowska * Display tag management table 73089ed97adSAnna Dabrowska */ 731df43a7beSAndreas Gohr public function html_table() 732df43a7beSAndreas Gohr { 73389ed97adSAnna Dabrowska global $ID, $INPUT; 73489ed97adSAnna Dabrowska 735df43a7beSAndreas Gohr $headers = [ 736df43a7beSAndreas Gohr ['value' => $this->getLang('admin tag'), 'sort_by' => 'tid'], 737df43a7beSAndreas Gohr ['value' => $this->getLang('admin occurrence'), 'sort_by' => 'count'] 738df43a7beSAndreas Gohr ]; 73926c4179dSAnna Dabrowska 74026c4179dSAnna Dabrowska if (!$this->conf['hidens']) { 741df43a7beSAndreas Gohr $headers[] = ['value' => $this->getLang('admin namespaces'), 'sort_by' => 'ns']; 74226c4179dSAnna Dabrowska } 743df43a7beSAndreas Gohr $headers[] = ['value' => $this->getLang('admin taggers'), 'sort_by' => 'taggers']; 744df43a7beSAndreas Gohr $headers[] = ['value' => $this->getLang('admin actions'), 'sort_by' => false]; 74589ed97adSAnna Dabrowska 746f6568bcbSAnna Dabrowska $sort = explode(',', $this->getParam('sort')); 74789ed97adSAnna Dabrowska $order_by = $sort[0]; 74889ed97adSAnna Dabrowska $desc = false; 74989ed97adSAnna Dabrowska if (isset($sort[1]) && $sort[1] === 'desc') { 75089ed97adSAnna Dabrowska $desc = true; 75189ed97adSAnna Dabrowska } 75289ed97adSAnna Dabrowska $filters = $INPUT->arr('tagging__filters'); 75389ed97adSAnna Dabrowska 75489ed97adSAnna Dabrowska $tags = $this->getAllTags($INPUT->str('filter'), $order_by, $desc, $filters); 75589ed97adSAnna Dabrowska 756df43a7beSAndreas Gohr $form = new Form(); 757a2246f69SAnna Dabrowska // required in admin mode 75889ed97adSAnna Dabrowska $form->setHiddenField('page', 'tagging'); 75989ed97adSAnna Dabrowska $form->setHiddenField('id', $ID); 760f6568bcbSAnna Dabrowska $form->setHiddenField('[tagging]sort', $this->getParam('sort')); 76189ed97adSAnna Dabrowska 76289ed97adSAnna Dabrowska /** 76389ed97adSAnna Dabrowska * Actions dialog 76489ed97adSAnna Dabrowska */ 76589ed97adSAnna Dabrowska $form->addTagOpen('div')->id('tagging__action-dialog')->attr('style', "display:none;"); 76689ed97adSAnna Dabrowska $form->addTagClose('div'); 76789ed97adSAnna Dabrowska 76889ed97adSAnna Dabrowska /** 76989ed97adSAnna Dabrowska * Tag pages dialog 77089ed97adSAnna Dabrowska */ 77189ed97adSAnna Dabrowska $form->addTagOpen('div')->id('tagging__taggedpages-dialog')->attr('style', "display:none;"); 77289ed97adSAnna Dabrowska $form->addTagClose('div'); 77389ed97adSAnna Dabrowska 77489ed97adSAnna Dabrowska /** 77589ed97adSAnna Dabrowska * Tag management table 77689ed97adSAnna Dabrowska */ 77789ed97adSAnna Dabrowska $form->addTagOpen('table')->addClass('inline plugin_tagging'); 77889ed97adSAnna Dabrowska 779df43a7beSAndreas Gohr $nscol = $this->conf['hidens'] ? '' : '<col class="wide-col" />'; 780a305ec34SAnna Dabrowska $form->addHTML( 781a305ec34SAnna Dabrowska '<colgroup> 782df43a7beSAndreas Gohr <col /> 783df43a7beSAndreas Gohr <col class="narrow-col" />' 78426c4179dSAnna Dabrowska . $nscol . 785df43a7beSAndreas Gohr '<col /> 786df43a7beSAndreas Gohr <col class="narrow-col" /> 787a305ec34SAnna Dabrowska </colgroup>' 788a305ec34SAnna Dabrowska ); 789a305ec34SAnna Dabrowska 79089ed97adSAnna Dabrowska /** 79189ed97adSAnna Dabrowska * Table headers 79289ed97adSAnna Dabrowska */ 79389ed97adSAnna Dabrowska $form->addTagOpen('tr'); 79489ed97adSAnna Dabrowska foreach ($headers as $header) { 79589ed97adSAnna Dabrowska $form->addTagOpen('th'); 79689ed97adSAnna Dabrowska if ($header['sort_by'] !== false) { 79789ed97adSAnna Dabrowska $param = $header['sort_by']; 79889ed97adSAnna Dabrowska $icon = 'arrow-both'; 79989ed97adSAnna Dabrowska $title = $this->getLang('admin sort ascending'); 80089ed97adSAnna Dabrowska if ($header['sort_by'] === $order_by) { 80189ed97adSAnna Dabrowska if ($desc === false) { 80289ed97adSAnna Dabrowska $icon = 'arrow-up'; 80389ed97adSAnna Dabrowska $title = $this->getLang('admin sort descending'); 80489ed97adSAnna Dabrowska $param .= ',desc'; 80589ed97adSAnna Dabrowska } else { 80689ed97adSAnna Dabrowska $icon = 'arrow-down'; 80789ed97adSAnna Dabrowska } 80889ed97adSAnna Dabrowska } 80926f61833SAnna Dabrowska $form->addButtonHTML( 810f6568bcbSAnna Dabrowska "tagging[sort]", 811df43a7beSAndreas Gohr $header['value'] . ' ' . inlineSVG(__DIR__ . "/images/$icon.svg") 812df43a7beSAndreas Gohr ) 81389ed97adSAnna Dabrowska ->addClass('plugin_tagging sort_button') 814f6568bcbSAnna Dabrowska ->attr('title', $title) 815f6568bcbSAnna Dabrowska ->val($param); 81689ed97adSAnna Dabrowska } else { 81789ed97adSAnna Dabrowska $form->addHTML($header['value']); 81889ed97adSAnna Dabrowska } 81989ed97adSAnna Dabrowska $form->addTagClose('th'); 82089ed97adSAnna Dabrowska } 82189ed97adSAnna Dabrowska $form->addTagClose('tr'); 82289ed97adSAnna Dabrowska 82389ed97adSAnna Dabrowska /** 82489ed97adSAnna Dabrowska * Table filters for all sortable columns 82589ed97adSAnna Dabrowska */ 82689ed97adSAnna Dabrowska $form->addTagOpen('tr'); 82789ed97adSAnna Dabrowska foreach ($headers as $header) { 82889ed97adSAnna Dabrowska $form->addTagOpen('th'); 82989ed97adSAnna Dabrowska if ($header['sort_by'] !== false) { 83089ed97adSAnna Dabrowska $field = $header['sort_by']; 8317c96ae87SAnna Dabrowska $input = $form->addTextInput("tagging__filters[$field]"); 832a305ec34SAnna Dabrowska $input->addClass('full-col'); 83389ed97adSAnna Dabrowska } 83489ed97adSAnna Dabrowska $form->addTagClose('th'); 83589ed97adSAnna Dabrowska } 83689ed97adSAnna Dabrowska $form->addTagClose('tr'); 83789ed97adSAnna Dabrowska 83889ed97adSAnna Dabrowska 83989ed97adSAnna Dabrowska foreach ($tags as $taginfo) { 84089ed97adSAnna Dabrowska $tagname = $taginfo['tid']; 84189ed97adSAnna Dabrowska $taggers = $taginfo['taggers']; 84289ed97adSAnna Dabrowska $ns = $taginfo['ns']; 84389ed97adSAnna Dabrowska $pids = explode(',', $taginfo['pids']); 84489ed97adSAnna Dabrowska 84589ed97adSAnna Dabrowska $form->addTagOpen('tr'); 84626f61833SAnna Dabrowska $form->addHTML('<td>'); 847a2246f69SAnna Dabrowska $form->addHTML('<a class="tagslist" href="#" data-tid="' . $taginfo['tid'] . '">'); 84826f61833SAnna Dabrowska $form->addHTML(hsc($tagname) . '</a>'); 84926f61833SAnna Dabrowska $form->addHTML('</td>'); 85089ed97adSAnna Dabrowska $form->addHTML('<td>' . $taginfo['count'] . '</td>'); 85126c4179dSAnna Dabrowska if (!$this->conf['hidens']) { 85289ed97adSAnna Dabrowska $form->addHTML('<td>' . hsc($ns) . '</td>'); 85326c4179dSAnna Dabrowska } 85489ed97adSAnna Dabrowska $form->addHTML('<td>' . hsc($taggers) . '</td>'); 85589ed97adSAnna Dabrowska 85689ed97adSAnna Dabrowska /** 85789ed97adSAnna Dabrowska * action buttons 85889ed97adSAnna Dabrowska */ 85989ed97adSAnna Dabrowska $form->addHTML('<td>'); 86089ed97adSAnna Dabrowska 86189ed97adSAnna Dabrowska // check ACLs 86289ed97adSAnna Dabrowska $userEdit = false; 86389ed97adSAnna Dabrowska foreach ($pids as $pid) { 864749c70e5SAndreas Gohr if (auth_quickaclcheck($pid) >= AUTH_EDIT) { 86589ed97adSAnna Dabrowska $userEdit = true; 866df43a7beSAndreas Gohr break; 86789ed97adSAnna Dabrowska } 86889ed97adSAnna Dabrowska } 86989ed97adSAnna Dabrowska 87089ed97adSAnna Dabrowska if ($userEdit) { 87126f61833SAnna Dabrowska $form->addButtonHTML( 872f6568bcbSAnna Dabrowska 'tagging[actions][rename][' . $taginfo['tid'] . ']', 873df43a7beSAndreas Gohr inlineSVG(__DIR__ . '/images/edit.svg') 874df43a7beSAndreas Gohr ) 87526f61833SAnna Dabrowska ->addClass('plugin_tagging action_button') 87626f61833SAnna Dabrowska ->attr('data-action', 'rename') 87726f61833SAnna Dabrowska ->attr('data-tid', $taginfo['tid']); 87826f61833SAnna Dabrowska $form->addButtonHTML( 879f6568bcbSAnna Dabrowska 'tagging[actions][delete][' . $taginfo['tid'] . ']', 880df43a7beSAndreas Gohr inlineSVG(__DIR__ . '/images/delete.svg') 881df43a7beSAndreas Gohr ) 88226f61833SAnna Dabrowska ->addClass('plugin_tagging action_button') 88326f61833SAnna Dabrowska ->attr('data-action', 'delete') 88426f61833SAnna Dabrowska ->attr('data-tid', $taginfo['tid']); 88589ed97adSAnna Dabrowska } 88689ed97adSAnna Dabrowska 88789ed97adSAnna Dabrowska $form->addHTML('</td>'); 88889ed97adSAnna Dabrowska $form->addTagClose('tr'); 88989ed97adSAnna Dabrowska } 89089ed97adSAnna Dabrowska 89189ed97adSAnna Dabrowska $form->addTagClose('table'); 8920b033188SAnna Dabrowska return '<div class="table">' . $form->toHTML() . '</div>'; 89389ed97adSAnna Dabrowska } 89489ed97adSAnna Dabrowska 89589ed97adSAnna Dabrowska /** 896ec4796e4SAnna Dabrowska * Display tag cleaner 897ec4796e4SAnna Dabrowska * 898ec4796e4SAnna Dabrowska * @return string 899ec4796e4SAnna Dabrowska */ 900ec4796e4SAnna Dabrowska public function html_clean() 901ec4796e4SAnna Dabrowska { 902ec4796e4SAnna Dabrowska $invalid = $this->getInvalidTaggings(); 903ec4796e4SAnna Dabrowska 904ec4796e4SAnna Dabrowska if (!$invalid) { 905ec4796e4SAnna Dabrowska return '<p><strong>' . $this->getLang('admin no invalid') . '</strong></p>'; 906ec4796e4SAnna Dabrowska } 907ec4796e4SAnna Dabrowska 908ec4796e4SAnna Dabrowska $form = new Form(); 909ec4796e4SAnna Dabrowska $form->setHiddenField('do', 'admin'); 910ec4796e4SAnna Dabrowska $form->setHiddenField('page', $this->getPluginName()); 911ec4796e4SAnna Dabrowska $form->addButton('cmd[clean]', $this->getLang('admin clean')); 912ec4796e4SAnna Dabrowska 913ec4796e4SAnna Dabrowska $html = $form->toHTML(); 914ec4796e4SAnna Dabrowska 915ec4796e4SAnna Dabrowska $html .= '<div class="table"><table class="inline plugin_tagging">'; 916ec4796e4SAnna Dabrowska $html .= '<thead><tr><th>' . 917ec4796e4SAnna Dabrowska $this->getLang('admin nonexistent page') . 918ec4796e4SAnna Dabrowska '</th><th>' . 919ec4796e4SAnna Dabrowska $this->getLang('admin tags') . 920ec4796e4SAnna Dabrowska '</th></tr></thead><tbody>'; 921ec4796e4SAnna Dabrowska 922ec4796e4SAnna Dabrowska foreach ($invalid as $row) { 923ec4796e4SAnna Dabrowska $html .= '<tr><td>' . $row['pid'] . '</td><td>' . $row['tags'] . '</td></tr>'; 924ec4796e4SAnna Dabrowska } 925ec4796e4SAnna Dabrowska 926ec4796e4SAnna Dabrowska $html .= '</tbody></table></div>'; 927ec4796e4SAnna Dabrowska 928ec4796e4SAnna Dabrowska return $html; 929ec4796e4SAnna Dabrowska } 930ec4796e4SAnna Dabrowska 931ec4796e4SAnna Dabrowska /** 932f6568bcbSAnna Dabrowska * Returns all tagging parameters from the query string 933f6568bcbSAnna Dabrowska * 934f6568bcbSAnna Dabrowska * @return mixed 935f6568bcbSAnna Dabrowska */ 936f6568bcbSAnna Dabrowska public function getParams() 937f6568bcbSAnna Dabrowska { 938f6568bcbSAnna Dabrowska global $INPUT; 939f6568bcbSAnna Dabrowska return $INPUT->param('tagging', []); 940f6568bcbSAnna Dabrowska } 941f6568bcbSAnna Dabrowska 942f6568bcbSAnna Dabrowska /** 943f6568bcbSAnna Dabrowska * Get a tagging parameter, empty string if not set 944f6568bcbSAnna Dabrowska * 945f6568bcbSAnna Dabrowska * @param string $name 946df43a7beSAndreas Gohr * @return string 947f6568bcbSAnna Dabrowska */ 948f6568bcbSAnna Dabrowska public function getParam($name) 949f6568bcbSAnna Dabrowska { 950f6568bcbSAnna Dabrowska $params = $this->getParams(); 951f6568bcbSAnna Dabrowska if ($params) { 952f6568bcbSAnna Dabrowska return $params[$name] ?: ''; 953f6568bcbSAnna Dabrowska } 954df43a7beSAndreas Gohr return ''; 955f6568bcbSAnna Dabrowska } 956f6568bcbSAnna Dabrowska 957f6568bcbSAnna Dabrowska /** 958f6568bcbSAnna Dabrowska * Sets a tagging parameter 959f6568bcbSAnna Dabrowska * 960f6568bcbSAnna Dabrowska * @param string $name 961f6568bcbSAnna Dabrowska * @param string|array $value 962f6568bcbSAnna Dabrowska */ 963f6568bcbSAnna Dabrowska public function setParam($name, $value) 964f6568bcbSAnna Dabrowska { 965f6568bcbSAnna Dabrowska global $INPUT; 966f6568bcbSAnna Dabrowska $params = $this->getParams(); 967f6568bcbSAnna Dabrowska $params = array_merge($params, [$name => $value]); 968df43a7beSAndreas Gohr 969f6568bcbSAnna Dabrowska $INPUT->set('tagging', $params); 970f6568bcbSAnna Dabrowska } 971f6568bcbSAnna Dabrowska 972f6568bcbSAnna Dabrowska /** 973f6568bcbSAnna Dabrowska * Default sorting by tag id 974f6568bcbSAnna Dabrowska */ 975f6568bcbSAnna Dabrowska public function setDefaultSort() 976f6568bcbSAnna Dabrowska { 977f6568bcbSAnna Dabrowska if (!$this->getParam('sort')) { 978f6568bcbSAnna Dabrowska $this->setParam('sort', 'tid'); 979f6568bcbSAnna Dabrowska } 980f6568bcbSAnna Dabrowska } 981f6568bcbSAnna Dabrowska 982f6568bcbSAnna Dabrowska /** 9834a7da0a5SAnna Dabrowska * Executes the query and returns the results as array 9841b4b4fa9SAnna Dabrowska * 98599122157SAnna Dabrowska * @param array $query 9861b4b4fa9SAnna Dabrowska * @return array 9871b4b4fa9SAnna Dabrowska */ 9884a7da0a5SAnna Dabrowska protected function queryDb($query) 9891b4b4fa9SAnna Dabrowska { 9904a7da0a5SAnna Dabrowska $db = $this->getDB(); 9914a7da0a5SAnna Dabrowska if (!$db) { 9924a7da0a5SAnna Dabrowska return []; 993972f6adfSAnna Dabrowska } 9944a7da0a5SAnna Dabrowska 995a6b932a0SAnna Dabrowska $res = $db->queryAll($query[0], $query[1]); 9964a7da0a5SAnna Dabrowska 9974a7da0a5SAnna Dabrowska $ret = []; 9984a7da0a5SAnna Dabrowska foreach ($res as $row) { 9994a7da0a5SAnna Dabrowska $ret[$row['item']] = $row['cnt']; 10004a7da0a5SAnna Dabrowska } 10014a7da0a5SAnna Dabrowska return $ret; 1002972f6adfSAnna Dabrowska } 100340b94b1aSAnna Dabrowska 100440b94b1aSAnna Dabrowska /** 100540b94b1aSAnna Dabrowska * Construct the HAVING part of the search query 100640b94b1aSAnna Dabrowska * 100740b94b1aSAnna Dabrowska * @param array $filters 100840b94b1aSAnna Dabrowska * @return array 100940b94b1aSAnna Dabrowska */ 101040b94b1aSAnna Dabrowska protected function getFilterSql($filters) 101140b94b1aSAnna Dabrowska { 101240b94b1aSAnna Dabrowska $having = ''; 101340b94b1aSAnna Dabrowska $parts = []; 101440b94b1aSAnna Dabrowska $params = []; 101540b94b1aSAnna Dabrowska $filters = array_filter($filters); 1016df43a7beSAndreas Gohr if ($filters !== []) { 101740b94b1aSAnna Dabrowska $having = ' HAVING '; 101840b94b1aSAnna Dabrowska foreach ($filters as $filter => $value) { 101940b94b1aSAnna Dabrowska $parts[] = " $filter LIKE ? "; 102040b94b1aSAnna Dabrowska $params[] = "%$value%"; 102140b94b1aSAnna Dabrowska } 102240b94b1aSAnna Dabrowska $having .= implode(' AND ', $parts); 102340b94b1aSAnna Dabrowska } 102440b94b1aSAnna Dabrowska return [$having, $params]; 102540b94b1aSAnna Dabrowska } 1026ec4796e4SAnna Dabrowska 1027ec4796e4SAnna Dabrowska /** 1028ec4796e4SAnna Dabrowska * Returns taggings of nonexistent pages 1029ec4796e4SAnna Dabrowska * 1030ec4796e4SAnna Dabrowska * @return array 1031ec4796e4SAnna Dabrowska */ 1032ec4796e4SAnna Dabrowska protected function getInvalidTaggings() 1033ec4796e4SAnna Dabrowska { 1034ec4796e4SAnna Dabrowska $db = $this->getDB(); 1035ec4796e4SAnna Dabrowska $query = 'SELECT "pid", 1036ec4796e4SAnna Dabrowska GROUP_CONCAT(CLEANTAG("tag")) AS "tags" 1037ec4796e4SAnna Dabrowska FROM "taggings" 1038f24ac95eSAnna Dabrowska WHERE NOT PAGEEXISTS(pid) 1039ec4796e4SAnna Dabrowska GROUP BY pid 1040ec4796e4SAnna Dabrowska '; 1041a6b932a0SAnna Dabrowska 1042a6b932a0SAnna Dabrowska return $db->queryAll($query); 1043ec4796e4SAnna Dabrowska } 1044cd08599fSAnna Dabrowska} 1045