はじまりの大地

This commit is contained in:
miteruzo
2024-07-08 03:32:47 +09:00
commit c616a96f53
7749 changed files with 478270 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
name: DokuWiki Default Tasks
on:
push:
pull_request:
schedule:
- cron: '14 17 21 * *'
jobs:
all:
uses: dokuwiki/github-action/.github/workflows/all.yml@main
+64
View File
@@ -0,0 +1,64 @@
Thanks to:
Geir Landro:
Dtree Javascript code.
Roland Hellebart:
The Dtree idea.
Chris Beetle:
The root namespace index.
Gleb:
The nons e headpage option suggestion.
Malyfred:
Resolved incorrect namespaces levels bug.
Raymond Elferink:
Resolved incorrect ACLs bug.
Ilya Lebedev:
Skip index option.
Franck Baron:
Js id option.
Jon B:
Skip file option.
Neosky:
Javascript toolbar bug.
Paul Grove:
Css dynamic properties and suggestion of js theme with differents image formats
Anja Vag:
Great help in testing and finding bugs.
Blaz:
Current page highliting suggestion.
Adrien CLERC:
Start page bug.
Ryan Jake and Fullindex plugin:
Sort by metada suggestion.
Herman Huitema:
Context menu search function and great help in testing patches.
Thomas Binder:
Fixed a bug with msort/nsort that did not manage empty arrays.
Fabian Pfannes:
German language
Urban:
Context menu patch and other suggestions
Gerrit Uitslag (Klap-in):
Rewrite of indexmenu to add new dokuwiki compatibility
Added a new toolbar wizard
add ''hsort'' for sorting [[config:startpage]] pages to top of listing
many others improvements
+871
View File
@@ -0,0 +1,871 @@
<?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 Group the namespaces and page and sort separately, or mix them and sort together
*/
private $group;
/**
* @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['group']
* $sort['hsort'];
*/
public function __construct($sort)
{
$this->sort = $sort['sort'];
$this->msort = $sort['msort'];
$this->rsort = $sort['rsort'];
$this->nsort = $sort['nsort'];
$this->group = $sort['group'];
$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]);
}
if($this->group) {
//group directories and pages, and sort separately
$dirsAndFiles = $dirs_tmp;
} else {
// no grouping
//mix 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 && !$this->group)) {
//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;
}
}
@@ -0,0 +1,35 @@
<?php
/**
* Test namespace includes
*
* @group plugin_indexmenu
* @group plugins
*/
class ActionTest extends DokuWikiTest
{
/**
* Setup - enable and load the include plugin and create the test pages
*/
public function setUp(): void
{
$this->pluginsEnabled[] = 'indexmenu';
parent::setUp(); // this enables the include plugin
// $this->helper = plugin_load('helper', 'include');
// global $conf;
// $conf['hidepages'] = 'inclhidden:hidden';
// for testing hidden pages
saveWikiText('ns2:bpage', "======H1======\nText", 'Sort different naturally/title/page');
saveWikiText('ns2:apage', "======H3======\nText", 'Sort different naturally/title/page');
saveWikiText('ns2:cpage', "======H2======\nText", 'Sort different naturally/title/page');
// pages on different levels
saveWikiText('ns1:ns1:apage', 'Page on level 1', 'Created page on level 1');
saveWikiText('ns1:lvl2:lvl3:lvl4:apage', 'Page on level 4', 'Created page on level 4');
saveWikiText('ns1:ns2:apage', 'Page on level 2', 'Created page on level 2');
saveWikiText('ns1:ns0:bpage', 'Page on level 2', 'Created page on level 2');
}
}
@@ -0,0 +1,345 @@
<?php
/**
* Test sorting
*
* Principle copied from _test/tests/lib/exe/ajax_requests.test.php
*
* @group ajax
* @group plugin_indexmenu
* @group plugins
*/
class AjaxRequestsTest extends DokuWikiTest
{
public function setUp(): void
{
$this->pluginsEnabled[] = 'indexmenu';
parent::setUp(); // this enables the indexmenu plugin
//needed for 'tsort' to use First headings, sets title during search, otherwise as fallback page name used.
global $conf;
$conf['useheading'] = 'navigation';
// for testing sorting pages
saveWikiText('ns2:cpage', "======Bb======\nText", 'Sort different page/title/creation date');
sleep(1); // ensure different timestamps for 'dsort'
saveWikiText('ns2:bpage', "======Aa======\nText", 'Sort different page/title/creation date');
sleep(1);
saveWikiText('ns2:apage', "======Cc======\nText", 'Sort different page/title/creation date');
//ensures title is added to metadata of page
idx_addPage('ns2:cpage');
idx_addPage('ns2:bpage');
idx_addPage('ns2:apage');
// pages on different levels
saveWikiText('ns1:ns2:apage', "======Bb======\nPage on level 2", 'Created page on level 2');
saveWikiText('ns1:ns1:apage', "======Ee======\nPage on level 2", 'Created page on level 2');
saveWikiText('ns1:ns1:lvl3:lvl4:apage', "======Cc======\nPage on levl 4", 'Page on level 4');
saveWikiText('ns1:ns1:start', "======Aa======\nPage on level 2", 'Startpage on level 2');
saveWikiText('ns1:ns0:bpage', "======Aa2======\nPage on level 2", 'Created page on level 2');
saveWikiText('ns1:apage', "======Dd======\nPage on level 1", 'Created page on level 1');
//ensures title is added to metadata
idx_addPage('ns1:ns1:apage');
idx_addPage('ns1:ns1:lvl3:lvl4:apage');
idx_addPage('ns1:ns1:start');
idx_addPage('ns1:ns2:apage');
idx_addPage('ns1:ns0:bpage');
idx_addPage('ns1:apage');
}
/**
* DataProvider for the builtin Ajax calls
*
* @return array
*/
public static function indexmenuCalls()
{
return [
// Call, POST parameters, result function
[
'indexmenu',
AjaxRequestsTest::prepareParams(['level' => 1]),
'expectedResultWiki'
],
[
'indexmenu',
AjaxRequestsTest::prepareParams(['ns' => 'ns2', 'level' => 1]),
'expectedResultNs2PageSort'
],
[
'indexmenu',
AjaxRequestsTest::prepareParams(['ns' => 'ns2', 'level' => 1, 'sort' => 't']),
'expectedResultNs2TitleSort'
],
[
'indexmenu',
AjaxRequestsTest::prepareParams(['ns' => 'ns2', 'level' => 1, 'sort' => 'd']),
'expectedResultNs2CreationDateSort'
],
[
'indexmenu',
AjaxRequestsTest::prepareParams(['ns' => 'ns1', 'level' => 1, 'sort' => 't']),
'expectedResultNs1TitleSort'
],
[
'indexmenu',
AjaxRequestsTest::prepareParams(['ns' => 'ns1', 'level' => 1, 'sort' => 't', 'nsort' => 1]),
'expectedResultNs1TitleSortNamespaceSort'
]
];
}
/**
* @dataProvider indexmenuCalls
*
* @param string $call
* @param array $post
* @param $expectedResult
*/
public function testBasicSorting($call, $post, $expectedResult)
{
$request = new TestRequest();
$response = $request->post(['call' => $call] + $post, '/lib/exe/ajax.php');
// $this->assertNotEquals("AJAX call '$call' unknown!\n", $response->getContent());
//var_export(json_decode($response->getContent()), true); // print as PHP array
$actualArray = json_decode($response->getContent(), true);
unset($actualArray['debug']);
unset($actualArray['sort']);
unset($actualArray['opts']);
$this->assertEquals($this->$expectedResult(), $actualArray);
// $regexp: null, or regexp pattern to match
// example: '/^<div class="odd type_d/'
// if (!empty($regexp)) {
// $this->assertRegExp($regexp, $response->getContent());
// }
}
public function test_params()
{
// print_r(AjaxRequestsTest::prepareParams(['level' => 2]));
$this->assertTrue(true);
}
public static function prepareParams($params = [])
{
$defaults = [
'ns' => 'wiki',
'req' => 'fancytree',
'level' => 1,
'nons' => 0,
'nopg' => 0,
'max' => 0,
'skipns' => ['/^board:(first|second|third|fourth|fifth)$/'],
'skipfile' => ['/(:start$)/'],
'sort' => 0,
'msort' => 0,
'rsort' => 0,
'nsort' => 0,
'hsort' => 0,
'init' => 1
];
$return = [];
foreach ($defaults as $key => $default) {
$return[$key] = $params[$key] ?? $default;
}
return $return;
}
public function expectedResultWiki()
{
return [
'children' => [
0 => [
'title' => 'dokuwiki',
'key' => 'wiki:dokuwiki',
'hns' => false,
'url' => '/./doku.php?id=wiki:dokuwiki'
],
1 => [
'title' => 'syntax',
'key' => 'wiki:syntax',
'hns' => false,
'url' => '/./doku.php?id=wiki:syntax'
]
]];
}
public function expectedResultNs1()
{
return [
'children' => [
0 => [
'title' => 'dokuwiki',
'key' => 'wiki:dokuwiki',
'hns' => false,
'url' => '/./doku.php?id=wiki:dokuwiki'
],
1 => [
'title' => 'syntax',
'key' => 'wiki:syntax',
'hns' => false,
'url' => '/./doku.php?id=wiki:syntax'
]
]];
}
public function expectedResultNs2PageSort()
{
return [
'children' => [
0 => [
'title' => 'Cc',
'key' => 'ns2:apage',
'hns' => false,
'url' => '/./doku.php?id=ns2:apage'
],
1 => [
'title' => 'Aa',
'key' => 'ns2:bpage',
'hns' => false,
'url' => '/./doku.php?id=ns2:bpage'
],
2 => [
'title' => 'Bb',
'key' => 'ns2:cpage',
'hns' => false,
'url' => '/./doku.php?id=ns2:cpage'
]
]];
}
public function expectedResultNs2TitleSort()
{
return [
'children' => [
0 => [
'title' => 'Aa',
'key' => 'ns2:bpage',
'hns' => false,
'url' => '/./doku.php?id=ns2:bpage'
],
1 => [
'title' => 'Bb',
'key' => 'ns2:cpage',
'hns' => false,
'url' => '/./doku.php?id=ns2:cpage'
],
2 => [
'title' => 'Cc',
'key' => 'ns2:apage',
'hns' => false,
'url' => '/./doku.php?id=ns2:apage'
]
]];
}
public function expectedResultNs2CreationDateSort()
{
return [
'children' => [
0 => [
'title' => 'Bb',
'key' => 'ns2:cpage',
'hns' => false,
'url' => '/./doku.php?id=ns2:cpage'
],
1 => [
'title' => 'Aa',
'key' => 'ns2:bpage',
'hns' => false,
'url' => '/./doku.php?id=ns2:bpage'
],
2 => [
'title' => 'Cc',
'key' => 'ns2:apage',
'hns' => false,
'url' => '/./doku.php?id=ns2:apage'
]
]];
}
public function expectedResultNs1TitleSort()
{
return [
'children' => [
0 => [
'title' => 'ns0',
'key' => 'ns1:ns0:',
'hns' => false,
'folder' => true,
'lazy' => true,
'url' => false
],
1 => [
'title' => 'Aa',
'key' => 'ns1:ns1:',
'hns' => 'ns1:ns1:start',
'folder' => true,
'lazy' => true,
'url' => '/./doku.php?id=ns1:ns1:start'
],
2 => [
'title' => 'ns2',
'key' => 'ns1:ns2:',
'hns' => false,
'folder' => true,
'lazy' => true,
'url' => false
],
3 => [
'title' => 'Dd',
'key' => 'ns1:apage',
'hns' => false,
'url' => '/./doku.php?id=ns1:apage'
]
]];
}
public function expectedResultNs1TitleSortNamespaceSort()
{
// 'nsort' let the sort explicitly use the namespace name as sort key.
// 'nsort' + 'tsort' works only for nsort if head pages are used.
return [
'children' => [
0 => [
'title' => 'Aa',
'key' => 'ns1:ns1:',
'hns' => 'ns1:ns1:start',
'folder' => true,
'lazy' => true,
'url' => '/./doku.php?id=ns1:ns1:start'
],
1 => [
'title' => 'Dd',
'key' => 'ns1:apage',
'hns' => false,
'url' => '/./doku.php?id=ns1:apage'
],
2 => [
'title' => 'ns0',
'key' => 'ns1:ns0:',
'hns' => false,
'folder' => true,
'lazy' => true,
'url' => false
],
3 => [
'title' => 'ns2',
'key' => 'ns1:ns2:',
'hns' => false,
'folder' => true,
'lazy' => true,
'url' => false
]
]];
}
}
@@ -0,0 +1,86 @@
<?php
namespace dokuwiki\plugin\indexmenu\test;
use DokuWikiTest;
/**
* General tests for the indexmenu plugin
*
* @group plugin_indexmenu
* @group plugins
*/
class GeneralTest extends DokuWikiTest
{
/**
* Simple test to make sure the plugin.info.txt is in correct format
*/
public function testPluginInfo(): void
{
$file = __DIR__ . '/../plugin.info.txt';
$this->assertFileExists($file);
$info = confToHash($file);
$this->assertArrayHasKey('base', $info);
$this->assertArrayHasKey('author', $info);
$this->assertArrayHasKey('email', $info);
$this->assertArrayHasKey('date', $info);
$this->assertArrayHasKey('name', $info);
$this->assertArrayHasKey('desc', $info);
$this->assertArrayHasKey('url', $info);
$this->assertEquals('indexmenu', $info['base']);
$this->assertRegExp('/^https?:\/\//', $info['url']);
$this->assertTrue(mail_isvalid($info['email']));
$this->assertRegExp('/^\d\d\d\d-\d\d-\d\d$/', $info['date']);
$this->assertTrue(false !== strtotime($info['date']));
}
/**
* Test to ensure that every conf['...'] entry in conf/default.php has a corresponding meta['...'] entry in
* conf/metadata.php.
*/
public function testPluginConf(): void
{
$conf_file = __DIR__ . '/../conf/default.php';
$meta_file = __DIR__ . '/../conf/metadata.php';
if (!file_exists($conf_file) && !file_exists($meta_file)) {
self::markTestSkipped('No config files exist -> skipping test');
}
if (file_exists($conf_file)) {
include($conf_file);
}
if (file_exists($meta_file)) {
include($meta_file);
}
$this->assertEquals(
gettype($conf),
gettype($meta),
'Both ' . DOKU_PLUGIN . 'indexmenu/conf/default.php and ' . DOKU_PLUGIN . 'indexmenu/conf/metadata.php have to exist and contain the same keys.'
);
if ($conf !== null && $meta !== null) {
foreach ($conf as $key => $value) {
$this->assertArrayHasKey(
$key,
$meta,
'Key $meta[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'indexmenu/conf/metadata.php'
);
}
foreach ($meta as $key => $value) {
$this->assertArrayHasKey(
$key,
$conf,
'Key $conf[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'indexmenu/conf/default.php'
);
}
}
}
}
@@ -0,0 +1,353 @@
<?php
use DOMWrap\Document;
require_once DOKU_INC . 'inc/parser/xhtml.php';
/**
* @group plugin_indexmenu
*/
class IndexmenuSyntaxTest extends DokuWikiTest
{
public function setup(): void
{
// global $conf;
$this->pluginsEnabled[] = 'indexmenu';
parent::setup();
//$conf['plugin']['indexmenu']['headpage'] = '';
//$conf['plugin']['indexmenu']['hide_headpage'] = false;
//saveWikiText('titleonly:sub:test', "====== Title ====== \n content", 'created');
//saveWikiText('test', "====== Title ====== \n content", 'created');
//idx_addPage('titleonly:sub:test');
//idx_addPage('test');
}
// public function __construct() {
//// $this->exampleIndex = "{{indexmenu>:}}";
//
// parent::__construct();
// }
/**
* Create from list of values the output array of handle()
*
* @param array $values
* @return array aligned similar to output of handle()
*/
private function createData($values)
{
[
$ns, $theme, $identifier, $nocookie, $navbar, $noscroll, $maxjs, $notoc, $jsajax, $context, $nomenu,
$sort, $msort, $rsort, $nsort, $level, $nons, $nopg, $subnss, $max, $maxAjax, $js, $skipns, $skipfile,
$skipnscombined, $skipfilecombined, $hsort, $headpage, $hide_headpage, $jsVersion
] = $values;
return [
$ns,
[
'theme' => $theme,
'identifier' => $identifier,
'nocookie' => $nocookie,
'navbar' => $navbar,
'noscroll' => $noscroll,
'maxJs' => $maxjs,
'notoc' => $notoc,
'jsAjax' => $jsajax,
'context' => $context,
'nomenu' => $nomenu,
],
[
'sort' => $sort,
'msort' => $msort,
'rsort' => $rsort,
'nsort' => $nsort,
'hsort' => $hsort,
],
[
'level' => $level,
'nons' => $nons,
'nopg' => $nopg,
'subnss' => $subnss,
'max' => $max,
'js' => $js,
'skipns' => $skipns,
'skipfile' => $skipfile,
'skipnscombined' => $skipnscombined,
'skipfilecombined' => $skipfilecombined,
'headpage' => $headpage,
'hide_headpage' => $hide_headpage,
'maxajax' => $maxAjax,
'navbar' => $navbar,
'theme' => $theme
],
$jsVersion
];
}
/**
* Data provider
*
* @return array[]
*/
public static function someSyntaxes()
{
return [
//root ns (empty is not recognized..)
// [syntax, data]
[
"{{indexmenu>:}}",
[
'', 'default', 'random', false, false, false, 1, false, '', false, false,
0, false, false, false, -1, false, false, [], 0, 1, false, '', '', [''], [''], false,
":start:,:same:,:inside:", 1, 1
]
],
//root ns, #levels=1, js renderer
[
"{{indexmenu>#1|js}}",
[
'', 'default', 'random', false, false, false, 1, false, '', false, false,
0, false, false, false, 1, false, false, [], 0, 1, true, '', '', [''], [''], false,
":start:,:same:,:inside:", 1, 1
]
],
//root ns, #levels=2, all not js specific options (nocookie is from context)
[
"{{indexmenu>#2 test#6|navbar context tsort dsort msort hsort rsort nsort nons nopg}}",
[
'', 'default', 'random', true, true, false, 1, false, '&sort=t&msort=indexmenu_n&rsort=1&nsort=1&hsort=1&nopg=1', true, false,
't', 'indexmenu_n', true, true, 2, true, true, [['test', 6]], 0, 1, false, '', '', [''], [''], true,
":start:,:same:,:inside:", 1, 1
]
],
//root ns, #levels=2, js renderer, all not js specific options
[
"{{indexmenu>#2 test#6|navbar js#bj_ubuntu.png context tsort dsort msort hsort rsort nsort nons nopg}}",
[
'', 'bj_ubuntu.png', 'random', true, true, false, 1, false, '&sort=t&msort=indexmenu_n&rsort=1&nsort=1&hsort=1&nopg=1', true, false,
't', 'indexmenu_n', true, true, 2, true, true, [['test', 6]], 0, 1, true, '', '', [''], [''], true,
":start:,:same:,:inside:", 1, 1
]
],
//root ns, #levels=1, all options
[
"{{indexmenu>#1|navbar context nocookie noscroll notoc nomenu dsort msort#date:modified hsort rsort nsort nons nopg max#2#4 maxjs#3 id#54321}}",
[
'', 'default', 'random', true, true, true, 1, true, '&sort=d&msort=date modified&rsort=1&nsort=1&hsort=1&nopg=1', true, true,
'd', 'date modified', true, true, 1, true, true, [], 0, 1, false, '', '', [''], [''], true,
":start:,:same:,:inside:", 1, 1
]
],
//root ns, #levels=1, js renderer, all options
[
"{{indexmenu>#1|js#bj_ubuntu.png navbar context nocookie noscroll notoc nomenu dsort msort#date:modified hsort rsort nsort nons nopg max#2#4 maxjs#3 id#54321}}",
[
'', 'bj_ubuntu.png', 54321, true, true, true, 3, true, '&sort=d&msort=date modified&rsort=1&nsort=1&hsort=1&nopg=1&max=4', true, true,
'd', 'date modified', true, true, 1, true, true, [], 2, 4, true, '', '', [''], [''], true,
":start:,:same:,:inside:", 1, 1
]
],
//root ns, #levels=1, skipfile and ns
[
"{{indexmenu>#1 test|skipfile+/(^myusers:spaces$|privatens:userss)/ skipns=/(^myusers:spaces$|privatens:users)/ id#ns}}",
[
'', 'default', 'random', false, false, false, 1, false, '&skipns=%3D/%28%5Emyusers%3Aspaces%24%7Cprivatens%3Ausers%29/&skipfile=%2B/%28%5Emyusers%3Aspaces%24%7Cprivatens%3Auserss%29/', false, false,
0, false, false, false, 1, false, false, [['test', -1]], 0, 1, false, '=/(^myusers:spaces$|privatens:users)/',
'+/(^myusers:spaces$|privatens:userss)/', ['/(^myusers:spaces$|privatens:users)/'], ['', '/(^myusers:spaces$|privatens:userss)/'], false,
":start:,:same:,:inside:", 1, 1
]
],
//root ns, #levels=1, js renderer, skipfile and ns
[
"{{indexmenu>#1 test|js skipfile=/(^myusers:spaces$|privatens:userss)/ skipns+/(^myusers:spaces$|privatens:userssss)/ id#ns}}",
[
'', 'default', 0, false, false, false, 1, false, '&skipns=%2B/%28%5Emyusers%3Aspaces%24%7Cprivatens%3Auserssss%29/&skipfile=%3D/%28%5Emyusers%3Aspaces%24%7Cprivatens%3Auserss%29/', false, false,
0, false, false, false, 1, false, false, [['test', -1]], 0, 1, true, '+/(^myusers:spaces$|privatens:userssss)/',
'=/(^myusers:spaces$|privatens:userss)/', ['', '/(^myusers:spaces$|privatens:userssss)/'], ['/(^myusers:spaces$|privatens:userss)/'], false,
":start:,:same:,:inside:", 1, 1
]
]
];
}
/**
* Parse the syntax to options
* expect: different combinations with or without js option, covers recognizing all syntax options
*
* @dataProvider someSyntaxes
*/
public function testHandle($syntax, $changedData)
{
$plugin = new syntax_plugin_indexmenu_indexmenu();
$null = new Doku_Handler();
$result = $plugin->handle($syntax, 0, 40, $null);
//copy unique generated number, which is about 23 characters
$len_id = strlen($result[1]['identifier']);
if (!is_numeric($changedData[2]) && ($len_id > 18 && $len_id <= 23)) {
$changedData[2] = $result[1]['identifier'];
}
$data = $this->createData($changedData);
$this->assertEquals($data, $result, 'Data array corrupted');
}
/**
* Data provider
*
* @return array[]
*/
public static function differentNSs()
{
$pageInRoot = 'page';
$pageInLvl1 = 'ns:page';
$pageInLvl2 = 'ns1:ns2:page';
return [
//indexmenu on page at root level
['{{indexmenu>|}}', '', [], $pageInRoot],
['{{indexmenu>#1}}', '', [], $pageInRoot],
['{{indexmenu>:}}', '', [], $pageInRoot],
['{{indexmenu>.}}', '', [], $pageInRoot],
['{{indexmenu>.:}}', '', [], $pageInRoot],
['{{indexmenu>..}}', '', [], $pageInRoot],
['{{indexmenu>..:}}', '', [], $pageInRoot],
['{{indexmenu>myns}}', 'myns', [], $pageInRoot],
['{{indexmenu>:myns}}', 'myns', [], $pageInRoot],
['{{indexmenu>.myns}}', 'myns', [], $pageInRoot],
['{{indexmenu>.:myns}}', 'myns', [], $pageInRoot],
['{{indexmenu>..myns}}', 'myns', [], $pageInRoot],
['{{indexmenu>..:myns}}', 'myns', [], $pageInRoot],
//indexmenu on page in a namespace
['{{indexmenu>|}}', '', [], $pageInLvl1],
['{{indexmenu>#1}}', '', [], $pageInLvl1],
['{{indexmenu>:}}', '', [], $pageInLvl1],
['{{indexmenu>.}}', 'ns', [], $pageInLvl1],
['{{indexmenu>.:}}', 'ns', [], $pageInLvl1],
['{{indexmenu>..}}', '', [], $pageInLvl1],
['{{indexmenu>..:}}', '', [], $pageInLvl1],
['{{indexmenu>myns}}', 'myns', [], $pageInLvl1], //was ns:myns
['{{indexmenu>:myns}}', 'myns', [], $pageInLvl1],
['{{indexmenu>.myns}}', 'ns:myns', [], $pageInLvl1],
['{{indexmenu>.:myns}}', 'ns:myns', [], $pageInLvl1],
['{{indexmenu>..myns}}', 'myns', [], $pageInLvl1],
['{{indexmenu>..:myns}}', 'myns', [], $pageInLvl1],
['{{indexmenu>myns:myns}}', 'myns:myns', [], $pageInLvl2],
//indexmenu on page in a namespace
['{{indexmenu>|}}', '', [], $pageInLvl2],
['{{indexmenu>#1}}', '', [], $pageInLvl2],
['{{indexmenu>:}}', '', [], $pageInLvl2],
['{{indexmenu>.}}', 'ns1:ns2', [], $pageInLvl2],
['{{indexmenu>.:}}', 'ns1:ns2', [], $pageInLvl2],
['{{indexmenu>..}}', '', [], $pageInLvl2], //strange indexmenu specific exception! TODO remove?
['{{indexmenu>..:}}', 'ns1', [], $pageInLvl2],
['{{indexmenu>myns}}', 'myns', [], $pageInLvl2], //was ns1:ns2:myns
['{{indexmenu>:myns}}', 'myns', [], $pageInLvl2],
['{{indexmenu>.myns}}', 'ns1:ns2:myns', [], $pageInLvl2],
['{{indexmenu>.:myns}}', 'ns1:ns2:myns', [], $pageInLvl2],
['{{indexmenu>..myns}}', 'ns1:myns', [], $pageInLvl2],
['{{indexmenu>..:myns}}', 'ns1:myns', [], $pageInLvl2],
['{{indexmenu>myns:myns}}', 'myns:myns', [], $pageInLvl2],
['{{indexmenu>..:..:myns}}', 'ns1:myns', [], 'ns1:ns2:ns3:page'],
['{{indexmenu>0}}', '0', [], 'ns1:page'], //was ns1:0
//indexmenu on page at root level and subns
['{{indexmenu> #1|}}', '', [], $pageInLvl2], //no subns, spaces before are removed
['{{indexmenu>#1 #1}}', '', [['', 1]], $pageInLvl2],
['{{indexmenu>: :}}', '', [['', -1]], $pageInLvl2],
['{{indexmenu>. .}}', 'ns1:ns2', [['ns1:ns2', -1]], $pageInLvl2],
['{{indexmenu>.: .:}}', 'ns1:ns2', [['ns1:ns2', -1]], $pageInLvl2],
['{{indexmenu>.. ..}}', '', [['', -1]], $pageInLvl2],
['{{indexmenu>..: ..:}}', 'ns1', [['ns1', -1]], $pageInLvl2],
['{{indexmenu>myns myns}}', 'myns', [['myns', -1]], $pageInLvl2], //was ns1:ns2:myns
['{{indexmenu>:myns :myns}}', 'myns', [['myns', -1]], $pageInLvl2],
['{{indexmenu>.myns .myns}}', 'ns1:ns2:myns', [['ns1:ns2:myns', -1]], $pageInLvl2],
['{{indexmenu>.:myns .:myns}}', 'ns1:ns2:myns', [['ns1:ns2:myns', -1]], $pageInLvl2],
['{{indexmenu>..myns ..myns}}', 'ns1:myns', [['ns1:myns', -1]], $pageInLvl2],
['{{indexmenu>..:myns ..myns}}', 'ns1:myns', [['ns1:myns', -1]], $pageInLvl2],
['{{indexmenu>myns:myns myns:myns}}', 'myns:myns', [['myns:myns', -1]], $pageInLvl2],
//indexmenu on page in a namespace
['{{indexmenu>|}}', '', [], $pageInLvl2],
['{{indexmenu>#1}}', '', [], $pageInLvl2],
['{{indexmenu>:}}', '', [], $pageInLvl2],
['{{indexmenu>.}}', 'ns1:ns2', [], $pageInLvl2],
['{{indexmenu>.:}}', 'ns1:ns2', [], $pageInLvl2],
['{{indexmenu>..}}', '', [], $pageInLvl2], //strange indexmenu specific exception! TODO remove?
['{{indexmenu>..:}}', 'ns1', [], $pageInLvl2],
['{{indexmenu>myns:}}', 'myns', [], $pageInLvl2], //was ns1:ns2:myns
['{{indexmenu>:myns:}}', 'myns', [], $pageInLvl2],
['{{indexmenu>.myns:}}', 'ns1:ns2:myns', [], $pageInLvl2],
['{{indexmenu>.:myns:}}', 'ns1:ns2:myns', [], $pageInLvl2],
['{{indexmenu>..myns:}}', 'ns1:myns', [], $pageInLvl2],
['{{indexmenu>..:myns:}}', 'ns1:myns', [], $pageInLvl2],
['{{indexmenu>myns:myns:}}', 'myns:myns', [], $pageInLvl2],
];
}
/**
* Parse the syntax to options
* expect: different combinations with or without js option, covers recognizing all syntax options
*
* @dataProvider differentNSs
*/
public function testResolving($syntax, $expectedNs, $expectedSubNss, $pageWithIndexmenu)
{
global $ID;
$ID = $pageWithIndexmenu;
$plugin = new syntax_plugin_indexmenu_indexmenu();
$null = new Doku_Handler();
$result = $plugin->handle($syntax, 0, 40, $null);
$this->assertEquals($expectedNs, $result[0], 'check resolved ns');
$this->assertEquals($expectedSubNss, $result[3]['subnss'], 'check resolved subNSs');
}
/**
* Rendering for nonexisting namespace
* expect: no paragraph due to no message set
* expect: one paragraph, since message set
* expect: contains namespace which replaced {{ns}}
* expect: message contained rendered italic syntax
*/
public function testRenderEmptymsg()
{
global $conf;
$noexistns = 'nonexisting:namespace';
$emptyindexsyntax = "{{indexmenu>$noexistns}}";
$xhtml = new Doku_Renderer_xhtml();
$plugin = new syntax_plugin_indexmenu_indexmenu();
$null = new Doku_Handler();
$result = $plugin->handle($emptyindexsyntax, 0, 10, $null);
//no empty message
$plugin->render('xhtml', $xhtml, $result);
$doc = (new Document())->html($xhtml->doc);
$this->assertEquals(0, $doc->find('p')->count());
// Fill in empty message
$conf['plugin']['indexmenu']['empty_msg'] = 'This namespace is //empty//: {{ns}}';
$plugin->render('xhtml', $xhtml, $result);
$doc = (new Document())->html($xhtml->doc);
$this->assertEquals(1, $doc->find('p')->count());
// $this->assertEquals(1, $doc->find("p:contains($noexistns)")->count());
$this->assertEquals(1, $doc->find("p em")->count());
}
}
+561
View File
@@ -0,0 +1,561 @@
<?php
/**
* Indexmenu Action Plugin: Indexmenu Component.
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Samuele Tognini <samuele@samuele.netsons.org>
*/
use dokuwiki\Extension\ActionPlugin;
use dokuwiki\Extension\Event;
use dokuwiki\Extension\EventHandler;
use dokuwiki\plugin\indexmenu\Search;
use dokuwiki\Ui\Index;
/**
* Class action_plugin_indexmenu
*/
class action_plugin_indexmenu extends ActionPlugin
{
/**
* plugin should use this method to register its handlers with the dokuwiki's event controller
*
* @param EventHandler $controller DokuWiki's event controller object.
*/
public function register(EventHandler $controller)
{
if ($this->getConf('only_admins')) {
$controller->register_hook('IO_WIKIPAGE_WRITE', 'BEFORE', $this, 'removeSyntaxIfNotAdmin');
}
if ($this->getConf('page_index') != '') {
$controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'loadOwnIndexPage');
}
$controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'extendJSINFO');
$controller->register_hook('PARSER_CACHE_USE', 'BEFORE', $this, 'purgeCache');
if ($this->getConf('show_sort')) {
$controller->register_hook('TPL_CONTENT_DISPLAY', 'BEFORE', $this, 'showSortNumberAtTopOfPage');
}
$controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'ajaxCalls');
$controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'addStylesForSkins');
}
/**
* Check if user has permission to insert indexmenu
*
* @param Event $event
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
*/
public function removeSyntaxIfNotAdmin(Event $event)
{
global $INFO;
if (!$INFO['ismanager']) {
$event->data[0][1] = preg_replace("/{{indexmenu(|_n)>.+?}}/", "", $event->data[0][1]);
}
}
/**
* Add additional info to $JSINFO
*
* @param Event $event
*
* @author Gerrit Uitslag <klapinklapin@gmail.com>
* @author Samuele Tognini <samuele@samuele.netsons.org>
*/
public function extendJSINFO(Event $event)
{
global $INFO, $JSINFO;
$JSINFO['isadmin'] = (int)$INFO['isadmin'];
$JSINFO['isauth'] = isset($INFO['userinfo']) ? (int) $INFO['userinfo'] : 0;
}
/**
* Check for pages changes and eventually purge cache.
*
* @param Event $event
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
*/
public function purgeCache(Event $event)
{
global $ID;
global $conf;
global $INPUT;
global $INFO;
/** @var cache_parser $cache */
$cache = &$event->data;
if (!isset($cache->page)) return;
//purge only xhtml cache
if ($cache->mode != "xhtml") return;
//Check if it is an indexmenu page
if (!p_get_metadata($ID, 'indexmenu hasindexmenu')) return;
$aclcache = $this->getConf('aclcache');
if ($conf['useacl']) {
$newkey = false;
if ($aclcache == 'user') {
//Cache per user
if ($INPUT->server->str('REMOTE_USER')) {
$newkey = $INPUT->server->str('REMOTE_USER');
}
} elseif ($aclcache == 'groups') {
//Cache per groups
if (isset($INFO['userinfo']['grps'])) {
$newkey = implode('#', $INFO['userinfo']['grps']);
}
}
if ($newkey) {
$cache->key .= "#" . $newkey;
$cache->cache = getCacheName($cache->key, $cache->ext);
}
}
//Check if a page is more recent than purgefile.
if (@filemtime($cache->cache) < @filemtime($conf['cachedir'] . '/purgefile')) {
$event->preventDefault();
$event->stopPropagation();
$event->result = false;
}
}
/**
* Render a defined page as index.
*
* @param Event $event
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
*/
public function loadOwnIndexPage(Event $event)
{
if ('index' != $event->data) return;
if (!file_exists(wikiFN($this->getConf('page_index')))) return;
global $lang;
echo '<h1><a id="index">' . $lang['btn_index'] . "</a></h1>\n";
echo p_wiki_xhtml($this->getConf('page_index'));
$event->preventDefault();
$event->stopPropagation();
}
/**
* Display the indexmenu sort number.
*
* @param Event $event
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
*/
public function showSortNumberAtTopOfPage(Event $event)
{
global $ID, $ACT, $INFO;
if ($INFO['isadmin'] && $ACT == 'show') {
if ($n = p_get_metadata($ID, 'indexmenu_n')) {
echo '<div class="info">';
echo $this->getLang('showsort') . $n;
echo '</div>';
}
}
}
/**
* Handles ajax requests for indexmenu
*
* @param Event $event
*/
public function ajaxCalls(Event $event)
{
if ($event->data !== 'indexmenu') {
return;
}
//no other ajax call handlers needed
$event->stopPropagation();
$event->preventDefault();
global $INPUT;
switch ($INPUT->str('req')) {
case 'local':
//list themes
$this->getlocalThemes();
break;
case 'toc':
//print toc preview
if ($INPUT->has('id')) {
echo $this->printToc($INPUT->str('id'));
}
break;
case 'index':
//for dTree
//retrieval of data of the extra nodes for the indexmenu (if ajax loading set with max#m(#n)
if ($INPUT->has('idx')) {
echo $this->printIndex($INPUT->str('idx'));
}
break;
case 'fancytree':
//data for new index build with Fancytree
$this->getDataFancyTree();
break;
}
}
/**
* Handles ajax requests for FancyTree
*
* @return void
*/
private function getDataFancyTree()
{
global $INPUT;
$ns = $INPUT->str('ns', '');
$ns = rtrim($ns, ':');
//key of directory has extra : on the end
$level = -1; //opened levels. -1=all levels open
$max = 1; //levels to load by lazyloading. Before the default was 0. CHANGED to 1.
$skipFileCombined = [];
$skipNsCombined = [];
if ($INPUT->int('max') > 0) {
$max = $INPUT->int('max'); // max#n#m, if init: #n, otherwise #m
$level = $max;
}
if ($INPUT->int('level', -10) >= -1) {
$level = $INPUT->int('level');
}
$isInit = $INPUT->bool('init');
$currentPage = $INPUT->str('currentpage');
if ($isInit) {
$subnss = $INPUT->arr('subnss');
// if 'navbar' is enabled add current ns to list
if ($INPUT->bool('navbar')) {
$currentNs = getNS($currentPage);
if ($currentNs !== false) {
$subnss[] = [$currentNs, 1];
}
}
// alternative, via javascript.. https://wwwendt.de/tech/fancytree/doc/jsdoc/Fancytree.html#loadKeyPath
} else {
//not set via javascript at the moment.. ajax opens per level, so subnss has no use here
$subnss = $INPUT->str('subnss');
if ($subnss !== '') {
$subnss = [[cleanID($subnss), 1]];
}
}
$skipf = $INPUT->str('skipfile');
$skipFileCombined[] = $this->getConf('skip_file');
if (!empty($skipf)) {
$index = 0;
//prefix is '=' or '+'
if ($skipf[0] == '+') {
$index = 1;
}
$skipFileCombined[$index] = substr($skipf, 1);
}
$skipn = $INPUT->str('skipns');
$skipNsCombined[] = $this->getConf('skip_index');
if (!empty($skipn)) {
$index = 0;
//prefix is '=' or '+'
if ($skipn[0] == '+') {
$index = 1;
}
$skipNsCombined[$index] = substr($skipn, 1);
}
$opts = [
//only set for init, lazy requests equal to max
'level' => $level,
//nons only needed for init as it has no nested nodes
'nons' => $INPUT->bool('nons'),
'nopg' => $INPUT->bool('nopg'),
//init with complex array, empty if lazy loading
'subnss' => $subnss,
'max' => $max,
'skipnscombined' => $skipNsCombined,
'skipfilecombined' => $skipFileCombined,
'headpage' => $this->getConf('headpage'),
'hide_headpage' => $this->getConf('hide_headpage'),
];
$sort = [
'sort' => $INPUT->str('sort'),
'msort' => $INPUT->str('msort'),
'rsort' => $INPUT->bool('rsort'),
'nsort' => $INPUT->bool('nsort'),
'group' => $INPUT->bool('group'),
'hsort' => $INPUT->bool('hsort')
];
$opts['tempNew'] = true; //TODO temporary for recognizing treenew in the search function
$search = new Search($sort);
$data = $search->search($ns, $opts);
$fancytreeData = $search->buildFancytreeData($data, $isInit, $currentPage, $opts['nopg']);
//add eventually debug info
if ($isInit) {
//for lazy loading are other items than children not supported.
// $fancytreeData['opts'] = $opts;
// $fancytreeData['sort'] = $sort;
// $fancytreeData['debug'] = $data;
} else {
//returns only children, therefore, add debug info to first child
// $fancytreeData[0]['opts'] = $opts;
// $fancytreeData[0]['sort'] = $sort;
// $fancytreeData[0]['debug'] = $data;
}
header('Content-Type: application/json');
echo json_encode($fancytreeData);
}
/**
* Print a list of local themes
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
* @author Gerrit Uitslag <klapinklapin@gmail.com>
*/
private function getlocalThemes()
{
header('Content-Type: application/json');
$themebase = 'lib/plugins/indexmenu/images';
$handle = @opendir(DOKU_INC . $themebase);
$themes = [];
while (false !== ($file = readdir($handle))) {
if (
is_dir(DOKU_INC . $themebase . '/' . $file)
&& $file != "."
&& $file != ".."
&& $file != "repository"
&& $file != "tmp"
&& $file != ".svn"
) {
$themes[] = $file;
}
}
closedir($handle);
sort($themes);
echo json_encode([
'themebase' => $themebase,
'themes' => $themes
]);
}
/**
* Print a toc preview
*
* @param string $id
* @return string
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
* @author Andreas Gohr <andi@splitbrain.org>
*/
private function printToc($id)
{
$id = cleanID($id);
if (auth_quickaclcheck($id) < AUTH_READ) return '';
$meta = p_get_metadata($id);
$toc = $meta['description']['tableofcontents'] ?? [];
if (count($toc) > 1) {
//display ToC of two or more headings
$out = $this->renderToc($toc);
} else {
//display page abstract
$out = $this->renderAbstract($id, $meta);
}
return $out;
}
/**
* Return the TOC rendered to XHTML
*
* @param $toc
* @return string
*
* @author Andreas Gohr <andi@splitbrain.org>
* @author Gerrit Uitslag <klapinklapin@gmail.com>
*/
private function renderToc($toc)
{
global $lang;
$out = '<div class="tocheader">';
$out .= $lang['toc'];
$out .= '</div>';
$out .= '<div class="indexmenu_toc_inside">';
$out .= html_buildlist($toc, 'toc', [$this, 'formatIndexmenuListTocItem'], null, true);
$out .= '</div>';
return $out;
}
/**
* Return the page abstract rendered to XHTML
*
* @param $id
* @param array $meta by reference
* @return string
*/
private function renderAbstract($id, $meta)
{
$out = '<div class="tocheader">';
$out .= '<a href="' . wl($id) . '">';
$out .= $meta['title'] ? hsc($meta['title']) : hsc(noNS($id));
$out .= '</a>';
$out .= '</div>';
if ($meta['description']['abstract']) {
$out .= '<div class="indexmenu_toc_inside">';
$out .= p_render('xhtml', p_get_instructions($meta['description']['abstract']), $info);
$out .= '</div></div>';
}
return $out;
}
/**
* Callback for html_buildlist
*
* @param $item
* @return string
*/
public function formatIndexmenuListTocItem($item)
{
global $INPUT;
$id = cleanID($INPUT->str('id'));
if (isset($item['hid'])) {
$link = '#' . $item['hid'];
} else {
$link = $item['link'];
}
//prefix anchers with page id
if ($link[0] == '#') {
$link = wl($id, $link, false, '');
}
return '<a href="' . $link . '">' . hsc($item['title']) . '</a>';
}
/**
* Print index nodes
*
* @param $ns
* @return string
*
* @author Rene Hadler <rene.hadler@iteas.at>
* @author Samuele Tognini <samuele@samuele.netsons.org>
* @author Andreas Gohr <andi@splitbrain.org>
*/
private function printIndex($ns)
{
global $conf, $INPUT;
$idxm = new syntax_plugin_indexmenu_indexmenu();
$ns = $idxm->parseNs(rawurldecode($ns));
$level = -1;
$max = 0;
$data = [];
$skipfilecombined = [];
$skipnscombined = [];
if ($INPUT->int('max') > 0) {
$max = $INPUT->int('max');
$level = $max;
}
$nss = $INPUT->str('nss', '', true);
$sort['sort'] = $INPUT->str('sort', '', true);
$sort['msort'] = $INPUT->str('msort', '', true);
$sort['rsort'] = $INPUT->bool('rsort', false, true);
$sort['nsort'] = $INPUT->bool('nsort', false, true);
$sort['group'] = $INPUT->bool('group', false, true);
$sort['hsort'] = $INPUT->bool('hsort', false, true);
$search = new Search($sort);
$fsdir = "/" . utf8_encodeFN(str_replace(':', '/', $ns));
$skipf = utf8_decodeFN($INPUT->str('skipfile'));
$skipfilecombined[] = $this->getConf('skip_file');
if (!empty($skipf)) {
$index = 0;
if ($skipf[0] == '+') {
$index = 1;
}
$skipfilecombined[$index] = substr($skipf, 1);
}
$skipn = utf8_decodeFN($INPUT->str('skipns'));
$skipnscombined[] = $this->getConf('skip_index');
if (!empty($skipn)) {
$index = 0;
if ($skipn[0] == '+') {
$index = 1;
}
$skipnscombined[$index] = substr($skipn, 1);
}
$opts = [
'level' => $level,
'nons' => $INPUT->bool('nons', false, true),
'nss' => [[$nss, 1]],
'max' => $max,
'js' => false,
'nopg' => $INPUT->bool('nopg', false, true),
'skipnscombined' => $skipnscombined,
'skipfilecombined' => $skipfilecombined,
'headpage' => $idxm->getConf('headpage'),
'hide_headpage' => $idxm->getConf('hide_headpage')
];
if ($sort['sort'] || $sort['msort'] || $sort['rsort'] || $sort['hsort']) {
$search->customSearch($data, $conf['datadir'], [$search, 'searchIndexmenuItems'], $opts, $fsdir);
} else {
search($data, $conf['datadir'], [$search, 'searchIndexmenuItems'], $opts, $fsdir);
}
$out = '';
if ($INPUT->int('nojs') === 1) {
$idx = new Index();
$out_tmp = html_buildlist($data, 'idx', [$idxm, 'formatIndexmenuItem'], [$idx, 'tagListItem']);
$out .= preg_replace('/<ul class="idx">(.*)<\/ul>/s', "$1", $out_tmp);
} else {
$nodes = $idxm->builddTreeNodes($data, '', false);
$out = "ajxnodes = [";
$out .= rtrim($nodes[0], ",");
$out .= "];";
}
return $out;
}
/**
* Add Js & Css after template is displayed
*
* @param Event $event
*/
public function addStylesForSkins(Event $event)
{
// $event->data["link"][] = [
// "type" => "text/css",
// "rel" => "stylesheet",
// "href" => DOKU_BASE . "lib/plugins/indexmenu/scripts/fancytree/... etc etc"
// ];
// $event->data["link"][] = [
// "type" => "text/css",
// "rel" => "stylesheet",
// "href" => "//fonts.googleapis.com/icon?family=Material+Icons"
// ];
// $event->data["link"][] = [
// "type" => "text/css",
// "rel" => "stylesheet",
// "href" => "//code.getmdl.io/1.3.0/material.indigo-pink.min.css"
// ];
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
// phpcs:ignorefile
/**
* AJAX Backend for indexmenu
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
*/
//fix for Opera XMLHttpRequests
if ($_POST === [] && @$HTTP_RAW_POST_DATA) {
parse_str($HTTP_RAW_POST_DATA, $_POST);
}
require_once(DOKU_INC . 'inc/init.php');
require_once(DOKU_INC . 'inc/auth.php');
//close session
session_write_close();
$ajax_indexmenu = new ajax_indexmenu_plugin();
$ajax_indexmenu->render();
/**
* Class ajax_indexmenu_plugin
* @deprecated 2023-11 not used anymore
*/
class ajax_indexmenu_plugin
{
/**
* Output
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
*/
public function render()
{
$req = $_REQUEST['req'];
$succ = false;
//send the zip
if ($req == 'send' && isset($_REQUEST['t'])) {
include(DOKU_PLUGIN . 'indexmenu/inc/repo.class.php');
$repo = new repo_indexmenu_plugin();
$succ = $repo->sendTheme($_REQUEST['t']);
}
if ($succ) return;
header('Content-Type: text/html; charset=utf-8');
header('Cache-Control: public, max-age=3600');
header('Pragma: public');
if ($req === 'local') {
//required for admin.php
//list themes
echo $this->localThemes();
}
}
/**
* Print a list of local themes
* TODO: delete this funstion; copy of this function is already in action.php
* @author Samuele Tognini <samuele@samuele.netsons.org>
*/
public function localThemes()
{
$list = 'indexmenu,' . DOKU_URL . ",lib/plugins/indexmenu/images,";
$data = [];
$handle = @opendir(DOKU_PLUGIN . "indexmenu/images");
while (false !== ($file = readdir($handle))) {
if (
is_dir(DOKU_PLUGIN . 'indexmenu/images/' . $file)
&& $file != "."
&& $file != ".."
&& $file != "repository"
&& $file != "tmp"
&& $file != ".svn"
) {
$data[] = $file;
}
}
closedir($handle);
sort($data);
$list .= implode(",", $data);
return $list;
}
}
+44
View File
@@ -0,0 +1,44 @@
//The data-uri() links in skin-common.less break. Needs to be replaced by url(), DokuWiki can inline if needed
//moved from skin-common.less to here to prevent wrong prefixing and renamed from spin to spin-fancytree
@keyframes spin-fancytree {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}
//Mixins
// note: import of skin-common.less in the imported file below works only if skin-common.less is copied to EACH skin
// folder and referred from its ui.fancytree.less respectively.
.importSkin(@skin-foldername) {
&.@{skin-foldername} {
@import "scripts/fancytree/@{skin-foldername}/ui.fancytree.less";
//overwrite default variable: @fancy-image-prefix: "./skin-win8/"; the current less compressor does not update paths
//relative to lib/exe/(css.php), workaround DOKU_BASE not available in css
@fancy-image-prefix: "../plugins/indexmenu/scripts/fancytree/@{skin-foldername}/";
}
}
//wrap everything by plugin class to ensure its dominates default dokuwiki paddings etc.
.indexmenu_js2 {
//workaround needed for LESS processor of DokuWiki
.setBgImageUrl(@url) when not (@fancy-use-sprites) {}
.useSprite(@x, @y) when not(@fancy-use-sprites) {}
.importSkin(skin-awesome);
.importSkin(skin-bootstrap);
.importSkin(skin-bootstrap-n);
.importSkin(skin-lion);
.importSkin(skin-material);
.importSkin(skin-mdi);
.importSkin(skin-vista);
.importSkin(skin-win7);
.importSkin(skin-win8);
.importSkin(skin-xp);
.importSkin(skin-typicons);
}
+21
View File
@@ -0,0 +1,21 @@
<?php
/**
* Default configuration for indexmenu plugin
*
* @license: GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author: Samuele Tognini <samuele@samuele.netsons.org>
*/
$conf['defaultoptions'] = '';
$conf['only_admins'] = 0;
$conf['aclcache'] = 'groups';
$conf['headpage'] = ':start:,:same:,:inside:';
$conf['hide_headpage'] = 1;
$conf['page_index'] = '';
$conf['empty_msg'] = '';
$conf['skip_index'] = '';
$conf['skip_file'] = '';
$conf['show_sort'] = 1;
//$conf['themes_url'] = 'http://samuele.netsons.org/dokuwiki';
//$conf['be_repo'] = 0;
+21
View File
@@ -0,0 +1,21 @@
<?php
/**
* Configuration-manager metadata for indexmenu plugin
*
* @license: GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author: Samuele Tognini <samuele@samuele.netsons.org>
*/
$meta['defaultoptions'] = array('string');
$meta['only_admins'] = array('onoff','_caution' => 'warning');
$meta['aclcache'] = array('multichoice', '_choices' => array('none', 'user', 'groups'));
$meta['headpage'] = array('multicheckbox', '_choices' => array(':start:', ':same:', ':inside:'));
$meta['hide_headpage'] = array('onoff');
$meta['page_index'] = array('string', '_pattern' => '#^[a-z:]*#');
$meta['empty_msg'] = array('string');
$meta['skip_index'] = array('string', '_pattern' => '/^($|\/.*\/.*$)/');
$meta['skip_file'] = array('string', '_pattern' => '/^($|\/.*\/.*$)/');
$meta['show_sort'] = array('onoff');
//$meta['themes_url'] = array('string','_pattern' => '/^($|http:\/\/\S+$)/i');
//$meta['be_repo'] = array('onoff');
Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

@@ -0,0 +1,3 @@
author=Bernard JOUVE <office [at] hd-recording [dot] at>
url=
description=
Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

@@ -0,0 +1,3 @@
author=Andreas Neuhold <office [at] hd-recording [dot] at>
url=
description=
Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

@@ -0,0 +1,2 @@
author=Dric
description=Black and White PNG icons.
Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

@@ -0,0 +1,3 @@
author=Andreas Neuhold <office [at] hd-recording [dot] at>
url=
description=
Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

@@ -0,0 +1,2 @@
author=Samuele Tognini
url=http://samuele.netsons.org/dokuwiki
Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 861 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 B

Some files were not shown because too many files have changed in this diff Show More