DokuWiki

It's better when it's simple

User Tools

Site Tools


plugin:pageindex

pageindex Plugin

Compatible with DokuWiki

No compatibility info given!

plugin Bulleted list of pages within a namespace

Last updated on
2021-03-17
Provides
Syntax

This extension has not been updated in over 2 years. It may no longer be maintained or supported and may have compatibility issues.

Similar to dir, nspages, nstoc, pagequery

Tagged with listing, menu, namespace, navigation

Notes on this Updated Version (2021-03-17)

The download link above is for the version released on 2006-08-01.

Below, I have added my 1.3 version, and I have also uploaded the code for syntax.php to github:

https://gist.github.com/jeffmikels/2b1d0948ffdbed600418a2492a98cfeb/archive/b569f90ab7a4498c06e1a6f30dd6e7439597c74d.zip

jeff [at] mikels [dot] cc

Usage

You can use one of four tag styles:

  • ~~PAGEINDEX=section:namespace;except1;except2,desc~~ ← Lists the specified namespace, excludes except1 and except2, sorts descending.
  • ~~PAGEINDEX=section:namespace;except1;except2~~ ← Lists the specified namespace, excludes except1 and except2, sorts ascending.
  • ~~PAGEINDEX=section:namespace~~ ← Lists the specified namespace.
  • ~~PAGEINDEX~~ ← Lists the current namespace.

I found that on the http://www.puzzlers.org/ website, we have sections which get lots of pages over a short time and cross-referencing was a chore since some users aren't as wiki-oriented as others. Now I just add a tag to each page and they automatically update over time.

The list will never include the current page, either, to reduce confusion.

NOTE: To keep your index up to date, you might want to add ~~NOCACHE~~ to pages where you use it.

Installation

Put the following code into lib/plugins/pageindex/syntax.php in your DokuWiki site. Then you can add the tags above into any page you want a namespace index.

The Code

Version 1.3

syntax.php
<?php
/**
 * Plugin page index: index table for pages in a name space
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Kite <Kite@puzzlers.org>
 * @based_on   "externallink" plugin by Otto Vainio <plugins@valjakko.net>
 */
 
if(!defined('DOKU_INC')) {
	define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
}
if(!defined('DOKU_PLUGIN')) {
	define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
}
 
require_once(DOKU_PLUGIN.'syntax.php');
require_once(DOKU_INC.'inc/search.php');
 
 
function search_list_index(&$data,$base,$file,$type,$lvl,$opts){
	global $ID;
	//we do nothing with directories
	if($type == 'd') return false;
	if(preg_match('#\.txt$#',$file)){
	//check ACL
		$id = pathID($file);
		if(auth_quickaclcheck($id) < AUTH_READ){
		return false;
	}
	if($opts['ns'].":$id" <> $ID) {
		$data[] = array( 
			'id'    => $opts['ns'].":$id",
			'type'  => $type,
			'level' => $lvl );
			}
	}
	return false;
}
 
 
 
 
/**
 * All DokuWiki plugins to extend the parser/rendering mechanism
 * need to inherit from this class
 */
class syntax_plugin_pageindex extends DokuWiki_Syntax_Plugin {
 
    /**
     * return some info
     */
    function getInfo(){
        return array(
            'author' => 'Kite',
            'email'  => 'kite@puzzlers.org',
            'date'   => '2009-02-01',
            'name'   => 'Page Index',
            'desc'   => 'Presents an index list of files in the current namespace',
            'url'    => 'http://www.dokuwiki.org/plugin:pageindex',
        );
    }
 
    /**
     * What kind of syntax are we?
     */
    function getType(){
        return 'substition';
    }
 
    // Just before build in links
    function getSort(){ return 299; }
 
    /**
     * What about paragraphs?
     */
    function getPType(){
        return 'block';
    }
 
	function connectTo($mode) {
       $this->Lexer->addSpecialPattern('~~PAGEINDEX[^~]*~~',$mode,'plugin_pageindex');
       //$this->Lexer->addSpecialPattern('~~PAGEINDEX~~',$mode,'plugin_pageindex');
    }
 
 
    /**
     * Handle the match
     */
    function handle($match, $state, $pos, &$handler){
    $match = preg_replace("%~~PAGEINDEX(=(.*))?~~%", "\\2", $match);
    //echo "\n\t<!-- syntax_plugin_pageindex.handle() found >> $match << -->\n";
        return $match;
    }
 
