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; 7*55d5df8cSAnna Dabrowskause dokuwiki\File\PageResolver; 8ec4796e4SAnna Dabrowska 9aa627deeSAndreas Gohr/** 10aa627deeSAndreas Gohr * Tagging Plugin (hlper component) 11aa627deeSAndreas Gohr * 12aa627deeSAndreas Gohr * @license GPL 2 13aa627deeSAndreas Gohr */ 14df43a7beSAndreas Gohrclass helper_plugin_tagging extends Plugin 15df43a7beSAndreas Gohr{ 16289f50bdSAndreas Gohr /** 17b12334e1SAndreas Gohr * Gives access to the database 18b12334e1SAndreas Gohr * 19b12334e1SAndreas Gohr * Initializes the SQLite helper and register the CLEANTAG function 20b12334e1SAndreas Gohr * 21b12334e1SAndreas Gohr * @return helper_plugin_sqlite|bool false if initialization fails 22289f50bdSAndreas Gohr */ 23df43a7beSAndreas Gohr public function getDB() 24df43a7beSAndreas Gohr { 25302a38efSAndreas Gohr static $db = null; 26aa627deeSAndreas Gohr if ($db !== null) { 27f61105deSAdrian Lang return $db; 28f61105deSAdrian Lang } 29f61105deSAdrian Lang 30302a38efSAndreas Gohr /** @var helper_plugin_sqlite $db */ 31f61105deSAdrian Lang $db = plugin_load('helper', 'sqlite'); 32aa627deeSAndreas Gohr if ($db === null) { 33f61105deSAdrian Lang msg('The tagging plugin needs the sqlite plugin', -1); 34ca455b8eSMichael Große 35f61105deSAdrian Lang return false; 36f61105deSAdrian Lang } 37aa627deeSAndreas Gohr $db->init('tagging', __DIR__ . '/db/'); 38df43a7beSAndreas Gohr $db->create_function('CLEANTAG', [$this, 'cleanTag'], 1); 39df43a7beSAndreas Gohr $db->create_function( 40df43a7beSAndreas Gohr 'GROUP_SORT', 417e05e623SSzymon Olewniczak function ($group, $newDelimiter) { 4240b94b1aSAnna Dabrowska $ex = array_filter(explode(',', $group)); 437e05e623SSzymon Olewniczak sort($ex); 44ca455b8eSMichael Große 457e05e623SSzymon Olewniczak return implode($newDelimiter, $ex); 46df43a7beSAndreas Gohr }, 47df43a7beSAndreas Gohr 2 48df43a7beSAndreas Gohr ); 4940b94b1aSAnna Dabrowska $db->create_function('GET_NS', 'getNS', 1); 50ca455b8eSMichael Große 51f61105deSAdrian Lang return $db; 52f61105deSAdrian Lang } 53f61105deSAdrian Lang 54302a38efSAndreas Gohr /** 552ace74f4SAndreas Gohr * Return the user to use for accessing tags 562ace74f4SAndreas Gohr * 572ace74f4SAndreas Gohr * Handles the singleuser mode by returning 'auto' as user. Returnes false when no user is logged in. 582ace74f4SAndreas Gohr * 592ace74f4SAndreas Gohr * @return bool|string 602ace74f4SAndreas Gohr */ 61df43a7beSAndreas Gohr public function getUser() 62df43a7beSAndreas Gohr { 630cfde7e9SMichael Große if (!isset($_SERVER['REMOTE_USER'])) { 640cfde7e9SMichael Große return false; 650cfde7e9SMichael Große } 660cfde7e9SMichael Große if ($this->getConf('singleusermode')) { 670cfde7e9SMichael Große return 'auto'; 680cfde7e9SMichael Große } 69ca455b8eSMichael Große 702ace74f4SAndreas Gohr return $_SERVER['REMOTE_USER']; 712ace74f4SAndreas Gohr } 722ace74f4SAndreas Gohr 732ace74f4SAndreas Gohr /** 74e4443e5cSAnna Dabrowska * If plugin elasticsearch is installed, inform it that we have just made changes 75e4443e5cSAnna Dabrowska * to some data relevant to a page. The page should be re-indexed. 76e4443e5cSAnna Dabrowska * 77e4443e5cSAnna Dabrowska * @param string $id 78e4443e5cSAnna Dabrowska */ 79e4443e5cSAnna Dabrowska public function updateElasticState($id) 80e4443e5cSAnna Dabrowska { 81e4443e5cSAnna Dabrowska /** @var \helper_plugin_elasticsearch_plugins $elasticHelper */ 82e4443e5cSAnna Dabrowska $elasticHelper = plugin_load('helper', 'elasticsearch_plugins'); 83e4443e5cSAnna Dabrowska if ($elasticHelper) { 84e4443e5cSAnna Dabrowska $elasticHelper->updateRefreshState($id); 85e4443e5cSAnna Dabrowska } 86e4443e5cSAnna Dabrowska } 87e4443e5cSAnna Dabrowska 88e4443e5cSAnna Dabrowska /** 89*55d5df8cSAnna Dabrowska * Resolve the ns filter 90*55d5df8cSAnna Dabrowska * 91*55d5df8cSAnna Dabrowska * @param array $data 92*55d5df8cSAnna Dabrowska * @return string 93*55d5df8cSAnna Dabrowska */ 94*55d5df8cSAnna Dabrowska public function resolveNs(array $data) 95*55d5df8cSAnna Dabrowska { 96*55d5df8cSAnna Dabrowska if (!isset($data['ns'])) { 97*55d5df8cSAnna Dabrowska $data['ns'] = '.'; 98*55d5df8cSAnna Dabrowska } 99*55d5df8cSAnna Dabrowska if ($data['ns'] === '*') { 100*55d5df8cSAnna Dabrowska $data['ns'] = ''; 101*55d5df8cSAnna Dabrowska } 102*55d5df8cSAnna Dabrowska 103*55d5df8cSAnna Dabrowska global $ID; 104*55d5df8cSAnna Dabrowska $resolver = new PageResolver($ID); 105*55d5df8cSAnna Dabrowska $data['ns'] = getNS($resolver->resolveId($data['ns'] . ':fakeIdNotResolvable')) ?: ':'; 106*55d5df8cSAnna Dabrowska 107*55d5df8cSAnna Dabrowska return $data['ns']; 108*55d5df8cSAnna Dabrowska } 109*55d5df8cSAnna Dabrowska 110*55d5df8cSAnna Dabrowska /** 111302a38efSAndreas Gohr * Canonicalizes the tag to its lower case nospace form 112302a38efSAndreas Gohr * 113302a38efSAndreas Gohr * @param $tag 1140cfde7e9SMichael Große * 115302a38efSAndreas Gohr * @return string 116302a38efSAndreas Gohr */ 117df43a7beSAndreas Gohr public function cleanTag($tag) 118df43a7beSAndreas Gohr { 119df43a7beSAndreas Gohr $tag = str_replace([' ', '-', '_', '#'], '', $tag); 120df43a7beSAndreas Gohr return PhpString::strtolower($tag); 121302a38efSAndreas Gohr } 122302a38efSAndreas Gohr 12356d82720SAndreas Gohr /** 12431396860SSzymon Olewniczak * Canonicalizes the namespace, remove the first colon and add glob 12531396860SSzymon Olewniczak * 12631396860SSzymon Olewniczak * @param $namespace 12731396860SSzymon Olewniczak * 12831396860SSzymon Olewniczak * @return string 12931396860SSzymon Olewniczak */ 130df43a7beSAndreas Gohr public function globNamespace($namespace) 131df43a7beSAndreas Gohr { 132de379874SAnna Dabrowska return cleanId($namespace) . '*'; 13331396860SSzymon Olewniczak } 13431396860SSzymon Olewniczak 13531396860SSzymon Olewniczak /** 13656d82720SAndreas Gohr * Create or Update tags of a page 13756d82720SAndreas Gohr * 13856d82720SAndreas Gohr * Uses the translation plugin to store the language of a page (if available) 13956d82720SAndreas Gohr * 14056d82720SAndreas Gohr * @param string $id The page ID 14156d82720SAndreas Gohr * @param string $user 14256d82720SAndreas Gohr * @param array $tags 1430cfde7e9SMichael Große * 14456d82720SAndreas Gohr * @return bool|SQLiteResult 14556d82720SAndreas Gohr */ 146df43a7beSAndreas Gohr public function replaceTags($id, $user, $tags) 147df43a7beSAndreas Gohr { 14856d82720SAndreas Gohr global $conf; 14956d82720SAndreas Gohr /** @var helper_plugin_translation $trans */ 15056d82720SAndreas Gohr $trans = plugin_load('helper', 'translation'); 15156d82720SAndreas Gohr if ($trans) { 15256d82720SAndreas Gohr $lang = $trans->realLC($trans->getLangPart($id)); 15356d82720SAndreas Gohr } else { 15456d82720SAndreas Gohr $lang = $conf['lang']; 15556d82720SAndreas Gohr } 15656d82720SAndreas Gohr 157f61105deSAdrian Lang $db = $this->getDB(); 158f61105deSAdrian Lang $db->query('BEGIN TRANSACTION'); 159df43a7beSAndreas Gohr 160df43a7beSAndreas Gohr $queries = [['DELETE FROM taggings WHERE pid = ? AND tagger = ?', $id, $user]]; 161f61105deSAdrian Lang foreach ($tags as $tag) { 162df43a7beSAndreas Gohr $queries[] = ['INSERT INTO taggings (pid, tagger, tag, lang) VALUES(?, ?, ?, ?)', $id, $user, $tag, $lang]; 163f61105deSAdrian Lang } 164f61105deSAdrian Lang 165f61105deSAdrian Lang foreach ($queries as $query) { 166df43a7beSAndreas Gohr if (!call_user_func_array([$db, 'query'], $query)) { 167f61105deSAdrian Lang $db->query('ROLLBACK TRANSACTION'); 168ca455b8eSMichael Große 169f61105deSAdrian Lang return false; 170f61105deSAdrian Lang } 171f61105deSAdrian Lang } 172ca455b8eSMichael Große 173f61105deSAdrian Lang return $db->query('COMMIT TRANSACTION'); 174f61105deSAdrian Lang } 175f61105deSAdrian Lang 1760a518a11SAndreas Gohr /** 177b12334e1SAndreas Gohr * Get a list of Tags or Pages matching search criteria 1780a518a11SAndreas Gohr * 179b12334e1SAndreas Gohr * @param array $filter What to search for array('field' => 'searchterm') 180b12334e1SAndreas Gohr * @param string $type What field to return 'tag'|'pid' 181077ff864SAndreas Gohr * @param int $limit Limit to this many results, 0 for all 1820cfde7e9SMichael Große * 1830a518a11SAndreas Gohr * @return array associative array in form of value => count 1840a518a11SAndreas Gohr */ 185df43a7beSAndreas Gohr public function findItems($filter, $type, $limit = 0) 186df43a7beSAndreas Gohr { 187df43a7beSAndreas Gohr $queryBuilder = new helper_plugin_tagging_querybuilder(); 1881b4b4fa9SAnna Dabrowska 1894a7da0a5SAnna Dabrowska $queryBuilder->setField($type); 1904a7da0a5SAnna Dabrowska $queryBuilder->setLimit($limit); 191739c5360SAnna Dabrowska $queryBuilder->setTags($this->extractFromQuery($filter)); 1924a7da0a5SAnna Dabrowska if (isset($filter['ns'])) $queryBuilder->includeNS($filter['ns']); 1934a7da0a5SAnna Dabrowska if (isset($filter['notns'])) $queryBuilder->excludeNS($filter['notns']); 1944a7da0a5SAnna Dabrowska if (isset($filter['tagger'])) $queryBuilder->setTagger($filter['tagger']); 1954a7da0a5SAnna Dabrowska if (isset($filter['pid'])) $queryBuilder->setPid($filter['pid']); 196b12334e1SAndreas Gohr 1974a7da0a5SAnna Dabrowska return $this->queryDb($queryBuilder->getQuery()); 198f61105deSAdrian Lang } 199f61105deSAdrian Lang 200b12334e1SAndreas Gohr /** 201302a38efSAndreas Gohr * Constructs the URL to search for a tag 202302a38efSAndreas Gohr * 2035540f91dSAndreas Gohr * @param string $tag 2045540f91dSAndreas Gohr * @param string $ns 2050cfde7e9SMichael Große * 206302a38efSAndreas Gohr * @return string 207302a38efSAndreas Gohr */ 208df43a7beSAndreas Gohr public function getTagSearchURL($tag, $ns = '') 209df43a7beSAndreas Gohr { 210a99fe09cSAnna Dabrowska $ret = '?do=search&sf=1&q=' . rawurlencode('#' . $this->cleanTag($tag)); 211*55d5df8cSAnna Dabrowska if ($ns && $ns !== ':') { 2120cfde7e9SMichael Große $ret .= rawurlencode(' @' . $ns); 2130cfde7e9SMichael Große } 2145540f91dSAndreas Gohr 2155540f91dSAndreas Gohr return $ret; 216f61105deSAdrian Lang } 217f61105deSAdrian Lang 2185540f91dSAndreas Gohr /** 2195540f91dSAndreas Gohr * Calculates the size levels for the given list of clouds 2205540f91dSAndreas Gohr * 2215540f91dSAndreas Gohr * Automatically determines sensible tresholds 2225540f91dSAndreas Gohr * 2235540f91dSAndreas Gohr * @param array $tags list of tags => count 2245540f91dSAndreas Gohr * @param int $levels 2250cfde7e9SMichael Große * 226df43a7beSAndreas Gohr * @return array 2275540f91dSAndreas Gohr */ 228df43a7beSAndreas Gohr public function cloudData($tags, $levels = 10) 229df43a7beSAndreas Gohr { 230f61105deSAdrian Lang $min = min($tags); 231f61105deSAdrian Lang $max = max($tags); 232f61105deSAdrian Lang 233f61105deSAdrian Lang // calculate tresholds 234df43a7beSAndreas Gohr $tresholds = []; 235f61105deSAdrian Lang for ($i = 0; $i <= $levels; $i++) { 236df43a7beSAndreas Gohr $tresholds[$i] = ($max - $min + 1) ** ($i / $levels) + $min - 1; 237f61105deSAdrian Lang } 238f61105deSAdrian Lang 239f61105deSAdrian Lang // assign weights 240f61105deSAdrian Lang foreach ($tags as $tag => $cnt) { 241f61105deSAdrian Lang foreach ($tresholds as $tresh => $val) { 242f61105deSAdrian Lang if ($cnt <= $val) { 243f61105deSAdrian Lang $tags[$tag] = $tresh; 244f61105deSAdrian Lang break; 245f61105deSAdrian Lang } 246f61105deSAdrian Lang $tags[$tag] = $levels; 247f61105deSAdrian Lang } 248f61105deSAdrian Lang } 249ca455b8eSMichael Große 250f61105deSAdrian Lang return $tags; 251f61105deSAdrian Lang } 252f61105deSAdrian Lang 2535540f91dSAndreas Gohr /** 2545540f91dSAndreas Gohr * Display a tag cloud 2555540f91dSAndreas Gohr * 2565540f91dSAndreas Gohr * @param array $tags list of tags => count 2575540f91dSAndreas Gohr * @param string $type 'tag' 2585540f91dSAndreas Gohr * @param Callable $func The function to print the link (gets tag and ns) 2595540f91dSAndreas Gohr * @param bool $wrap wrap cloud in UL tags? 2605540f91dSAndreas Gohr * @param bool $return returnn HTML instead of printing? 2615540f91dSAndreas Gohr * @param string $ns Add this namespace to search links 2620cfde7e9SMichael Große * 2635540f91dSAndreas Gohr * @return string 2645540f91dSAndreas Gohr */ 265df43a7beSAndreas Gohr public function html_cloud($tags, $type, $func, $wrap = true, $return = false, $ns = '') 266df43a7beSAndreas Gohr { 267a66f6715SAndreas Gohr global $INFO; 268a66f6715SAndreas Gohr 269a66f6715SAndreas Gohr $hidden_str = $this->getConf('hiddenprefix'); 270a66f6715SAndreas Gohr $hidden_len = strlen($hidden_str); 271a66f6715SAndreas Gohr 272f61105deSAdrian Lang $ret = ''; 2730cfde7e9SMichael Große if ($wrap) { 2740cfde7e9SMichael Große $ret .= '<ul class="tagging_cloud clearfix">'; 2750cfde7e9SMichael Große } 276f61105deSAdrian Lang if (count($tags) === 0) { 277f61105deSAdrian Lang // Produce valid XHTML (ul needs a child) 278f61105deSAdrian Lang $this->setupLocale(); 279f61105deSAdrian Lang $ret .= '<li><div class="li">' . $this->lang['js']['no' . $type . 's'] . '</div></li>'; 280f61105deSAdrian Lang } else { 281f61105deSAdrian Lang $tags = $this->cloudData($tags); 282f61105deSAdrian Lang foreach ($tags as $val => $size) { 283a66f6715SAndreas Gohr // skip hidden tags for users that can't edit 284df43a7beSAndreas Gohr if ( 285df43a7beSAndreas Gohr $type === 'tag' && 286df43a7beSAndreas Gohr $hidden_len && 287df43a7beSAndreas Gohr substr($val, 0, $hidden_len) == $hidden_str && 288a66f6715SAndreas Gohr !($this->getUser() && $INFO['writable']) 289a66f6715SAndreas Gohr ) { 290a66f6715SAndreas Gohr continue; 291a66f6715SAndreas Gohr } 292a66f6715SAndreas Gohr 293f61105deSAdrian Lang $ret .= '<li class="t' . $size . '"><div class="li">'; 2945540f91dSAndreas Gohr $ret .= call_user_func($func, $val, $ns); 295f61105deSAdrian Lang $ret .= '</div></li>'; 296f61105deSAdrian Lang } 297f61105deSAdrian Lang } 2980cfde7e9SMichael Große if ($wrap) { 2990cfde7e9SMichael Große $ret .= '</ul>'; 3000cfde7e9SMichael Große } 3010cfde7e9SMichael Große if ($return) { 3020cfde7e9SMichael Große return $ret; 3030cfde7e9SMichael Große } 304f61105deSAdrian Lang echo $ret; 305ca455b8eSMichael Große 3065540f91dSAndreas Gohr return ''; 307f61105deSAdrian Lang } 308f61105deSAdrian Lang 3095540f91dSAndreas Gohr /** 3100b6fad27Ssandos187 * Display a List of Page Links 3110b6fad27Ssandos187 * 3120b6fad27Ssandos187 * @param array $pids list of pids => count 3130b6fad27Ssandos187 * @return string 3140b6fad27Ssandos187 */ 315df43a7beSAndreas Gohr public function html_page_list($pids) 316df43a7beSAndreas Gohr { 3170b6fad27Ssandos187 $ret = '<div class="search_quickresult">'; 3180b6fad27Ssandos187 $ret .= '<ul class="search_quickhits">'; 3190b6fad27Ssandos187 3200b6fad27Ssandos187 if (count($pids) === 0) { 3210b6fad27Ssandos187 // Produce valid XHTML (ul needs a child) 3220b6fad27Ssandos187 $ret .= '<li><div class="li">' . $this->lang['js']['nopages'] . '</div></li>'; 3230b6fad27Ssandos187 } else { 324bdf1ecf0SAnna Dabrowska foreach (array_keys($pids) as $val) { 3250b6fad27Ssandos187 $ret .= '<li><div class="li">'; 326db3ab356SAnna Dabrowska $ret .= html_wikilink(":$val"); 3270b6fad27Ssandos187 $ret .= '</div></li>'; 3280b6fad27Ssandos187 } 3290b6fad27Ssandos187 } 3300b6fad27Ssandos187 3310b6fad27Ssandos187 $ret .= '</ul>'; 3320b6fad27Ssandos187 $ret .= '</div>'; 3330b6fad27Ssandos187 $ret .= '<div class="clearer"></div>'; 3340b6fad27Ssandos187 3350b6fad27Ssandos187 return $ret; 3360b6fad27Ssandos187 } 3370b6fad27Ssandos187 3380b6fad27Ssandos187 /** 3395540f91dSAndreas Gohr * Get the link to a search for the given tag 3405540f91dSAndreas Gohr * 3415540f91dSAndreas Gohr * @param string $tag search for this tag 3425540f91dSAndreas Gohr * @param string $ns limit search to this namespace 3430cfde7e9SMichael Große * 3445540f91dSAndreas Gohr * @return string 3455540f91dSAndreas Gohr */ 346df43a7beSAndreas Gohr protected function linkToSearch($tag, $ns = '') 347df43a7beSAndreas Gohr { 3485540f91dSAndreas Gohr return '<a href="' . hsc($this->getTagSearchURL($tag, $ns)) . '">' . $tag . '</a>'; 349f61105deSAdrian Lang } 350f61105deSAdrian Lang 351fb1d0583SAndreas Gohr /** 352fb1d0583SAndreas Gohr * Display the Tags for the current page and prepare the tag editing form 3533496cc8aSAndreas Gohr * 3543496cc8aSAndreas Gohr * @param bool $print Should the HTML be printed or returned? 3550cfde7e9SMichael Große * 3563496cc8aSAndreas Gohr * @return string 357fb1d0583SAndreas Gohr */ 358df43a7beSAndreas Gohr public function tpl_tags($print = true) 359df43a7beSAndreas Gohr { 360f61105deSAdrian Lang global $INFO; 361f61105deSAdrian Lang global $lang; 3623bf0e2f1SMichael Große 363df43a7beSAndreas Gohr $filter = ['pid' => $INFO['id']]; 3643bf0e2f1SMichael Große if ($this->getConf('singleusermode')) { 3653bf0e2f1SMichael Große $filter['tagger'] = 'auto'; 3663bf0e2f1SMichael Große } 3673bf0e2f1SMichael Große 3683bf0e2f1SMichael Große $tags = $this->findItems($filter, 'tag'); 3693496cc8aSAndreas Gohr 370df43a7beSAndreas Gohr $ret = '<div class="plugin_tagging_edit">'; 371df43a7beSAndreas Gohr $ret .= $this->html_cloud($tags, 'tag', [$this, 'linkToSearch'], true, true); 372f61105deSAdrian Lang 3732ace74f4SAndreas Gohr if ($this->getUser() && $INFO['writable']) { 374f61105deSAdrian Lang $lang['btn_tagging_edit'] = $lang['btn_secedit']; 375e5b42768SSzymon Olewniczak $ret .= '<div id="tagging__edit_buttons_group">'; 376df43a7beSAndreas Gohr $ret .= html_btn('tagging_edit', $INFO['id'], '', []); 377dd52fd45SSzymon Olewniczak if (auth_isadmin()) { 37826f61833SAnna Dabrowska $ret .= '<label>' 37926f61833SAnna Dabrowska . $this->getLang('toggle admin mode') 38026f61833SAnna Dabrowska . '<input type="checkbox" id="tagging__edit_toggle_admin" /></label>'; 381dd52fd45SSzymon Olewniczak } 382e5b42768SSzymon Olewniczak $ret .= '</div>'; 383df43a7beSAndreas Gohr $form = new Form(); 3842819ffcdSSzymon Olewniczak $form->id('tagging__edit'); 3852819ffcdSSzymon Olewniczak $form->setHiddenField('tagging[id]', $INFO['id']); 3862819ffcdSSzymon Olewniczak $form->setHiddenField('call', 'plugin_tagging_save'); 387df43a7beSAndreas Gohr $tags = $this->findItems(['pid' => $INFO['id'], 'tagger' => $this->getUser()], 'tag'); 38826f61833SAnna Dabrowska $form->addTextarea('tagging[tags]') 38926f61833SAnna Dabrowska ->val(implode(', ', array_keys($tags))) 39026f61833SAnna Dabrowska ->addClass('edit') 39126f61833SAnna Dabrowska ->attr('rows', 4); 392cf52ec2dSSzymon Olewniczak $form->addButton('', $lang['btn_save'])->id('tagging__edit_save'); 393cf52ec2dSSzymon Olewniczak $form->addButton('', $lang['btn_cancel'])->id('tagging__edit_cancel'); 3942819ffcdSSzymon Olewniczak $ret .= $form->toHTML(); 395f61105deSAdrian Lang } 3963496cc8aSAndreas Gohr $ret .= '</div>'; 3973496cc8aSAndreas Gohr 3980cfde7e9SMichael Große if ($print) { 3990cfde7e9SMichael Große echo $ret; 4000cfde7e9SMichael Große } 401ca455b8eSMichael Große 4023496cc8aSAndreas Gohr return $ret; 403f61105deSAdrian Lang } 404872edc7cSRené Corinth 4058a1a3846SAndreas Gohr /** 406a99b66c1SSzymon Olewniczak * @param string $namespace empty for entire wiki 407a99b66c1SSzymon Olewniczak * 40840b94b1aSAnna Dabrowska * @param string $order_by 40940b94b1aSAnna Dabrowska * @param bool $desc 41040b94b1aSAnna Dabrowska * @param array $filters 4118a1a3846SAndreas Gohr * @return array 4128a1a3846SAndreas Gohr */ 413df43a7beSAndreas Gohr public function getAllTags($namespace = '', $order_by = 'tid', $desc = false, $filters = []) 414df43a7beSAndreas Gohr { 415df43a7beSAndreas Gohr $order_fields = ['pid', 'tid', 'taggers', 'ns', 'count']; 416f0084ee1SSzymon Olewniczak if (!in_array($order_by, $order_fields)) { 417f0084ee1SSzymon Olewniczak msg('cannot sort by ' . $order_by . ' field does not exists', -1); 418f0084ee1SSzymon Olewniczak $order_by = 'tag'; 419f0084ee1SSzymon Olewniczak } 420872edc7cSRené Corinth 421df43a7beSAndreas Gohr [$having, $params] = $this->getFilterSql($filters); 42240b94b1aSAnna Dabrowska 423a2246f69SAnna Dabrowska $db = $this->getDB(); 424872edc7cSRené Corinth 425f0084ee1SSzymon Olewniczak $query = 'SELECT "pid", 426ca455b8eSMichael Große CLEANTAG("tag") AS "tid", 427f0084ee1SSzymon Olewniczak GROUP_SORT(GROUP_CONCAT("tagger"), \', \') AS "taggers", 42840b94b1aSAnna Dabrowska GROUP_SORT(GROUP_CONCAT(GET_NS("pid")), \', \') AS "ns", 42989ed97adSAnna Dabrowska GROUP_SORT(GROUP_CONCAT("pid"), \', \') AS "pids", 430193a767dSSzymon Olewniczak COUNT(*) AS "count" 43157e45304SSzymon Olewniczak FROM "taggings" 4324227fca4SAnna Dabrowska WHERE "pid" GLOB ? AND GETACCESSLEVEL(pid) >= ' . AUTH_READ 4334227fca4SAnna Dabrowska . ' GROUP BY "tid"'; 43440b94b1aSAnna Dabrowska $query .= $having; 43540b94b1aSAnna Dabrowska $query .= 'ORDER BY ' . $order_by; 436ca455b8eSMichael Große if ($desc) { 437ca455b8eSMichael Große $query .= ' DESC'; 438ca455b8eSMichael Große } 439cb469644SSzymon Olewniczak 44040b94b1aSAnna Dabrowska array_unshift($params, $this->globNamespace($namespace)); 44140b94b1aSAnna Dabrowska $res = $db->query($query, $params); 442872edc7cSRené Corinth 4437e05e623SSzymon Olewniczak return $db->res2arr($res); 444872edc7cSRené Corinth } 445872edc7cSRené Corinth 4468a1a3846SAndreas Gohr /** 44772431326SMichael Große * Get all pages with tags and their tags 44872431326SMichael Große * 449790ca788SAndreas Gohr * @return array ['pid' => ['tag1','tag2','tag3']] 45072431326SMichael Große */ 451df43a7beSAndreas Gohr public function getAllTagsByPage() 452df43a7beSAndreas Gohr { 45372431326SMichael Große $query = ' 45472431326SMichael Große SELECT pid, GROUP_CONCAT(tag) AS tags 45572431326SMichael Große FROM taggings 45672431326SMichael Große GROUP BY pid 45772431326SMichael Große '; 45872431326SMichael Große $db = $this->getDb(); 45972431326SMichael Große $res = $db->query($query); 460790ca788SAndreas Gohr return array_map( 461df43a7beSAndreas Gohr fn($i) => explode(',', $i), 462790ca788SAndreas Gohr array_column($db->res2arr($res), 'tags', 'pid') 463790ca788SAndreas Gohr ); 46472431326SMichael Große } 46572431326SMichael Große 46672431326SMichael Große /** 4678a1a3846SAndreas Gohr * Renames a tag 4688a1a3846SAndreas Gohr * 4698a1a3846SAndreas Gohr * @param string $formerTagName 4704227fca4SAnna Dabrowska * @param string $newTagNames 4718a1a3846SAndreas Gohr */ 472df43a7beSAndreas Gohr public function renameTag($formerTagName, $newTagNames) 473df43a7beSAndreas Gohr { 474872edc7cSRené Corinth 4754227fca4SAnna Dabrowska if (empty($formerTagName) || empty($newTagNames)) { 4768a1a3846SAndreas Gohr msg($this->getLang("admin enter tag names"), -1); 4778a1a3846SAndreas Gohr return; 478872edc7cSRené Corinth } 479872edc7cSRené Corinth 480870d77ddSAnna Dabrowska $keepFormerTag = false; 481870d77ddSAnna Dabrowska 4824227fca4SAnna Dabrowska // enable splitting tags on rename 483df43a7beSAndreas Gohr $newTagNames = array_map(fn($tag) => $this->cleanTag($tag), explode(',', $newTagNames)); 4844227fca4SAnna Dabrowska 4854227fca4SAnna Dabrowska $db = $this->getDB(); 486872edc7cSRené Corinth 4874227fca4SAnna Dabrowska // non-admins can rename only their own tags 4884227fca4SAnna Dabrowska if (!auth_isadmin()) { 4894227fca4SAnna Dabrowska $queryTagger = ' AND tagger = ?'; 4904227fca4SAnna Dabrowska $tagger = $this->getUser(); 4914227fca4SAnna Dabrowska } else { 4924227fca4SAnna Dabrowska $queryTagger = ''; 4934227fca4SAnna Dabrowska $tagger = ''; 4944227fca4SAnna Dabrowska } 4954227fca4SAnna Dabrowska 4960ec63874SAnna Dabrowska $insertQuery = 'INSERT INTO taggings '; 4970ec63874SAnna Dabrowska $insertQuery .= 'SELECT pid, ?, tagger, lang FROM taggings'; 4980ec63874SAnna Dabrowska $where = ' WHERE CLEANTAG(tag) = ?'; 4990ec63874SAnna Dabrowska $where .= ' AND GETACCESSLEVEL(pid) >= ' . AUTH_EDIT; 5000ec63874SAnna Dabrowska $where .= $queryTagger; 5010ec63874SAnna Dabrowska 5020ec63874SAnna Dabrowska $db->query('BEGIN TRANSACTION'); 5030ec63874SAnna Dabrowska 5040ec63874SAnna Dabrowska // insert new tags first 5050ec63874SAnna Dabrowska foreach ($newTagNames as $newTag) { 506870d77ddSAnna Dabrowska if ($newTag === $this->cleanTag($formerTagName)) { 507870d77ddSAnna Dabrowska $keepFormerTag = true; 508870d77ddSAnna Dabrowska continue; 509870d77ddSAnna Dabrowska } 510870d77ddSAnna Dabrowska $params = [$newTag, $this->cleanTag($formerTagName)]; 511df43a7beSAndreas Gohr if ($tagger) $params[] = $tagger; 5120ec63874SAnna Dabrowska $res = $db->query($insertQuery . $where, $params); 5130ec63874SAnna Dabrowska if ($res === false) { 5140ec63874SAnna Dabrowska $db->query('ROLLBACK TRANSACTION'); 5150ec63874SAnna Dabrowska return; 5164227fca4SAnna Dabrowska } 5170ec63874SAnna Dabrowska $db->res_close($res); 5180ec63874SAnna Dabrowska } 5190ec63874SAnna Dabrowska 520870d77ddSAnna Dabrowska // finally delete the renamed tags 521870d77ddSAnna Dabrowska if (!$keepFormerTag) { 5220ec63874SAnna Dabrowska $deleteQuery = 'DELETE FROM taggings'; 5230ec63874SAnna Dabrowska $params = [$this->cleanTag($formerTagName)]; 524df43a7beSAndreas Gohr if ($tagger) $params[] = $tagger; 5250ec63874SAnna Dabrowska if ($db->query($deleteQuery . $where, $params) === false) { 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 { 547dd52fd45SSzymon Olewniczak 548dd52fd45SSzymon Olewniczak $db = $this->getDb(); 549dd52fd45SSzymon Olewniczak 55026f61833SAnna Dabrowska $res = $db->query( 55126f61833SAnna Dabrowska 'SELECT pid FROM taggings WHERE CLEANTAG(tag) = ? AND pid = ?', 55226f61833SAnna Dabrowska $this->cleanTag($formerTagName), 55326f61833SAnna Dabrowska $pid 55426f61833SAnna Dabrowska ); 555dd52fd45SSzymon Olewniczak $check = $db->res2arr($res); 556dd52fd45SSzymon Olewniczak 557dd52fd45SSzymon Olewniczak if (empty($check)) { 558df43a7beSAndreas Gohr return [true, $this->getLang('admin tag does not exists')]; 559dd52fd45SSzymon Olewniczak } 560dd52fd45SSzymon Olewniczak 561dd52fd45SSzymon Olewniczak if (empty($newTagName)) { 56226f61833SAnna Dabrowska $res = $db->query( 56326f61833SAnna Dabrowska 'DELETE FROM taggings WHERE pid = ? AND CLEANTAG(tag) = ?', 56426f61833SAnna Dabrowska $pid, 56526f61833SAnna Dabrowska $this->cleanTag($formerTagName) 56626f61833SAnna Dabrowska ); 567dd52fd45SSzymon Olewniczak } else { 56826f61833SAnna Dabrowska $res = $db->query( 56926f61833SAnna Dabrowska 'UPDATE taggings SET tag = ? WHERE pid = ? AND CLEANTAG(tag) = ?', 57026f61833SAnna Dabrowska $newTagName, 57126f61833SAnna Dabrowska $pid, 57226f61833SAnna Dabrowska $this->cleanTag($formerTagName) 57326f61833SAnna Dabrowska ); 574dd52fd45SSzymon Olewniczak } 575dd52fd45SSzymon Olewniczak $db->res2arr($res); 576dd52fd45SSzymon Olewniczak 577df43a7beSAndreas Gohr return [false, $this->getLang('admin renamed')]; 578dd52fd45SSzymon Olewniczak } 579dd52fd45SSzymon Olewniczak 580dd52fd45SSzymon Olewniczak /** 5818f630140SSzymon Olewniczak * Deletes a tag 5828f630140SSzymon Olewniczak * 5838f630140SSzymon Olewniczak * @param array $tags 58431396860SSzymon Olewniczak * @param string $namespace current namespace context as in getAllTags() 5858f630140SSzymon Olewniczak */ 586df43a7beSAndreas Gohr public function deleteTags($tags, $namespace = '') 587df43a7beSAndreas Gohr { 588ca455b8eSMichael Große if (empty($tags)) { 589ca455b8eSMichael Große return; 590ca455b8eSMichael Große } 5918f630140SSzymon Olewniczak 59231396860SSzymon Olewniczak $namespace = cleanId($namespace); 59331396860SSzymon Olewniczak 5941f5991a7SMichael Große $db = $this->getDB(); 5958f630140SSzymon Olewniczak 596de379874SAnna Dabrowska $queryBody = 'FROM taggings WHERE pid GLOB ? AND (' . 59731396860SSzymon Olewniczak implode(' OR ', array_fill(0, count($tags), 'CLEANTAG(tag) = ?')) . ')'; 598df43a7beSAndreas Gohr $args = array_map([$this, 'cleanTag'], $tags); 59931396860SSzymon Olewniczak array_unshift($args, $this->globNamespace($namespace)); 6008f630140SSzymon Olewniczak 6014227fca4SAnna Dabrowska // non-admins can delete only their own tags 6024227fca4SAnna Dabrowska if (!auth_isadmin()) { 6034227fca4SAnna Dabrowska $queryBody .= ' AND tagger = ?'; 604df43a7beSAndreas Gohr $args[] = $this->getUser(); 6054227fca4SAnna Dabrowska } 606ca455b8eSMichael Große 6071f5991a7SMichael Große $affectedPagesQuery = 'SELECT DISTINCT pid ' . $queryBody; 6081f5991a7SMichael Große $resAffectedPages = $db->query($affectedPagesQuery, $args); 6091f5991a7SMichael Große $numAffectedPages = count($resAffectedPages->fetchAll()); 6101f5991a7SMichael Große 6111f5991a7SMichael Große $deleteQuery = 'DELETE ' . $queryBody; 6121f5991a7SMichael Große $db->query($deleteQuery, $args); 6131f5991a7SMichael Große 6141f5991a7SMichael Große msg(sprintf($this->getLang("admin deleted"), count($tags), $numAffectedPages), 1); 6158f630140SSzymon Olewniczak } 616cd08599fSAnna Dabrowska 617cd08599fSAnna Dabrowska /** 618ec4796e4SAnna Dabrowska * Delete taggings of nonexistent pages 619ec4796e4SAnna Dabrowska */ 620ec4796e4SAnna Dabrowska public function deleteInvalidTaggings() 621ec4796e4SAnna Dabrowska { 622ec4796e4SAnna Dabrowska $db = $this->getDB(); 623ec4796e4SAnna Dabrowska $query = 'DELETE FROM "taggings" 6241815f49fSAnna Dabrowska WHERE NOT PAGEEXISTS(pid) 625ec4796e4SAnna Dabrowska '; 626ec4796e4SAnna Dabrowska $res = $db->query($query); 627ec4796e4SAnna Dabrowska $db->res_close($res); 628ec4796e4SAnna Dabrowska } 629ec4796e4SAnna Dabrowska 630ec4796e4SAnna Dabrowska /** 631cd08599fSAnna Dabrowska * Updates tags with a new page name 632cd08599fSAnna Dabrowska * 633cd08599fSAnna Dabrowska * @param string $oldName 634cd08599fSAnna Dabrowska * @param string $newName 635cd08599fSAnna Dabrowska */ 636df43a7beSAndreas Gohr public function renamePage($oldName, $newName) 637df43a7beSAndreas Gohr { 638f6568bcbSAnna Dabrowska $db = $this->getDB(); 639cd08599fSAnna Dabrowska $db->query('UPDATE taggings SET pid = ? WHERE pid = ?', $newName, $oldName); 640cd08599fSAnna Dabrowska } 641972f6adfSAnna Dabrowska 642972f6adfSAnna Dabrowska /** 6431b4b4fa9SAnna Dabrowska * Extracts tags from search query 6441b4b4fa9SAnna Dabrowska * 6451b4b4fa9SAnna Dabrowska * @param array $parsedQuery 6461b4b4fa9SAnna Dabrowska * @return array 6471b4b4fa9SAnna Dabrowska */ 648739c5360SAnna Dabrowska public function extractFromQuery($parsedQuery) 6491b4b4fa9SAnna Dabrowska { 6501b4b4fa9SAnna Dabrowska $tags = []; 6511b4b4fa9SAnna Dabrowska if (isset($parsedQuery['phrases'][0])) { 6521b4b4fa9SAnna Dabrowska $tags = $parsedQuery['phrases']; 6531b4b4fa9SAnna Dabrowska } elseif (isset($parsedQuery['and'][0])) { 6541b4b4fa9SAnna Dabrowska $tags = $parsedQuery['and']; 6551b4b4fa9SAnna Dabrowska } elseif (isset($parsedQuery['tag'])) { 6561b4b4fa9SAnna Dabrowska // handle autocomplete call 6571b4b4fa9SAnna Dabrowska $tags[] = $parsedQuery['tag']; 6581b4b4fa9SAnna Dabrowska } 6591b4b4fa9SAnna Dabrowska return $tags; 6601b4b4fa9SAnna Dabrowska } 6611b4b4fa9SAnna Dabrowska 6621b4b4fa9SAnna Dabrowska /** 6634a7da0a5SAnna Dabrowska * Search for tagged pages 664972f6adfSAnna Dabrowska * 665739c5360SAnna Dabrowska * @param array $tagFiler 6664a7da0a5SAnna Dabrowska * @return array 667972f6adfSAnna Dabrowska */ 668739c5360SAnna Dabrowska public function searchPages($tagFiler) 669972f6adfSAnna Dabrowska { 6701b4b4fa9SAnna Dabrowska global $INPUT; 6711b4b4fa9SAnna Dabrowska global $QUERY; 672df43a7beSAndreas Gohr $parsedQuery = ft_queryParser(new Indexer(), $QUERY); 673972f6adfSAnna Dabrowska 674df43a7beSAndreas Gohr $queryBuilder = new helper_plugin_tagging_querybuilder(); 675972f6adfSAnna Dabrowska 6764a7da0a5SAnna Dabrowska $queryBuilder->setField('pid'); 677cbe7b4baSAnna Dabrowska $queryBuilder->setTags($tagFiler); 678f014dfc9SAnna Dabrowska $queryBuilder->setLogicalAnd($INPUT->str('tagging-logic') === 'and'); 6794a7da0a5SAnna Dabrowska if (isset($parsedQuery['ns'])) $queryBuilder->includeNS($parsedQuery['ns']); 6804a7da0a5SAnna Dabrowska if (isset($parsedQuery['notns'])) $queryBuilder->excludeNS($parsedQuery['notns']); 6814a7da0a5SAnna Dabrowska if (isset($parsedQuery['tagger'])) $queryBuilder->setTagger($parsedQuery['tagger']); 6824a7da0a5SAnna Dabrowska if (isset($parsedQuery['pid'])) $queryBuilder->setPid($parsedQuery['pid']); 683972f6adfSAnna Dabrowska 6844a7da0a5SAnna Dabrowska return $this->queryDb($queryBuilder->getPages()); 685972f6adfSAnna Dabrowska } 686972f6adfSAnna Dabrowska 6871b4b4fa9SAnna Dabrowska /** 6884227fca4SAnna Dabrowska * Syntax to allow users to manage tags on regular pages, respects ACLs 6894227fca4SAnna Dabrowska * @param string $ns 6904227fca4SAnna Dabrowska * @return string 6914227fca4SAnna Dabrowska */ 6924227fca4SAnna Dabrowska public function manageTags($ns) 6934227fca4SAnna Dabrowska { 6944227fca4SAnna Dabrowska global $INPUT; 6954227fca4SAnna Dabrowska 696f6568bcbSAnna Dabrowska $this->setDefaultSort(); 6974227fca4SAnna Dabrowska 6984227fca4SAnna Dabrowska // initially set namespace filter to what is defined in syntax 6994227fca4SAnna Dabrowska if ($ns && !$INPUT->has('tagging__filters')) { 7004227fca4SAnna Dabrowska $INPUT->set('tagging__filters', ['ns' => $ns]); 7014227fca4SAnna Dabrowska } 7024227fca4SAnna Dabrowska 7034227fca4SAnna Dabrowska return $this->html_table(); 7044227fca4SAnna Dabrowska } 7054227fca4SAnna Dabrowska 7064227fca4SAnna Dabrowska /** 707a2246f69SAnna Dabrowska * HTML list of tagged pages 708a2246f69SAnna Dabrowska * 709a2246f69SAnna Dabrowska * @param string $tid 710a2246f69SAnna Dabrowska * @return string 711a2246f69SAnna Dabrowska */ 712a2246f69SAnna Dabrowska public function getPagesHtml($tid) 713a2246f69SAnna Dabrowska { 714a2246f69SAnna Dabrowska $html = ''; 715a2246f69SAnna Dabrowska 716a2246f69SAnna Dabrowska $db = $this->getDB(); 71731bddc5fSAnna Dabrowska $sql = 'SELECT pid from taggings where CLEANTAG(tag) = CLEANTAG(?)'; 718a2246f69SAnna Dabrowska $res = $db->query($sql, $tid); 719a2246f69SAnna Dabrowska $pages = $db->res2arr($res); 720a2246f69SAnna Dabrowska 721a2246f69SAnna Dabrowska if ($pages) { 722a2246f69SAnna Dabrowska $html .= '<ul>'; 723a2246f69SAnna Dabrowska foreach ($pages as $page) { 724a2246f69SAnna Dabrowska $pid = $page['pid']; 725a2246f69SAnna Dabrowska $html .= '<li><a href="' . wl($pid) . '" target="_blank">' . $pid . '</li>'; 726a2246f69SAnna Dabrowska } 727a2246f69SAnna Dabrowska $html .= '</ul>'; 728a2246f69SAnna Dabrowska } 729a2246f69SAnna Dabrowska 730a2246f69SAnna Dabrowska return $html; 731a2246f69SAnna Dabrowska } 732a2246f69SAnna Dabrowska 733a2246f69SAnna Dabrowska /** 73489ed97adSAnna Dabrowska * Display tag management table 73589ed97adSAnna Dabrowska */ 736df43a7beSAndreas Gohr public function html_table() 737df43a7beSAndreas Gohr { 73889ed97adSAnna Dabrowska global $ID, $INPUT; 73989ed97adSAnna Dabrowska 740df43a7beSAndreas Gohr $headers = [ 741df43a7beSAndreas Gohr ['value' => $this->getLang('admin tag'), 'sort_by' => 'tid'], 742df43a7beSAndreas Gohr ['value' => $this->getLang('admin occurrence'), 'sort_by' => 'count'] 743df43a7beSAndreas Gohr ]; 74426c4179dSAnna Dabrowska 74526c4179dSAnna Dabrowska if (!$this->conf['hidens']) { 746df43a7beSAndreas Gohr $headers[] = ['value' => $this->getLang('admin namespaces'), 'sort_by' => 'ns']; 74726c4179dSAnna Dabrowska } 748df43a7beSAndreas Gohr $headers[] = ['value' => $this->getLang('admin taggers'), 'sort_by' => 'taggers']; 749df43a7beSAndreas Gohr $headers[] = ['value' => $this->getLang('admin actions'), 'sort_by' => false]; 75089ed97adSAnna Dabrowska 751f6568bcbSAnna Dabrowska $sort = explode(',', $this->getParam('sort')); 75289ed97adSAnna Dabrowska $order_by = $sort[0]; 75389ed97adSAnna Dabrowska $desc = false; 75489ed97adSAnna Dabrowska if (isset($sort[1]) && $sort[1] === 'desc') { 75589ed97adSAnna Dabrowska $desc = true; 75689ed97adSAnna Dabrowska } 75789ed97adSAnna Dabrowska $filters = $INPUT->arr('tagging__filters'); 75889ed97adSAnna Dabrowska 75989ed97adSAnna Dabrowska $tags = $this->getAllTags($INPUT->str('filter'), $order_by, $desc, $filters); 76089ed97adSAnna Dabrowska 761df43a7beSAndreas Gohr $form = new Form(); 762a2246f69SAnna Dabrowska // required in admin mode 76389ed97adSAnna Dabrowska $form->setHiddenField('page', 'tagging'); 76489ed97adSAnna Dabrowska $form->setHiddenField('id', $ID); 765f6568bcbSAnna Dabrowska $form->setHiddenField('[tagging]sort', $this->getParam('sort')); 76689ed97adSAnna Dabrowska 76789ed97adSAnna Dabrowska /** 76889ed97adSAnna Dabrowska * Actions dialog 76989ed97adSAnna Dabrowska */ 77089ed97adSAnna Dabrowska $form->addTagOpen('div')->id('tagging__action-dialog')->attr('style', "display:none;"); 77189ed97adSAnna Dabrowska $form->addTagClose('div'); 77289ed97adSAnna Dabrowska 77389ed97adSAnna Dabrowska /** 77489ed97adSAnna Dabrowska * Tag pages dialog 77589ed97adSAnna Dabrowska */ 77689ed97adSAnna Dabrowska $form->addTagOpen('div')->id('tagging__taggedpages-dialog')->attr('style', "display:none;"); 77789ed97adSAnna Dabrowska $form->addTagClose('div'); 77889ed97adSAnna Dabrowska 77989ed97adSAnna Dabrowska /** 78089ed97adSAnna Dabrowska * Tag management table 78189ed97adSAnna Dabrowska */ 78289ed97adSAnna Dabrowska $form->addTagOpen('table')->addClass('inline plugin_tagging'); 78389ed97adSAnna Dabrowska 784df43a7beSAndreas Gohr $nscol = $this->conf['hidens'] ? '' : '<col class="wide-col" />'; 785a305ec34SAnna Dabrowska $form->addHTML( 786a305ec34SAnna Dabrowska '<colgroup> 787df43a7beSAndreas Gohr <col /> 788df43a7beSAndreas Gohr <col class="narrow-col" />' 78926c4179dSAnna Dabrowska . $nscol . 790df43a7beSAndreas Gohr '<col /> 791df43a7beSAndreas Gohr <col class="narrow-col" /> 792a305ec34SAnna Dabrowska </colgroup>' 793a305ec34SAnna Dabrowska ); 794a305ec34SAnna Dabrowska 79589ed97adSAnna Dabrowska /** 79689ed97adSAnna Dabrowska * Table headers 79789ed97adSAnna Dabrowska */ 79889ed97adSAnna Dabrowska $form->addTagOpen('tr'); 79989ed97adSAnna Dabrowska foreach ($headers as $header) { 80089ed97adSAnna Dabrowska $form->addTagOpen('th'); 80189ed97adSAnna Dabrowska if ($header['sort_by'] !== false) { 80289ed97adSAnna Dabrowska $param = $header['sort_by']; 80389ed97adSAnna Dabrowska $icon = 'arrow-both'; 80489ed97adSAnna Dabrowska $title = $this->getLang('admin sort ascending'); 80589ed97adSAnna Dabrowska if ($header['sort_by'] === $order_by) { 80689ed97adSAnna Dabrowska if ($desc === false) { 80789ed97adSAnna Dabrowska $icon = 'arrow-up'; 80889ed97adSAnna Dabrowska $title = $this->getLang('admin sort descending'); 80989ed97adSAnna Dabrowska $param .= ',desc'; 81089ed97adSAnna Dabrowska } else { 81189ed97adSAnna Dabrowska $icon = 'arrow-down'; 81289ed97adSAnna Dabrowska } 81389ed97adSAnna Dabrowska } 81426f61833SAnna Dabrowska $form->addButtonHTML( 815f6568bcbSAnna Dabrowska "tagging[sort]", 816df43a7beSAndreas Gohr $header['value'] . ' ' . inlineSVG(__DIR__ . "/images/$icon.svg") 817df43a7beSAndreas Gohr ) 81889ed97adSAnna Dabrowska ->addClass('plugin_tagging sort_button') 819f6568bcbSAnna Dabrowska ->attr('title', $title) 820f6568bcbSAnna Dabrowska ->val($param); 82189ed97adSAnna Dabrowska } else { 82289ed97adSAnna Dabrowska $form->addHTML($header['value']); 82389ed97adSAnna Dabrowska } 82489ed97adSAnna Dabrowska $form->addTagClose('th'); 82589ed97adSAnna Dabrowska } 82689ed97adSAnna Dabrowska $form->addTagClose('tr'); 82789ed97adSAnna Dabrowska 82889ed97adSAnna Dabrowska /** 82989ed97adSAnna Dabrowska * Table filters for all sortable columns 83089ed97adSAnna Dabrowska */ 83189ed97adSAnna Dabrowska $form->addTagOpen('tr'); 83289ed97adSAnna Dabrowska foreach ($headers as $header) { 83389ed97adSAnna Dabrowska $form->addTagOpen('th'); 83489ed97adSAnna Dabrowska if ($header['sort_by'] !== false) { 83589ed97adSAnna Dabrowska $field = $header['sort_by']; 8367c96ae87SAnna Dabrowska $input = $form->addTextInput("tagging__filters[$field]"); 837a305ec34SAnna Dabrowska $input->addClass('full-col'); 83889ed97adSAnna Dabrowska } 83989ed97adSAnna Dabrowska $form->addTagClose('th'); 84089ed97adSAnna Dabrowska } 84189ed97adSAnna Dabrowska $form->addTagClose('tr'); 84289ed97adSAnna Dabrowska 84389ed97adSAnna Dabrowska 84489ed97adSAnna Dabrowska foreach ($tags as $taginfo) { 84589ed97adSAnna Dabrowska $tagname = $taginfo['tid']; 84689ed97adSAnna Dabrowska $taggers = $taginfo['taggers']; 84789ed97adSAnna Dabrowska $ns = $taginfo['ns']; 84889ed97adSAnna Dabrowska $pids = explode(',', $taginfo['pids']); 84989ed97adSAnna Dabrowska 85089ed97adSAnna Dabrowska $form->addTagOpen('tr'); 85126f61833SAnna Dabrowska $form->addHTML('<td>'); 852a2246f69SAnna Dabrowska $form->addHTML('<a class="tagslist" href="#" data-tid="' . $taginfo['tid'] . '">'); 85326f61833SAnna Dabrowska $form->addHTML(hsc($tagname) . '</a>'); 85426f61833SAnna Dabrowska $form->addHTML('</td>'); 85589ed97adSAnna Dabrowska $form->addHTML('<td>' . $taginfo['count'] . '</td>'); 85626c4179dSAnna Dabrowska if (!$this->conf['hidens']) { 85789ed97adSAnna Dabrowska $form->addHTML('<td>' . hsc($ns) . '</td>'); 85826c4179dSAnna Dabrowska } 85989ed97adSAnna Dabrowska $form->addHTML('<td>' . hsc($taggers) . '</td>'); 86089ed97adSAnna Dabrowska 86189ed97adSAnna Dabrowska /** 86289ed97adSAnna Dabrowska * action buttons 86389ed97adSAnna Dabrowska */ 86489ed97adSAnna Dabrowska $form->addHTML('<td>'); 86589ed97adSAnna Dabrowska 86689ed97adSAnna Dabrowska // check ACLs 86789ed97adSAnna Dabrowska $userEdit = false; 86889ed97adSAnna Dabrowska foreach ($pids as $pid) { 869749c70e5SAndreas Gohr if (auth_quickaclcheck($pid) >= AUTH_EDIT) { 87089ed97adSAnna Dabrowska $userEdit = true; 871df43a7beSAndreas Gohr break; 87289ed97adSAnna Dabrowska } 87389ed97adSAnna Dabrowska } 87489ed97adSAnna Dabrowska 87589ed97adSAnna Dabrowska if ($userEdit) { 87626f61833SAnna Dabrowska $form->addButtonHTML( 877f6568bcbSAnna Dabrowska 'tagging[actions][rename][' . $taginfo['tid'] . ']', 878df43a7beSAndreas Gohr inlineSVG(__DIR__ . '/images/edit.svg') 879df43a7beSAndreas Gohr ) 88026f61833SAnna Dabrowska ->addClass('plugin_tagging action_button') 88126f61833SAnna Dabrowska ->attr('data-action', 'rename') 88226f61833SAnna Dabrowska ->attr('data-tid', $taginfo['tid']); 88326f61833SAnna Dabrowska $form->addButtonHTML( 884f6568bcbSAnna Dabrowska 'tagging[actions][delete][' . $taginfo['tid'] . ']', 885df43a7beSAndreas Gohr inlineSVG(__DIR__ . '/images/delete.svg') 886df43a7beSAndreas Gohr ) 88726f61833SAnna Dabrowska ->addClass('plugin_tagging action_button') 88826f61833SAnna Dabrowska ->attr('data-action', 'delete') 88926f61833SAnna Dabrowska ->attr('data-tid', $taginfo['tid']); 89089ed97adSAnna Dabrowska } 89189ed97adSAnna Dabrowska 89289ed97adSAnna Dabrowska $form->addHTML('</td>'); 89389ed97adSAnna Dabrowska $form->addTagClose('tr'); 89489ed97adSAnna Dabrowska } 89589ed97adSAnna Dabrowska 89689ed97adSAnna Dabrowska $form->addTagClose('table'); 8970b033188SAnna Dabrowska return '<div class="table">' . $form->toHTML() . '</div>'; 89889ed97adSAnna Dabrowska } 89989ed97adSAnna Dabrowska 90089ed97adSAnna Dabrowska /** 901ec4796e4SAnna Dabrowska * Display tag cleaner 902ec4796e4SAnna Dabrowska * 903ec4796e4SAnna Dabrowska * @return string 904ec4796e4SAnna Dabrowska */ 905ec4796e4SAnna Dabrowska public function html_clean() 906ec4796e4SAnna Dabrowska { 907ec4796e4SAnna Dabrowska $invalid = $this->getInvalidTaggings(); 908ec4796e4SAnna Dabrowska 909ec4796e4SAnna Dabrowska if (!$invalid) { 910ec4796e4SAnna Dabrowska return '<p><strong>' . $this->getLang('admin no invalid') . '</strong></p>'; 911ec4796e4SAnna Dabrowska } 912ec4796e4SAnna Dabrowska 913ec4796e4SAnna Dabrowska $form = new Form(); 914ec4796e4SAnna Dabrowska $form->setHiddenField('do', 'admin'); 915ec4796e4SAnna Dabrowska $form->setHiddenField('page', $this->getPluginName()); 916ec4796e4SAnna Dabrowska $form->addButton('cmd[clean]', $this->getLang('admin clean')); 917ec4796e4SAnna Dabrowska 918ec4796e4SAnna Dabrowska $html = $form->toHTML(); 919ec4796e4SAnna Dabrowska 920ec4796e4SAnna Dabrowska $html .= '<div class="table"><table class="inline plugin_tagging">'; 921ec4796e4SAnna Dabrowska $html .= '<thead><tr><th>' . 922ec4796e4SAnna Dabrowska $this->getLang('admin nonexistent page') . 923ec4796e4SAnna Dabrowska '</th><th>' . 924ec4796e4SAnna Dabrowska $this->getLang('admin tags') . 925ec4796e4SAnna Dabrowska '</th></tr></thead><tbody>'; 926ec4796e4SAnna Dabrowska 927ec4796e4SAnna Dabrowska foreach ($invalid as $row) { 928ec4796e4SAnna Dabrowska $html .= '<tr><td>' . $row['pid'] . '</td><td>' . $row['tags'] . '</td></tr>'; 929ec4796e4SAnna Dabrowska } 930ec4796e4SAnna Dabrowska 931ec4796e4SAnna Dabrowska $html .= '</tbody></table></div>'; 932ec4796e4SAnna Dabrowska 933ec4796e4SAnna Dabrowska return $html; 934ec4796e4SAnna Dabrowska } 935ec4796e4SAnna Dabrowska 936ec4796e4SAnna Dabrowska /** 937f6568bcbSAnna Dabrowska * Returns all tagging parameters from the query string 938f6568bcbSAnna Dabrowska * 939f6568bcbSAnna Dabrowska * @return mixed 940f6568bcbSAnna Dabrowska */ 941f6568bcbSAnna Dabrowska public function getParams() 942f6568bcbSAnna Dabrowska { 943f6568bcbSAnna Dabrowska global $INPUT; 944f6568bcbSAnna Dabrowska return $INPUT->param('tagging', []); 945f6568bcbSAnna Dabrowska } 946f6568bcbSAnna Dabrowska 947f6568bcbSAnna Dabrowska /** 948f6568bcbSAnna Dabrowska * Get a tagging parameter, empty string if not set 949f6568bcbSAnna Dabrowska * 950f6568bcbSAnna Dabrowska * @param string $name 951df43a7beSAndreas Gohr * @return string 952f6568bcbSAnna Dabrowska */ 953f6568bcbSAnna Dabrowska public function getParam($name) 954f6568bcbSAnna Dabrowska { 955f6568bcbSAnna Dabrowska $params = $this->getParams(); 956f6568bcbSAnna Dabrowska if ($params) { 957f6568bcbSAnna Dabrowska return $params[$name] ?: ''; 958f6568bcbSAnna Dabrowska } 959df43a7beSAndreas Gohr return ''; 960f6568bcbSAnna Dabrowska } 961f6568bcbSAnna Dabrowska 962f6568bcbSAnna Dabrowska /** 963f6568bcbSAnna Dabrowska * Sets a tagging parameter 964f6568bcbSAnna Dabrowska * 965f6568bcbSAnna Dabrowska * @param string $name 966f6568bcbSAnna Dabrowska * @param string|array $value 967f6568bcbSAnna Dabrowska */ 968f6568bcbSAnna Dabrowska public function setParam($name, $value) 969f6568bcbSAnna Dabrowska { 970f6568bcbSAnna Dabrowska global $INPUT; 971f6568bcbSAnna Dabrowska $params = $this->getParams(); 972f6568bcbSAnna Dabrowska $params = array_merge($params, [$name => $value]); 973df43a7beSAndreas Gohr 974f6568bcbSAnna Dabrowska $INPUT->set('tagging', $params); 975f6568bcbSAnna Dabrowska } 976f6568bcbSAnna Dabrowska 977f6568bcbSAnna Dabrowska /** 978f6568bcbSAnna Dabrowska * Default sorting by tag id 979f6568bcbSAnna Dabrowska */ 980f6568bcbSAnna Dabrowska public function setDefaultSort() 981f6568bcbSAnna Dabrowska { 982f6568bcbSAnna Dabrowska if (!$this->getParam('sort')) { 983f6568bcbSAnna Dabrowska $this->setParam('sort', 'tid'); 984f6568bcbSAnna Dabrowska } 985f6568bcbSAnna Dabrowska } 986f6568bcbSAnna Dabrowska 987f6568bcbSAnna Dabrowska /** 9884a7da0a5SAnna Dabrowska * Executes the query and returns the results as array 9891b4b4fa9SAnna Dabrowska * 99099122157SAnna Dabrowska * @param array $query 9911b4b4fa9SAnna Dabrowska * @return array 9921b4b4fa9SAnna Dabrowska */ 9934a7da0a5SAnna Dabrowska protected function queryDb($query) 9941b4b4fa9SAnna Dabrowska { 9954a7da0a5SAnna Dabrowska $db = $this->getDB(); 9964a7da0a5SAnna Dabrowska if (!$db) { 9974a7da0a5SAnna Dabrowska return []; 998972f6adfSAnna Dabrowska } 9994a7da0a5SAnna Dabrowska 100099122157SAnna Dabrowska $res = $db->query($query[0], $query[1]); 10014a7da0a5SAnna Dabrowska $res = $db->res2arr($res); 10024a7da0a5SAnna Dabrowska 10034a7da0a5SAnna Dabrowska $ret = []; 10044a7da0a5SAnna Dabrowska foreach ($res as $row) { 10054a7da0a5SAnna Dabrowska $ret[$row['item']] = $row['cnt']; 10064a7da0a5SAnna Dabrowska } 10074a7da0a5SAnna Dabrowska return $ret; 1008972f6adfSAnna Dabrowska } 100940b94b1aSAnna Dabrowska 101040b94b1aSAnna Dabrowska /** 101140b94b1aSAnna Dabrowska * Construct the HAVING part of the search query 101240b94b1aSAnna Dabrowska * 101340b94b1aSAnna Dabrowska * @param array $filters 101440b94b1aSAnna Dabrowska * @return array 101540b94b1aSAnna Dabrowska */ 101640b94b1aSAnna Dabrowska protected function getFilterSql($filters) 101740b94b1aSAnna Dabrowska { 101840b94b1aSAnna Dabrowska $having = ''; 101940b94b1aSAnna Dabrowska $parts = []; 102040b94b1aSAnna Dabrowska $params = []; 102140b94b1aSAnna Dabrowska $filters = array_filter($filters); 1022df43a7beSAndreas Gohr if ($filters !== []) { 102340b94b1aSAnna Dabrowska $having = ' HAVING '; 102440b94b1aSAnna Dabrowska foreach ($filters as $filter => $value) { 102540b94b1aSAnna Dabrowska $parts[] = " $filter LIKE ? "; 102640b94b1aSAnna Dabrowska $params[] = "%$value%"; 102740b94b1aSAnna Dabrowska } 102840b94b1aSAnna Dabrowska $having .= implode(' AND ', $parts); 102940b94b1aSAnna Dabrowska } 103040b94b1aSAnna Dabrowska return [$having, $params]; 103140b94b1aSAnna Dabrowska } 1032ec4796e4SAnna Dabrowska 1033ec4796e4SAnna Dabrowska /** 1034ec4796e4SAnna Dabrowska * Returns taggings of nonexistent pages 1035ec4796e4SAnna Dabrowska * 1036ec4796e4SAnna Dabrowska * @return array 1037ec4796e4SAnna Dabrowska */ 1038ec4796e4SAnna Dabrowska protected function getInvalidTaggings() 1039ec4796e4SAnna Dabrowska { 1040ec4796e4SAnna Dabrowska $db = $this->getDB(); 1041ec4796e4SAnna Dabrowska $query = 'SELECT "pid", 1042ec4796e4SAnna Dabrowska GROUP_CONCAT(CLEANTAG("tag")) AS "tags" 1043ec4796e4SAnna Dabrowska FROM "taggings" 1044f24ac95eSAnna Dabrowska WHERE NOT PAGEEXISTS(pid) 1045ec4796e4SAnna Dabrowska GROUP BY pid 1046ec4796e4SAnna Dabrowska '; 1047ec4796e4SAnna Dabrowska $res = $db->query($query); 1048ec4796e4SAnna Dabrowska return $db->res2arr($res); 1049ec4796e4SAnna Dabrowska } 1050cd08599fSAnna Dabrowska} 1051