1
0
Fork 0
dokuwiki-plugins-extra/plugins/55/indexmenu/Search.php
Daniel Baumann f339727d60
Adding indexmenu version 2024-01-05 (ed06f21).
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-03-24 19:33:15 +01:00

858 lines
33 KiB
PHP

<?php
namespace dokuwiki\plugin\indexmenu;
use dokuwiki\Utf8\Sort;
class Search
{
/**
* @var bool|string sort by t=title, d=date of creation, 0 if not set i.e. default page sort (old dTree..)
*/
private $sort;
/**
* @var string 'indexmenu_n' or other key from the metadata structure
*/
private $msort;
/**
* @var bool Reverse the sorting of pages, combined with $nsort also the namespaces
*/
private $rsort;
/**
* @var bool also sorts the namespaces
*/
private $nsort;
/**
* @var bool Sort the headpages as defined by global config setting startpage to the top
*/
private $hsort;
/**
* Search constructor.
*
* @param array $sort
* $sort['sort']
* $sort['msort']
* $sort['rsort']
* $sort['nsort']
* $sort['hsort'];
*/
public function __construct($sort)
{
$this->sort = $sort['sort'];
$this->msort = $sort['msort'];
$this->rsort = $sort['rsort'];
$this->nsort = $sort['nsort'];
$this->hsort = $sort['hsort'];
}
/**
* Build the data array for fancytree from search results
*
* @param array $data results from search
* @param bool $isInit true if first level of nodes from tree, false if next levels
* @param bool $currentPage current wikipage id
* @param bool $isNopg if nopg is set
* @return array
*/
public function buildFancytreeData($data, $isInit, $currentPage, $isNopg)
{
if (empty($data)) return [];
$children = [];
$opts = [
'currentPage' => $currentPage,
'isParentLazy' => false,
'nopg' => $isNopg
];
$hasActiveNode = false;
$this->makeNodes($data, -1, 0, $children, $hasActiveNode, $opts);
if ($isInit) {
$nodes['children'] = $children;
return $nodes;
} else {
return $children;
}
}
/**
* Collects the children at the same level since last parsed item
*
* @param array $data results from search
* @param int $indexLatestParsedItem
* @param int $previousLevel level of parent
* @param array $nodes by reference, here the child nodes are stored
* @param bool $hasActiveNode active node must be unique, needs tracking
* @param array $opts <ul>
* <li>$opts['currentPage'] string id of main article</li>
* <li>$opts['isParentLazy'] bool Used for recognizing the extra level below lazy nodes</li>
* <li>$opts['nopg'] bool needed for currentpage handling</li>
* </ul>
* @return int latest parsed item from data array
*/
private function makeNodes(&$data, $indexLatestParsedItem, $previousLevel, &$nodes, &$hasActiveNode, $opts)
{
$i = 0;
$counter = 0;
foreach ($data as $i => $item) {
//skip parsed items
if ($i <= $indexLatestParsedItem) {
continue;
}
if ($item['level'] < $previousLevel || $counter === 0 && $item['level'] == $previousLevel) {
return $i - 1;
}
$node = [
'title' => $item['title'],
'key' => $item['id'] . ($item['type'] === 'f' ? '' : ':'), //ensure ns is unique
'hns' => $item['hns'] //false if not available
];
// f=file, d=directory, l=directory which is lazy loaded later
if ($item['type'] == 'f') {
// let php create url (considering rewriting etc)
$node['url'] = wl($item['id']);
//set current page to active
if ($opts['currentPage'] == $item['id']) {
if (!$hasActiveNode) {
$node['active'] = true;
$hasActiveNode = true;
}
}
} else {
// type: d/l
$node['folder'] = true;
// let php create url (considering rewriting etc)
$node['url'] = $item['hns'] === false ? false : wl($item['hns']);
if (!$item['hnsExists']) {
//change link color
$node['hnsNotExisting'] = true;
}
if ($item['open'] === true) {
$node['expanded'] = true;
}
$node['children'] = [];
$indexLatestParsedItem = $this->makeNodes(
$data,
$i,
$item['level'],
$node['children'],
$hasActiveNode,
[
'currentPage' => $opts['currentPage'],
'isParentLazy' => $item['type'] === 'l',
'nopg' => $opts['nopg']
]
);
// a lazy node, but because we have sometime no pages or nodes (due e.g. acl/hidden/nopg), it could be
// empty. Therefore we did extra work by walking a level deeper and check here whether it has children
if ($item['type'] === 'l') {
if (empty($node['children'])) {
//an empty lazy node, is not marked lazy
if ($opts['isParentLazy']) {
//a lazy node with a lazy parent has no children loaded, so stays always empty
//(these nodes are not really used, but only counted)
$node['lazy'] = true;
unset($node['children']);
}
} else {
//has children, so mark lazy
$node['lazy'] = true;
unset($node['children']); //do not keep, because these nodes do not know yet their child folders
}
}
//might be duplicated if hide_headpage is disabled, or with nopg and a :same: headpage
//mark active after processing children, such that deepest level is activated
if (
$item['hns'] === $opts['currentPage']
|| $opts['nopg'] && getNS($opts['currentPage']) === $item['id']
) {
//with hide_headpage enabled, the parent node must be actived
//special: nopg has no pages, therefore, mark its parent node active
if (!$hasActiveNode) {
$node['active'] = true;
$hasActiveNode = true;
}
}
}
if ($item['type'] === 'f' || !empty($node['children']) || isset($node['lazy']) || $item['hns'] !== false) {
// add only files, non-empty folders, lazy-loaded or folder with only a headpage
$nodes[] = $node;
}
$previousLevel = $item['level'];
$counter++;
}
return $i;
}
/**
* Search pages/folders depending on the given options $opts
*
* @param string $ns
* @param array $opts<ul>
* <li>$opts['skipns'] string regexp matching namespaceids to skip (ignored)</li>
* <li>$opts['skipfile'] string regexp matching pageids to skip (ignored)</li>
* <li>$opts['skipnscombined'] array regexp matching namespaceids to skip</li>
* <li>$opts['skipfilecombined'] array regexp matching pageids to skip</li>
* <li>$opts['headpage'] string headpages options or pageids</li>
* <li>$opts['level'] int desired depth of main namespace, -1 = all levels</li>
* <li>$opts['subnss'] array with entries: array(namespaceid,level) specifying namespaces with their own
* number of opened levels</li>
* <li>$opts['nons'] bool exclude namespace nodes</li>
* <li>$opts['max'] int If initially closed, the node at max level will retrieve all its child nodes
* through the AJAX mechanism</li>
* <li>$opts['nopg'] bool exclude page nodes</li>
* <li>$opts['hide_headpage'] int don't hide (0) or hide (1)</li>
* <li>$opts['js'] bool use js-render (only used for old 'searchIndexmenuItems')</li>
* </ul>
* @return array The results of the search
*/
public function search($ns, $opts): array
{
global $conf;
if (!empty($opts['tempNew'])) {
//a specific callback for Fancytree
$callback = [$this, 'searchIndexmenuItemsNew'];
} else {
$callback = [$this, 'searchIndexmenuItems'];
}
$dataDir = $conf['datadir'];
$data = [];
$fsDir = "/" . utf8_encodeFN(str_replace(':', '/', $ns));
if ($this->sort || $this->msort || $this->rsort || $this->hsort) {
$this->customSearch($data, $dataDir, $callback, $opts, $fsDir);
} else {
search($data, $dataDir, $callback, $opts, $fsDir);
}
return $data;
}
/**
* Callback that adds an item of namespace/page to the browsable index, if it fits in the specified options
*
* @param array $data Already collected nodes
* @param string $base Where to start the search, usually this is $conf['datadir']
* @param string $file Current file or directory relative to $base
* @param string $type Type either 'd' for directory or 'f' for file
* @param int $lvl Current recursion depth
* @param array $opts Option array as given to search():<ul>
* <li>$opts['skipns'] string regexp matching namespaceids to skip (ignored),</li>
* <li>$opts['skipfile'] string regexp matching pageids to skip (ignored),</li>
* <li>$opts['skipnscombined'] array regexp matching namespaceids to skip,</li>
* <li>$opts['skipfilecombined'] array regexp matching pageids to skip,</li>
* <li>$opts['headpage'] string headpages options or pageids,</li>
* <li>$opts['level'] int desired depth of main namespace, -1 = all levels,</li>
* <li>$opts['subnss'] array with entries: array(namespaceid,level) specifying namespaces with their own number
* of opened levels,</li>
* <li>$opts['nons'] bool Exclude namespace nodes,</li>
* <li>$opts['max'] int If initially closed, the node at max level will retrieve all its child nodes through
* the AJAX mechanism,</li>
* <li>$opts['nopg'] bool Exclude page nodes,</li>
* <li>$opts['hide_headpage'] int don't hide (0) or hide (1),</li>
* <li>$opts['js'] bool use js-render</li>
* </ul>
* @return bool if this directory should be traversed (true) or not (false)
*
* @author Andreas Gohr <andi@splitbrain.org>
* modified by Samuele Tognini <samuele@samuele.netsons.org>
*/
public function searchIndexmenuItems(&$data, $base, $file, $type, $lvl, $opts)
{
global $conf;
$hns = false;
$isOpen = false;
$title = null;
$skipns = $opts['skipnscombined'];
$skipfile = $opts['skipfilecombined'];
$headpage = $opts['headpage'];
$id = pathID($file);
if ($type == 'd') {
// Skip folders in plugin conf
foreach ($skipns as $skipn) {
if (!empty($skipn) && preg_match($skipn, $id)) {
return false;
}
}
//check ACL (for sneaky_index namespaces too).
if ($conf['sneaky_index'] && auth_quickaclcheck($id . ':') < AUTH_READ) return false;
//Open requested level
if ($opts['level'] > $lvl || $opts['level'] == -1) {
$isOpen = true;
}
//Search optional subnamespaces with
if (!empty($opts['subnss'])) {
$subnss = $opts['subnss'];
$counter = count($subnss);
for ($a = 0; $a < $counter; $a++) {
if (preg_match("/^" . $id . "($|:.+)/i", $subnss[$a][0], $match)) {
//It contains a subnamespace
$isOpen = true;
} elseif (preg_match("/^" . $subnss[$a][0] . "(:.*)/i", $id, $match)) {
//It's inside a subnamespace, check level
// -1 is open all, otherwise count number of levels in the remainer of the pageid
// (match[0] is always prefixed with :)
if ($subnss[$a][1] == -1 || substr_count($match[1], ":") < $subnss[$a][1]) {
$isOpen = true;
} else {
$isOpen = false;
}
}
}
}
//decide if it should be traversed
if ($opts['nons']) {
return $isOpen; // in nons, level is only way to show/hide nodes (in nons nodes are not expandable)
} elseif ($opts['max'] > 0 && !$isOpen && $lvl >= $opts['max']) {
//Stop recursive searching
$shouldBeTraversed = false;
//change type
$type = "l";
} elseif ($opts['js']) {
$shouldBeTraversed = true; //TODO if js tree, then traverse deeper???
} else {
$shouldBeTraversed = $isOpen;
}
//Set title and headpage
$title = static::getNamespaceTitle($id, $headpage, $hns);
// when excluding page nodes: guess a headpage based on the headpage setting
if ($opts['nopg'] && $hns === false) {
$hns = $this->guessHeadpage($headpage, $id);
}
} else {
//Nopg. Dont show pages
if ($opts['nopg']) return false;
$shouldBeTraversed = true;
//Nons.Set all pages at first level
if ($opts['nons']) {
$lvl = 1;
}
//don't add
if (substr($file, -4) != '.txt') return false;
//check hiddens and acl
if (isHiddenPage($id) || auth_quickaclcheck($id) < AUTH_READ) return false;
//Skip files in plugin conf
foreach ($skipfile as $skipf) {
if (!empty($skipf) && preg_match($skipf, $id)) {
return false;
}
}
//Skip headpages to hide (nons has no namespace nodes, therefore, no duplicated links to headpage)
if (!$opts['nons'] && !empty($headpage) && $opts['hide_headpage']) {
//start page is in root
if ($id == $conf['start']) return false;
$ahp = explode(",", $headpage);
foreach ($ahp as $hp) {
switch ($hp) {
case ":inside:":
if (noNS($id) == noNS(getNS($id))) return false;
break;
case ":same:":
if (@is_dir(dirname(wikiFN($id)) . "/" . utf8_encodeFN(noNS($id)))) return false;
break;
//it' s an inside start
case ":start:":
if (noNS($id) == $conf['start']) return false;
break;
default:
if (noNS($id) == cleanID($hp)) return false;
}
}
}
//Set title
if ($conf['useheading'] == 1 || $conf['useheading'] === 'navigation') {
$title = p_get_first_heading($id, false);
}
if (is_null($title)) {
$title = noNS($id);
}
$title = hsc($title);
}
$item = [
'id' => $id,
'type' => $type,
'level' => $lvl,
'open' => $isOpen,
'title' => $title,
'hns' => $hns,
'file' => $file,
'shouldBeTraversed' => $shouldBeTraversed
];
$item['sort'] = $this->getSortValue($item);
$data[] = $item;
return $shouldBeTraversed;
}
/**
* Callback that adds an item of namespace/page to the browsable index, if it fits in the specified options
*
* TODO Version as used for Fancytree js tree
*
* @param array $data indexed array of collected nodes, each item has:<ul>
* <li>$item['id'] string namespace or page id</li>
* <li>$item['type'] string f/d/l</li>
* <li>$item['level'] string current recursion depth (start count at 1)</li>
* <li>$item['open'] bool if a node is open</li>
* <li>$item['title'] string </li>
* <li>$item['hns'] string|false page id or false</li>
* <li>$item['hnsExists'] bool only false if hns is guessed(not-existing) for nopg</li>
* <li>$item['file'] string path to file or directory</li>
* <li>$item['shouldBeTraversed'] bool directory should be searched</li>
* <li>$item['sort'] mixed sort value</li>
* </ul>
* @param string $base Where to start the search, usually this is $conf['datadir']
* @param string $file Current file or directory relative to $base
* @param string $type Type either 'd' for directory or 'f' for file
* @param int $lvl Current recursion depth
* @param array $opts Option array as given to search()<ul>
* <li>$opts['skipns'] string regexp matching namespaceids to skip (ignored)</li>
* <li>$opts['skipfile'] string regexp matching pageids to skip (ignored)</li>
* <li>$opts['skipnscombined'] array regexp matching namespaceids to skip</li>
* <li>$opts['skipfilecombined'] array regexp matching pageids to skip</li>
* <li>$opts['headpage'] string headpages options or pageids</li>
* <li>$opts['level'] int desired depth of main namespace, -1 = all levels</li>
* <li>$opts['subnss'] array with entries: array(namespaceid,level) specifying namespaces with their
* own level</li>
* <li>$opts['nons'] bool exclude namespace nodes</li>
* <li>$opts['max'] int If initially closed, the node at max level will retrieve all its child nodes
* through the AJAX mechanism</li>
* <li>$opts['nopg'] bool exclude page nodes</li>
* <li>$opts['hide_headpage'] int don't hide (0) or hide (1)</li>
* </ul>
* @return bool if this directory should be traversed (true) or not (false)
*
* @author Andreas Gohr <andi@splitbrain.org>
* modified by Samuele Tognini <samuele@samuele.netsons.org>
*/
public function searchIndexmenuItemsNew(&$data, $base, $file, $type, $lvl, $opts)
{
global $conf;
$hns = false;
$isOpen = false;
$title = null;
$skipns = $opts['skipnscombined'];
$skipfile = $opts['skipfilecombined'];
$headpage = $opts['headpage'];
$hnsExists = true; //nopg guesses pages
$id = pathID($file);
if ($type == 'd') {
// Skip folders in plugin conf
foreach ($skipns as $skipn) {
if (!empty($skipn) && preg_match($skipn, $id)) {
return false;
}
}
//check ACL (for sneaky_index namespaces too).
if ($conf['sneaky_index'] && auth_quickaclcheck($id . ':') < AUTH_READ) return false;
//Open requested level
if ($opts['level'] > $lvl || $opts['level'] == -1) {
$isOpen = true;
}
//Search optional subnamespaces with
$isFolderAdjacentToSubNss = false;
if (!empty($opts['subnss'])) {
$subnss = $opts['subnss'];
$counter = count($subnss);
for ($a = 0; $a < $counter; $a++) {
if (preg_match("/^" . $id . "($|:.+)/i", $subnss[$a][0], $match)) {
//this folder contains a subnamespace
$isOpen = true;
} elseif (preg_match("/^" . $subnss[$a][0] . "(:.*)/i", $id, $match)) {
//this folder is inside a subnamespace, check level
if ($subnss[$a][1] == -1 || substr_count($match[1], ":") < $subnss[$a][1]) {
$isOpen = true;
} else {
$isOpen = false;
}
} elseif (
preg_match(
"/^" . (($ns = getNS($id)) === false ? '' : $ns) . "($|:.+)/i",
$subnss[$a][0],
$match
)
) {
// parent folder contains a subnamespace, if level deeper it does not match anymore
// that is handled with normal >max handling
$isOpen = false;
if ($opts['max'] > 0) {
$isFolderAdjacentToSubNss = true;
}
}
}
}
//decide if it should be traversed
if ($opts['nons']) {
return $isOpen; // in nons, level is only way to show/hide nodes (in nons nodes are not expandable)
} elseif ($opts['max'] > 0 && !$isOpen) { // note: for Fancytree >=1 is used
// limited levels per request, node is closed
if ($lvl == $opts['max'] || $isFolderAdjacentToSubNss) {
// change type, more nodes should be loaded by ajax, but for nopg we need extra level to determine
// if folder is empty
// and folders adjacent to subns must be traversed as well
$type = "l";
$shouldBeTraversed = true;
} elseif ($lvl > $opts['max']) { // deeper lvls only used temporary for checking existance children
//change type, more nodes should be loaded by ajax
$type = "l"; // use lazy loading
$shouldBeTraversed = false;
} else {
//node is closed, but still more levels requested with max
$shouldBeTraversed = true;
}
} else {
$shouldBeTraversed = $isOpen;
}
//Set title and headpage
$title = static::getNamespaceTitle($id, $headpage, $hns);
// when excluding page nodes: guess a headpage based on the headpage setting
if ($opts['nopg'] && $hns === false) {
$hns = $this->guessHeadpage($headpage, $id);
$hnsExists = false;
}
} else {
//Nopg.Dont show pages
if ($opts['nopg']) return false;
$shouldBeTraversed = true;
//Nons.Set all pages at first level
if ($opts['nons']) {
$lvl = 1;
}
//don't add
if (substr($file, -4) != '.txt') return false;
//check hiddens and acl
if (isHiddenPage($id) || auth_quickaclcheck($id) < AUTH_READ) return false;
//Skip files in plugin conf
foreach ($skipfile as $skipf) {
if (!empty($skipf) && preg_match($skipf, $id)) {
return false;
}
}
//Skip headpages to hide
if (!$opts['nons'] && !empty($headpage) && $opts['hide_headpage']) {
//start page is in root
if ($id == $conf['start']) return false;
$hpOptions = explode(",", $headpage);
foreach ($hpOptions as $hp) {
switch ($hp) {
case ":inside:":
if (noNS($id) == noNS(getNS($id))) return false;
break;
case ":same:":
if (@is_dir(dirname(wikiFN($id)) . "/" . utf8_encodeFN(noNS($id)))) return false;
break;
//it' s an inside start
case ":start:":
if (noNS($id) == $conf['start']) return false;
break;
default:
if (noNS($id) == cleanID($hp)) return false;
}
}
}
//Set title
if ($conf['useheading'] == 1 || $conf['useheading'] === 'navigation') {
$title = p_get_first_heading($id, false);
}
if (is_null($title)) {
$title = noNS($id);
}
$title = hsc($title);
}
$item = [
'id' => $id,
'type' => $type,
'level' => $lvl,
'open' => $isOpen,
'title' => $title,
'hns' => $hns,
'hnsExists' => $hnsExists,
'file' => $file,
'shouldBeTraversed' => $shouldBeTraversed
];
$item['sort'] = $this->getSortValue($item);
$data[] = $item;
return $shouldBeTraversed;
}
/**
* callback that recurse directory
*
* This function recurses into a given base directory
* and calls the supplied function for each file and directory
*
* Similar to search() of inc/search.php, but has extended sorting options
*
* @param array $data The results of the search are stored here
* @param string $base Where to start the search
* @param callback $func Callback (function name or array with object,method)
* @param array $opts List of indexmenu options
* @param string $dir Current directory beyond $base
* @param int $lvl Recursion Level
*
* @author Andreas Gohr <andi@splitbrain.org>
* @author modified by Samuele Tognini <samuele@samuele.netsons.org>
*/
public function customSearch(&$data, $base, $func, $opts, $dir = '', $lvl = 1)
{
$dirs = [];
$files = [];
$files_tmp = [];
$dirs_tmp = [];
$count = count($data);
//read in directories and files
$dh = @opendir($base . '/' . $dir);
if (!$dh) return;
while (($file = readdir($dh)) !== false) {
//skip hidden files and upper dirs
if (preg_match('/^[._]/', $file)) continue;
if (is_dir($base . '/' . $dir . '/' . $file)) {
$dirs[] = $dir . '/' . $file;
continue;
}
$files[] = $dir . '/' . $file;
}
closedir($dh);
//Collect and sort files
foreach ($files as $file) {
call_user_func_array($func, [&$files_tmp, $base, $file, 'f', $lvl, $opts]);
}
usort($files_tmp, [$this, "compareNodes"]);
//Collect and sort dirs
if ($this->nsort) {
//collect the wanted directories in dirs_tmp
foreach ($dirs as $dir) {
call_user_func_array($func, [&$dirs_tmp, $base, $dir, 'd', $lvl, $opts]);
}
//combine directories and pages and sort together
$dirsAndFiles = array_merge($dirs_tmp, $files_tmp);
usort($dirsAndFiles, [$this, "compareNodes"]);
//add and search each directory
foreach ($dirsAndFiles as $dirOrFile) {
$data[] = $dirOrFile;
if ($dirOrFile['type'] != 'f' && $dirOrFile['shouldBeTraversed']) {
$this->customSearch($data, $base, $func, $opts, $dirOrFile['file'], $lvl + 1);
}
}
} else {
//sort by directory name
Sort::sort($dirs);
//collect directories
foreach ($dirs as $dir) {
if (call_user_func_array($func, [&$data, $base, $dir, 'd', $lvl, $opts])) {
$this->customSearch($data, $base, $func, $opts, $dir, $lvl + 1);
}
}
}
//count added items
$added = count($data) - $count;
if ($added === 0 && $files_tmp === []) {
//remove empty directory again, only if it has not a headpage associated
$lastItem = end($data);
if (!$lastItem['hns']) {
array_pop($data);
}
} elseif (!$this->nsort) {
//add files to index
$data = array_merge($data, $files_tmp);
}
}
/**
* Get namespace title, checking for headpages
*
* @param string $ns namespace
* @param string $headpage comma-separated headpages options and headpages
* @param string|false $hns reference pageid of headpage, false when not existing
* @return string when headpage & heading on: title of headpage, otherwise: namespace name
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
*/
public static function getNamespaceTitle($ns, $headpage, &$hns)
{
global $conf;
$hns = false;
$title = noNS($ns);
if (empty($headpage)) {
return $title;
}
$hpOptions = explode(",", $headpage);
foreach ($hpOptions as $hp) {
switch ($hp) {
case ":inside:":
$page = $ns . ":" . noNS($ns);
break;
case ":same:":
$page = $ns;
break;
//it's an inside start
case ":start:":
$page = ltrim($ns . ":" . $conf['start'], ":");
break;
//inside pages
default:
if (!blank($hp)) { //empty setting results in empty string here
$page = $ns . ":" . $hp;
}
}
//check headpage
if (@file_exists(wikiFN($page)) && auth_quickaclcheck($page) >= AUTH_READ) {
if ($conf['useheading'] == 1 || $conf['useheading'] === 'navigation') {
$title_tmp = p_get_first_heading($page, false);
if (!is_null($title_tmp)) {
$title = $title_tmp;
}
}
$title = hsc($title);
$hns = $page;
//headpage found, exit for
break;
}
}
return $title;
}
/**
* callback that sorts nodes
*
* @param array $a first node as array with 'sort' entry
* @param array $b second node as array with 'sort' entry
* @return int if less than zero 1st node is less than 2nd, otherwise equal respectively larger
*/
private function compareNodes($a, $b)
{
if ($this->rsort) {
return Sort::strcmp($b['sort'], $a['sort']);
} else {
return Sort::strcmp($a['sort'], $b['sort']);
}
}
/**
* Add sort information to item.
*
* @param array $item
* @return bool|int|mixed|string
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
*/
private function getSortValue($item)
{
global $conf;
$sort = false;
$page = false;
if ($item['type'] == 'd' || $item['type'] == 'l') {
//Fake order info when nsort is not requested
if ($this->nsort) {
$page = $item['hns'];
} else {
$sort = 0;
}
}
if ($item['type'] == 'f') {
$page = $item['id'];
}
if ($page) {
if ($this->hsort && noNS($item['id']) == $conf['start']) {
$sort = 1;
}
if ($this->msort) {
$sort = p_get_metadata($page, $this->msort);
}
if (!$sort && $this->sort) {
switch ($this->sort) {
case 't':
$sort = $item['title'];
break;
case 'd':
$sort = @filectime(wikiFN($page));
break;
}
}
}
if ($sort === false) {
$sort = noNS($item['id']);
}
return $sort;
}
/**
* Guess based on first option of the headpage config setting (default :start: if enabled) the headpage of the node
*
* @param string $headpage config setting
* @param string $ns namespace
* @return string guessed headpage
*/
private function guessHeadpage(string $headpage, string $ns): string
{
global $conf;
$hns = false;
$hpOptions = explode(",", $headpage);
foreach ($hpOptions as $hp) {
switch ($hp) {
case ":inside:":
$hns = $ns . ":" . noNS($ns);
break 2;
case ":same:":
$hns = $ns;
break 2;
//it's an inside start
case ":start:":
$hns = ltrim($ns . ":" . $conf['start'], ":");
break 2;
//inside pages
default:
if (!blank($hp)) {
$hns = $ns . ":" . $hp;
break 2;
}
}
}
if ($hns === false) {
//fallback to start if headpage setting was empty
$hns = ltrim($ns . ":" . $conf['start'], ":");
}
return $hns;
}
}