    /**
     * Create output
     */
    function render($mode, &$renderer, $data) {
        if($mode == 'xhtml'){
            $text=$this->_pageindex($renderer, $data);
            $renderer->doc .= $text;
            return true;
        }
        return false;
    }
 
 
	function _pageindex(&$renderer, $data) {
		global $conf;
		global $ID;
 
		//$renderer->doc .= "\n\n<!-- syntax_plugin_pageindex._pageindex(\$renderer, \"$data\") -->\n";
 
		/*
			MODIFIED BY JEFF MIKELS TO CHANGE THE WAY THE PARAMETERS ARE HANDLED
			~~PAGEINDEX[=namespace[;excluded-pages][,DESC]]~~
			where excluded-pages are a semicolon-separated list of pages to exclude
			and DESC (case insensitive) will reverse the sort order of the rendered index
		*/
		$dataparams = explode(',', $data);
		$parameters = explode(';', $dataparams[0]);
		$ns  = cleanID(getNS("$parameters[0]:dummy"));
 
		#fixme use appropriate function
		if(empty($ns)){
			$ns = dirname(str_replace(':',DIRECTORY_SEPARATOR,$ID));  // 2007/12/30 Kite - use localized constant
			if($ns == '.') $ns ='';
		}
		//$ns  = utf8_encodeFN(str_replace(':',DIRECTORY_SEPARATOR,$ns));   // 2007/12/30 Kite - use localized constant
		//$ns  = utf8_encodeFN($ns);
 
		$search_data = array();   // Oct 3, 2006 renamed $data to $search_data for clarity
		$dir = $conf['datadir']. DIRECTORY_SEPARATOR .str_replace(':',DIRECTORY_SEPARATOR,$ns);   // 2007/12/30 Kite - use localized constant
		$ns = str_replace(DIRECTORY_SEPARATOR,':',$ns);
		$renderer->doc .= "\n<!-- \$dir = $dir  \$ns = $ns -->\n";
		search($search_data,     // results   == renamed $data to $search_data
			$dir,                  // folder root
			'search_list_index',   // handler
			array('ns' => $ns)     // options
		);
 
		$checked = [];
 
		// Remove the items not wanted in the list
		if(is_array($parameters)) {
			$skipitems = array_slice($parameters, 1);
			foreach($search_data as $item) {
				$found = false;
				// Add ns if user didn't
				foreach($skipitems as $skip) {
					$skip = strpos($skip,":") ? $skip : "$ns:$skip"; 
					if($item['id'] == $skip) {
						$found = true;
						break;
					}
				}
				if(!$found) {
					// Pass this one through
					$checked[] = $item;
				} else {
					//$renderer->doc .= "<!-- rejected entry ".$item['id']." -->\n";
				}
			}
		}
 
		// use the filtered data rather than $search_data
		if(count($checked)) {
 
			// sort properly
			if (!empty($dataparams[1]) && strtolower($dataparams[1]) == 'desc') {
				$checked = array_reverse($checked);
			}
 
/* Option to use an HTML List */
			$renderer->doc .= html_buildlist($checked,
				'idx',
				'html_list_index',
				'html_li_index'
			);
 
/* Option to use the PageList plugin */
/*
			$pages = $checked;
			$pagelist =& plugin_load('helper', 'pagelist');
			if (!$pagelist) return false; // failed to load plugin
			$pagelist->startList();
			foreach ($pages as $page){
				$pagelist->addPage($page);
			}
			$renderer->doc .= $pagelist->finishList();
*/
		} else {
			$renderer->doc .= "\n\t<p>There are no documents to show.</p>\n";
		}
	} // _pageindex()
} // syntax_plugin_pageindex

Version 1.2

syntax.php
<?php
/**
 * Plugin page index: index table for pages in a name space
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Kite <Kite@puzzlers.org>
 * @based_on   "externallink" plugin by Otto Vainio <plugins@valjakko.net>
 */
 
