taghelper = $this->loadHelper('tag'); } public function getMethods() { $result = []; $result[] = [ 'name' => 'getTagsByRegExp', 'desc' => 'returns tags for given Regular Expression', 'params' => [ 'tags (required)' => 'string', 'namespace (optional)' => 'string',], 'return' => ['tags' => 'array'], ]; $result[] = [ 'name' => 'getTagsByNamespace', 'desc' => 'returns tags for given namespace', 'params' => [ 'namespace' => 'string',], 'return' => ['tags' => 'array'], ]; $result[] = [ 'name' => 'getTagsByPageID', 'desc' => 'returns tags for given pageID', 'params' => [ 'pageID' => 'string',], 'return' => ['tags' => 'array'], ]; return $result; } /** * Search in Tagindex for tags that matches the tag pattern and are in requested namespace * * @param string $tagExpression regexp pattern of wanted tags e.g. "status:.*" * @param string $ns list only pages from this namespace * @param bool $aclSafe if true, add only tags that are on readable pages * @return string[]|false with tag=>label pairs * */ public function getTagsByRegExp($tagExpression, $ns = '', $aclSafe = false) { if (!$this->taghelper) { return false; } $tags = $this->getIndex('subject', '_w'); $matchedTag_label = []; foreach ($tags as $tag) { if ($this->matchesTagExpression($tagExpression, $tag) && $this->isTagInNamespace($tag, $ns, $aclSafe)) { $matchedTag_label[$tag] = $this->getTagLabel($tag); } } asort($matchedTag_label); //TODO update next release to dokuwiki builtin sort return $matchedTag_label; } /** * Test if tag matches with requested pattern * * @param string $tagExpression regexp pattern of wanted tags e.g. "status:.*" * @param string $tag * @return bool */ public function matchesTagExpression($tagExpression, $tag) { return (bool)@preg_match('/^' . $tagExpression . '$/i', $tag); } /** * Returns latest part of tag as label * * @param string $tag * @return string */ public function getTagLabel($tag) { $label = strrchr($tag, ':'); $label = $label != '' ? $label : $tag; return PhpString::ucwords(str_replace('_', ' ', trim($label, ':'))); } /** * Returns all tags used in given namespace * * @param string $ns list only tags used on pages from this namespace * @param bool $aclSafe if true, checks if user has read permission for the pages containing the tags * @return array|false|int[]|string[] */ public function getTagsByNamespace($ns = '', $aclSafe = true) { if (!$this->taghelper) { return false; } return array_keys($this->getTagsByRegExp('.*', $ns, $aclSafe)); } /** * Checks if current user can read the given pageid * * @param string $pageid * @return bool */ public function canRead($pageid) { return auth_quickaclcheck($pageid) >= AUTH_READ; } /** * Returns all tags for the given pageid * * @param string $pageID * @return array|mixed */ public function getTagsByPageID($pageID) { $meta = p_get_metadata($pageID, 'subject'); if ($meta === null) { $meta = []; } return $meta; } /** * Returns true if tags are equal * * @param string $tag1 tag being searched * @param string $tag2 tag from index * @return bool whether equal tags */ public function tagCompare($tag1, $tag2) { return $tag1 == $tag2; } /** * Checks if tag is used in the namespace, eventually can consider read permission as well * * @param string $tag * @param string $ns list pages from this namespace * @param bool $aclSafe if true, uses tag from a page only if user has read permissions * @return bool */ protected function isTagInNamespace($tag, $ns, $aclSafe = true) { if ($ns == '') { return true; } if (!$this->taghelper) { return false; } $indexer = idx_get_indexer(); $pages = $indexer->lookupKey('subject', $tag, [$this, 'tagCompare']); foreach ($pages as $page) { if ($this->taghelper->isVisible($page, $ns)) { if (!$aclSafe) { return true; } if (auth_quickaclcheck($page) >= AUTH_READ) { return true; } } } return false; } /** * Returns entire index file as array * * from inc/indexer.php * * @param string $idx * @param string $suffix * @return array|false */ protected function getIndex($idx, $suffix) { global $conf; $fn = $conf['indexdir'] . '/' . $idx . $suffix . '.idx'; if (!@file_exists($fn)) { return []; } return file($fn, FILE_IGNORE_NEW_LINES); } /** @var string */ protected $ps_ns = ''; /** @var array */ protected $ps_pages_id = []; /** @var array */ protected $ps_pages = []; /** * @param string $tag space separated tags * @param string $ns list only pages from this namespace * @return array */ public function getPagesByTag($tag, $ns = '') { $tags = explode(' ', $tag); $this->startPageSearch($ns); foreach ($tags as $t) { if ($t[0] == '+') { $this->addAndTag(substr($t, 1)); } elseif ($t[0] == '-') { $this->addSubTag(substr($t, 1)); } else { $this->addOrTag($t); } } return $this->getPages(); } /** * @param string $ns */ protected function startPageSearch($ns = '') { $this->ps_ns = $ns; $this->ps_pages_id = []; $this->ps_pages = []; } /** * @param string $tagExpression regexp pattern of wanted tags e.g. "status:.*" */ protected function addAndTag($tagExpression) { $tags = $this->getTagsByRegExp($tagExpression, $this->ps_ns); $pages = []; foreach ($tags as $t => $v) { $Hpages = $this->taghelper->getTopic($this->ps_ns, null, $t); foreach ($Hpages as $p) { $pages[] = $p['id']; if (!isset($this->ps_pages[$p['id']])) { $this->ps_pages[$p['id']] = $p; } } } $pages = array_unique($pages); $this->ps_pages_id = array_intersect($this->ps_pages_id, $pages); } /** * @param string $tagExpression regexp pattern of wanted tags e.g. "status:.*" */ protected function addSubTag($tagExpression) { $tags = $this->getTagsByRegExp($tagExpression, $this->ps_ns); $pages = array(); foreach ($tags as $t => $v) { $Hpages = $this->taghelper->getTopic($this->ps_ns, '', $t); foreach ($Hpages as $p) { $pages[] = $p['id']; } } $pages = array_unique($pages); $this->ps_pages_id = array_diff($this->ps_pages_id, $pages); } /** * @param string $tagExpression regexp pattern of wanted tags e.g. "status:.*" * @return void */ protected function addOrTag($tagExpression) { $tags = $this->getTagsByRegExp($tagExpression, $this->ps_ns); $pages = array(); foreach ($tags as $t => $v) { $Hpages = $this->taghelper->getTopic($this->ps_ns, '', $t); foreach ($Hpages as $p) { $pages[] = $p['id']; if (!isset($this->ps_pages[$p['id']])) { $this->ps_pages[$p['id']] = $p; } } } $pages = array_unique($pages); $this->ps_pages_id = array_merge($this->ps_pages_id, $pages); $this->ps_pages_id = array_unique($this->ps_pages_id); } /** * @return array */ protected function getPages() { $ret = []; foreach ($this->ps_pages_id as $id) { $ret[] = $this->ps_pages[$id]; } return $ret; } /** * @param string $tag * @return false|string */ public function getImageLinkByTag($tag) { $id = $this->getConf('nsTagImage') . ':' . str_replace([' ', ':'], '_', $tag); $src = $id . '.jpg'; if (!@file_exists(mediaFN($src))) { $src = $id . '.png'; if (!@file_exists(mediaFN($src))) { $src = $id . '.jpeg'; if (!@file_exists(mediaFN($src))) { $src = false; } } } if ($src !== false) { return ml($src); } return false; } /** * Generate html for in a cell of the column of the tags as images * * @param string $id pageid * @param string $col tagexpression: regexp pattern of wanted tags e.g. "status:.*" * @param string $ns namespace with images * @return string html of tagimage(s) in cell */ public function getTagImageColumn($id, $col, $ns) { if (!isset($this->tagsPerPage[$id])) { $this->tagsPerPage[$id] = $this->getTagsByPageID($id); } $foundTags = []; foreach ($this->tagsPerPage[$id] as $tag) { if ($this->matchesTagExpression($col, $tag)) { $foundTags[] = hsc($this->getTagLabel($tag)); } } $images = []; foreach ($foundTags as $foundTag) { $imageid = $ns . ':' . substr($foundTag, strrpos($foundTag, ':')); $src = $imageid . '.jpg'; if (!@file_exists(mediaFN($src))) { $src = $imageid . '.png'; if (!@file_exists(mediaFN($src))) { $src = $imageid . '.jpeg'; if (!@file_exists(mediaFN($src))) { $src = $imageid . '.gif'; if (!@file_exists(mediaFN($src))) { $src = false; } } } } if ($src !== false) { $images[] = ''; } } return implode("
", $images); } /** * return all pages defined by tag_list_r in a specific namespace * * @param string $ns the namespace to look in * @param array $tag_list_r an array containing strings with tags seperated by ' ' * */ public function getAllPages($ns, $tag_list_r) { $pages = array(); $pages[''] = ''; $tag_list = implode(' ', $tag_list_r); $page_r = $this->getPagesByTags($ns, $tag_list); foreach ($page_r as $page) { $title = p_get_metadata($page, 'title', METADATA_DONT_RENDER); $title = $title ?: $page; $pages[$page] = strip_tags($title); //FIXME hsc() doesent work with chosen } asort($pages); return $pages; } /** * Returns page title, otherwise pageid * * @param string $pageid * @return string */ public function getPageTitle($pageid) { $title = p_get_metadata($pageid, 'title', METADATA_DONT_RENDER); $title = $title ?: $pageid; return strip_tags($title); } /** * Gets the pages defined by tag_list * * partially copied from tag->helper with less checks (on cache) and no meta lookups * @param string $ns the namespace to look in * @param string $tag_list the tags separated by ' ' * * @return array array of page ids */ public function getPagesByTags($ns, $tag_list) { $tags = $this->taghelper->parseTagList($tag_list, true); $matchedPages = $this->taghelper->getIndexedPagesMatchingTagQuery($tags); $filteredPages = []; foreach ($matchedPages as $matchedPage) { // filter by namespace, root namespace is identified with a dot // root namespace is specified, discard all pages who lay outside the root namespace if (($ns == '.' && getNS($matchedPage) === false) || strpos(':' . getNS($matchedPage) . ':', ':' . $ns . ':') === 0 || $ns === '') { if (auth_quickaclcheck($matchedPage) >= AUTH_READ) { $filteredPages[] = $matchedPage; } } } return $filteredPages; } /** * @param $tag * @return string */ public function getTagCategory($tag) { $label = strstr($tag, ':', true); $label = $label != '' ? $label : $tag; return PhpString::ucwords(str_replace('_', ' ', trim($label, ':'))); } /** * Used by pagelist plugin for filling the cell of the table header * * @param string $column column name is a tagexpression * @return string */ public function th($column = '') { if (strpos($column, '*')) { return $this->getTagCategory($column); } else { return $this->getTagLabel($column); } } /** @var array[] with pageid => array with tags */ protected $tagsPerPage = []; /** * Used by pagelist plugin for filling the cells of the table * and in listing by the tagfilter * * @param string $id page id of row * @param string $column column name is a tagexpression: regexp pattern of wanted tags e.g. "status:.*". Supported since 2022 in pagelist plugin * @return string */ public function td($id, $column = null) { if($column === null) { return ''; } if (!isset($this->tagsPerPage[$id])) { $this->tagsPerPage[$id] = $this->getTagsByPageID($id); } $foundTags = []; foreach ($this->tagsPerPage[$id] as $tag) { if ($this->matchesTagExpression($column, $tag)) { $foundTags[] = hsc($this->getTagLabel($tag)); } } return implode("
", $foundTags); } /** * Returns per tag the pages where these are used as array with: tag=>array with pages * The tags matches the tag regexp pattern and only shown if it is used at pages in requested namespace, these pages * are listed in an array per tag * * Does not check ACL * * @param string $tags tag expression e.g. "status:.*" * @param string $ns list only pages from this namespace * @return array [tag]=>array pages where tag is used */ public function getPagesByMatchedTags($tags, $ns = '') { if (!$this->taghelper) return []; $tags = $this->taghelper->parseTagList($tags, false); //array $indexer = idx_get_indexer(); $indexTags = array_keys($indexer->histogram(1, 0, 3, 'subject')); $matchedTags = []; foreach ($indexTags as $tag) { foreach ($tags as $tagExpr) { if ($this->matchesTagExpression($tagExpr, $tag)) $matchedTags[] = $tag; } } $matchedTags = array_unique($matchedTags); $matchedPages = []; foreach ($matchedTags as $tag) { $pages = $this->taghelper->getIndexedPagesMatchingTagQuery([$tag]); // keep only if in requested ns $matchedPages[$tag] = array_filter($pages, function ($pageid) use ($ns) { return $ns === '' || strpos(':' . getNS($pageid) . ':', ':' . $ns . ':') === 0; }); } //clean empty tags, because not in requested namespace $matchedPages = array_filter($matchedPages); ksort($matchedPages); return $matchedPages; } }