if(!defined('DOKU_INC')) {
	define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
}
if(!defined('DOKU_PLUGIN')) {
	define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
}
 
require_once(DOKU_PLUGIN.'syntax.php');
require_once(DOKU_INC.'inc/search.php');
 
 
function search_list_index(&$data,$base,$file,$type,$lvl,$opts){
	global $ID;
	//we do nothing with directories
	if($type == 'd') return false;
	if(preg_match('#\.txt$#',$file)){
	//check ACL
		$id = pathID($file);
		if(auth_quickaclcheck($id) < AUTH_READ){
		return false;
	}
	if($opts['ns'].":$id" <> $ID) {
		$data[] = array( 
			'id'    => $opts['ns'].":$id",
			'type'  => $type,
			'level' => $lvl );
			}
	}
	return false;
}
 
 
 
 
/**
 * All DokuWiki plugins to extend the parser/rendering mechanism
 * need to inherit from this class
 */
class syntax_plugin_pageindex extends DokuWiki_Syntax_Plugin {
 
    /**
     * return some info
     */
    function getInfo(){
        return array(
            'author' => 'Kite',
            'email'  => 'kite@puzzlers.org',
            'date'   => '2009-02-01',
            'name'   => 'Page Index',
            'desc'   => 'Presents an index list of files in the current namespace',
            'url'    => 'http://www.dokuwiki.org/plugin:pageindex',
        );
    }
 
    /**
     * What kind of syntax are we?
     */
    function getType(){
        return 'substition';
    }
 
    // Just before build in links
    function getSort(){ return 299; }
 
    /**
     * What about paragraphs?
     */
    function getPType(){
        return 'block';
    }
 
	function connectTo($mode) {
       $this->Lexer->addSpecialPattern('~~PAGEINDEX[^~]*~~',$mode,'plugin_pageindex');
       //$this->Lexer->addSpecialPattern('~~PAGEINDEX~~',$mode,'plugin_pageindex');
    }
 
 
    /**
     * Handle the match
     */
    function handle($match, $state, $pos, &$handler){
		$match = preg_replace("%~~PAGEINDEX(=(.*))?~~%", "\\2", $match);
		//echo "\n\t<!-- syntax_plugin_pageindex.handle() found >> $match << -->\n";
        return $match;
    }
 
    /**
     * Create output
     */
    function render($mode, &$renderer, $data) {
        if($mode == 'xhtml'){
            $text=$this->_pageindex($renderer, $data);
            $renderer->doc .= $text;
            return true;
        }
        return false;
    }
 
 
	function _pageindex(&$renderer, $data) {
		global $conf;
		global $ID;
 
		//$renderer->doc .= "\n\n<!-- syntax_plugin_pageindex._pageindex(\$renderer, \"$data\") -->\n";
		$parameters = split(';', $data);
		$ns  = cleanID(getNS("$parameters[0]:dummy"));
		#fixme use appropriate function
		if(empty($ns)){
			$ns = dirname(str_replace(':',DIRECTORY_SEPARATOR,$ID));  // 2007/12/30 Kite - use localized constant
			if($ns == '.') $ns ='';
		}
		//$ns  = utf8_encodeFN(str_replace(':',DIRECTORY_SEPARATOR,$ns));   // 2007/12/30 Kite - use localized constant
		//$ns  = utf8_encodeFN($ns);
 
		$search_data = array();   // Oct 3, 2006 renamed $data to $search_data for clarity
		$dir = $conf['datadir']. DIRECTORY_SEPARATOR .str_replace(':',DIRECTORY_SEPARATOR,$ns);   // 2007/12/30 Kite - use localized constant
		$ns = str_replace(DIRECTORY_SEPARATOR,':',$ns);
		$renderer->doc .= "\n<!-- \$dir = $dir  \$ns = $ns -->\n";
		search($search_data,          // results   == renamed $data to $search_data
			$dir,                  // folder root
			'search_list_index',   // handler
			array('ns' => $ns));   // options
		// Remove the items not wanted in the list
		if(is_array($parameters)) {
			$skipitems = array_slice($parameters, 1);
			foreach($search_data as $item) {
				$found = false;
                                // Add ns if user didn't
                                foreach($skipitems as $skip) {
                                    $skip = strpos($skip,":") ? $skip : "$ns:$skip"; 
                                    if($item['id'] == $skip) {
                                        $found = true;
                                        break;
				    }
				}
				if(!$found) {
					// Pass this one through
					$checked[] = $item;
				} else {
					//$renderer->doc .= "<!-- rejected entry ".$item['id']." -->\n";
				}
			}
		}
 
		if(count($checked)) {  // use the filtered data rather than $search_data
/* Option to use an HTML List */
 
			$renderer->doc .= html_buildlist($checked,
				'idx',
				'html_list_index',
				'html_li_index');
 
/* Option to use the PageList plugin */
/*
                        $pages = $checked;
                        $pagelist =& plugin_load('helper', 'pagelist');
                        if (!$pagelist) return false; // failed to load plugin
                        $pagelist->startList();
                        foreach ($pages as $page){
                            $pagelist->addPage($page);
                        }
                        $renderer->doc .= $pagelist->finishList();
*/
		} else {
			$renderer->doc .= "\n\t<p>There are no documents to show.</p>\n";
		}
	} // _pageindex()
} // syntax_plugin_pageindex

Version 1

syntax.php
<?php
/**
 * Plugin page index: index table for pages in a name space
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Kite <Kite@puzzlers.org>
 * @based_on   "externallink" plugin by Otto Vainio <plugins@valjakko.net>
 */
 
if(!defined('DOKU_INC')) {
	define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
}
if(!defined('DOKU_PLUGIN')) {
	define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
}
 
require_once(DOKU_PLUGIN.'syntax.php');
require_once(DOKU_INC.'inc/search.php');
 
 
function search_list_index(&$data,$base,$file,$type,$lvl,$opts){
	global $ID;
	//we do nothing with directories
	if($type == 'd') return false;
	if(preg_match('#\.txt$#',$file)){
	//check ACL
		$id = pathID($file);
		if(auth_quickaclcheck($id) < AUTH_READ){
		return false;
	}
	if($opts['ns'].":$id" <> $ID) {
		$data[] = array( 
			'id'    => $opts['ns'].":$id",
			'type'  => $type,
			'level' => $lvl );
			}
	}
	return false;
}
 
 
 
 
/**
 * All DokuWiki plugins to extend the parser/rendering mechanism
 * need to inherit from this class
 */
class syntax_plugin_pageindex extends DokuWiki_Syntax_Plugin {
 
    /**
     * return some info
     */
    function getInfo(){
        return array(
            'author' => 'Kite',
            'email'  => 'kite@puzzlers.org',
            'date'   => '2006-08-01',
            'name'   => 'Page Index',
            'desc'   => 'Presents an index list of files in the current namespace',
            'url'    => 'http://www.dokuwiki.org/plugin:pageindex',
        );
    }
 
    /**
     * What kind of syntax are we?
     */
    function getType(){
        return 'substition';
    }
 
    // Just before build in links
    function getSort(){ return 299; }
 
    /**
     * What about paragraphs?
     */
    function getPType(){
        return 'block';
    }
 
	function connectTo($mode) {
       $this->Lexer->addSpecialPattern('~~PAGEINDEX[^~]*~~',$mode,'plugin_pageindex');
       //$this->Lexer->addSpecialPattern('~~PAGEINDEX~~',$mode,'plugin_pageindex');
    }
 
 
    /**
     * Handle the match
     */
    function handle($match, $state, $pos, &$handler){
		$match = preg_replace("%~~PAGEINDEX(=(.*))?~~%", "\\2", $match);
		//echo "\n\t<!-- syntax_plugin_pageindex.handle() found >> $match << -->\n";
        return $match;
    }
 
    /**
     * Create output
     */
    function render($mode, &$renderer, $data) {
        if($mode == 'xhtml'){
            $text=$this->_pageindex($renderer, $data);
            $renderer->doc .= $text;
            return true;
        }
        return false;
    }
 
 
	function _pageindex(&$renderer, $data) {
		global $conf;
		global $ID;
 
		//$renderer->doc .= "\n\n<!-- syntax_plugin_pageindex._pageindex(\$renderer, \"$data\") -->\n";
// New Oct 3, 2006 -- begin
		$parameters = split(';', $data);
// New Oct 3, 2006 -- end
		$ns  = cleanID(getNS("$data:dummy"));
		#fixme use appropriate function
		if(empty($ns)){
			$ns = dirname(str_replace(':',DIRECTORY_SEPARATOR,$ID));  // 2007/12/30 Kite - use localized constant
			if($ns == '.') $ns ='';
		}
		//$ns  = utf8_encodeFN(str_replace(':',DIRECTORY_SEPARATOR,$ns));   // 2007/12/30 Kite - use localized constant
		//$ns  = utf8_encodeFN($ns);
 
		$search_data = array();   // Oct 3, 2006 renamed $data to $search_data for clarity
		$dir = $conf['datadir']. DIRECTORY_SEPARATOR .str_replace(':',DIRECTORY_SEPARATOR,$ns);   // 2007/12/30 Kite - use localized constant
		$ns = str_replace(DIRECTORY_SEPARATOR,':',$ns);
		$renderer->doc .= "\n<!-- \$dir = $dir  \$ns = $ns -->\n";
		search($search_data,          // results   == renamed $data to $search_data
			$dir,                  // folder root
			'search_list_index',   // handler
			array('ns' => $ns));   // options
// New Oct 3, 2006 -- begin
		// Remove the items not wanted in the list
		if(is_array($parameters)) {
			$skipitems = array_slice($parameters, 1);
			foreach($search_data as $item) {
				$found = false;
				foreach($skipitems as $skip) {
                                        // Suggested fix: ensures the current namespace
                                        $skip = strpos($skip,":") ? $skip : "$ns:$skip"; // Add ns if user didn't
					if($item['id'] == $skip) {
						$found = true; 
						break;
					}
				}
				if(!$found) {
					// Pass this one through
					$checked[] = $item;
				} else {
					//$renderer->doc .= "<!-- rejected entry ".$item['id']." -->\n";
				}
			}
		}
 
		if(count($checked)) {  // use the filtered data rather than $search_data
			$renderer->doc .= html_buildlist($checked,
				'idx',
				'html_list_index',
				'html_li_index');
		} else {
			$renderer->doc .= "\n\t<p>There are no documents to show.</p>\n";
		}
 
// New Oct 3, 2006 -- end
 
//		if(count($search_data)) {  // renamed $data to $search_data
//			$renderer->doc .= html_buildlist($search_data,
//				'idx',
//				'html_list_index',
//				'html_li_index');
//		} else {
//			$renderer->doc .= "\n\t<p>There are no documents to show.</p>\n";
//		}
		//$renderer->doc .= "\n<!-- leaving syntax_plugin_pageindex._pageindex() -->\n";
	} // _pageindex()
} // syntax_plugin_pageindex

Updates

March 17, 2021

I added another option to the pageindex code to change the page sorting of the generated indexes.

You can now write your pageindex code like this:

~~pageindex=namespace;except1;except2,desc~~

to get all the pages in namespace other than except1 or except2 and sort the results in descending alphabetical order.
jeff [at] mikels [dot] cc

Oct 3, 2006

I added a bit of code into the “meat” function that performs a neat bit for me. I want to use a syntax of “show me everything except one or two. That is, I have a default “start” or “index” page that has no real content except a ~~pageindex~~ macro. I want to show all the pages except that start page from other pages. Now I can use: ~~pageindex=namespace;except1;except2~~ to get all the pages in namespace other than except1 or except2.
kite [at] puzzlers [dot] org

Dec 30, 2007

I changed references to '/' to DIRECTORY_SEPARATOR per the 'possible bug' comment below.
kite [at] puzzlers [dot] org

Discussion

Listing of subNamespace

I need also a listing of the namespace under the current namespace. How i have to modify the code? Can you show me an example. Thanks, Johann.

*** syntax.php  2006/12/16 19:27:01     1.1
--- syntax.php  2006/12/16 19:37:06
***************
*** 21,27 ****
  function search_list_index(&$data,$base,$file,$type,$lvl,$opts){
        global $ID;
        //we do nothing with directories
!       if($type == 'd') return false;
        if(preg_match('#\.txt$#',$file)){
        //check ACL
                $id = pathID($file);
--- 21,35 ----
  function search_list_index(&$data,$base,$file,$type,$lvl,$opts){
        global $ID;
        //we do nothing with directories
!       if($type == 'd') {
!               $id = pathID($file);
!               if($opts['ns'].":$id" <> $ID) {
!                       $data[] = array( 
!                               'id'    => $opts['ns'].":$id",
!                               'type'  => $type,
!                               'level' => $lvl );
!               }
!       }
        if(preg_match('#\.txt$#',$file)){
        //check ACL
                $id = pathID($file);

Possible bug?

Thanks for the plugin, it's very handy. But I have to say I guess I found a bug. If working on Windows (I test my websites on Windows but host them on Linux/FreeBSD then) the hard-coded slashes in the code (I mean like that: /) cause the bug. Windows puts backslashes in directory path and the plugin, quite contrary, adds slashes. It works OK until no arguments are specified (just ~~PAGEINDEX~~), but if something like ~~PAGEINDEX=dev;sidebar~~ is tried then it starts telling there are no documents to index. Please replace ALL hard-coded slashes to something platform-independent. Regards, Webmaster.

2007/12/30 I've altered the lines with '/' constants to use the DIRECTORY_SEPARATOR constant so paths should now be more platform independent.
kite [at] puzzlers [dot] org


User Comments

We had to replace the line

// New Oct 3, 2006 -- end
$ns  = cleanID(getNS("$data:dummy"));

with

// New Oct 3, 2006 -- end
$ns  = cleanID(getNS("{$parameters[0]}:dummy"));

to make the exclude function work. We also had to specify the full pagename including namespace: prefix to actually exclude the page.

Rüdiger Marwein / Roland Eckert / 21torr.com

Solution to last thing above

One way to avoid having to prefix the name space, is to add one line to the $skipitems loop.

Before snippet:
            foreach($skipitems as $skip) {
               if($item['id'] == $skip) {
                  $found = true;
                  break;


After snippet:

            foreach($skipitems as $skip) {
               $skip = strpos($skip,":") ? $skip : "$ns:$skip"; // Add ns if user didn't
               if($item['id'] == $skip) {
                  $found = true;
                  break;


Henrik Andreasson.


Bug: after copying the script from the page above I got a PHP error on my next “save page” click. I had to remove the ?> part of the last line of the script and it functioned since.

It is reported here about this problem in general: php_closing_tags - Stephen (stephen-dot-leedle-at-gmx-dot-de)


I prefer the pagelist plugin for rendering. It's easy to add : Just replace theses lines :

			$renderer->doc .= html_buildlist($checked,
				'idx',
				'html_list_index',
				'html_li_index');

by

            $pages = $checked;
            $pagelist =& plugin_load('helper', 'pagelist');
            if (!$pagelist) return false; // failed to load plugin
            $pagelist->startList();
            foreach ($pages as $page){
                $pagelist->addPage($page);
            }
            $renderer->doc .= $pagelist->finishList();

You can set flags too with

            $pagelist->setFlags(array("nouser", "desc")); // Just after $pagelist->startList();

I don't know why, but it don't work with “tag” and “comments” flags. Any idea ? (I have both plugins installed)

Jean-Michel 2008-03-02 18:26


:!: Possible bug

I really like this plugin, but I think I've found a bug. When I create a new page in a namespace, it doesn't appear on the list created by the plugin, but if I make a preview of the page it does appear.

May it be a problem of refreshing?

Ayla 2008-04-06 18:40

try to to a on the page with the ~~PAGEINDEX~~ . Every Page is cached in plain-HTML for a given time if not modified or with that tag.


:!: It's not a bug, it's a feature

If you use some templates that make a sitemap on the right or on the left … beware of redefinition of function search_list_index() workaround is vi regexp in the pasted syntax.php :

:%s/search_list_index/search_list_index_whateveryouwant/g 

:!: Possible bug: No unicode (cyrillics, for example) support.


:!: Possible bug: Not obeying config:hidepages.

plugin/pageindex.txt · Last modified: 2023-12-16 23:13 by Aleksandr

Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Share Alike 4.0 International
CC Attribution-Share Alike 4.0 International Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki