*/
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/init.php');
require_once(DOKU_INC.'inc/cliopts.php');
/**
* Mindmap plugin.
*
*/
class syntax_plugin_mindmap extends DokuWiki_Syntax_Plugin {
/**
* Returns plugin information.
*
* @return array with plugin information.
*/
function getInfo() {
return array(
'author' => 'Jannes Drost-Tenfelde',
'email' => 'info@drost-tenfelde.de',
'date' => '2011-10-11',
'name' => 'mindmap',
'desc' => 'This plugin allows you to make mindmaps of your website.',
'url' => 'http://www.drost-tenfelde.de/?id=dokuwiki:plugins:mindmap',
);
}
/**
* Returns the syntax type of the plugin.
*
* @return type.
*/
function getType(){
return 'substition';
}
/**
* Returns the paragraph type.
*
* @return paragraph type.
*/
function getPType(){
return 'block';
}
/**
* Where to sort in?
*/
function getSort(){
return 301;
}
/**
* Connects the syntax pattern to the lexer.
*/
function connectTo($mode) {
$this->Lexer->addSpecialPattern('\{\{mindmap>[^}]*\}\}', $mode, 'plugin_mindmap');
}
/**
* Handles the matched pattern.
*
*/
function handle($match, $state, $pos, &$handler){
global $ID;
$info = $this->getInfo();
//strip markup from start and end
$match = substr($match, 10, -2);
// Assemble data
$data = array();
// Get namespaces and parameters
list($data['namespaces'], $parameters_string) = explode('#', $match);
$data['namespaces'] = str_replace('&', ',', $data['namespaces']);
parse_str($parameters_string, $params);
// Add the default values
$data['height'] = 0;
$data['width'] = 0;
$data['align'] = '';
$data['format'] = 'dot';
$data['depth'] = 3;
$data['include_media'] = 'none';
$data['use_cached_pages'] = 1;
// Get the parameters (key=value), seperated by &
$pairs = explode('&', $parameters_string);
// Turn the pairs into key=>value
foreach ($pairs as $pair) {
list($key, $value) = explode('=', $pair, 2);
$data[trim($key)] = trim($value);
}
// Turn all keys to lower case
$data = array_change_key_case($data, CASE_LOWER);
return $data;
}
/**
* Renders the output.
*
*/
function render($mode, &$renderer, $data) {
global $conf;
global $lang;
if ($mode == 'xhtml') {
// Get the path
$plugin_path = DOKU_BASE.'lib/plugins/mindmap/';
// Was a different location for the plugin set?
if ( $this->getConf('use_plugin_path') == 1) {
$conf_plugin_path = $this->getConf('plugin_path');
// Make sure the plugin path is set
if ( $conf_plugin_path != '' ) {
// Make sure there is a trailing /
if ( $conf_plugin_path[strlen($conf_plugin_path) - 1] != '/' ) {
$conf_plugin_path .= '/';
}
$plugin_path = $conf_plugin_path;
}
}
if ( $data['format'] == 'gexf') {
// Add a link to the GEXF XML file
$xml = $plugin_path.'xml.php?'.buildURLparams($data);
$renderer->doc .= '
'.$this->getLang('gexf_mindmap').'
';
}
else {
// Force dot format
$data['format'] = 'dot';
$img = $plugin_path.'img.php?'.buildURLparams($data);
if ( ($data['height'] != 0) || ($data['width'] != 0) ) {
// Add a link
$renderer->doc .= '';
}
// Add the image
$renderer->doc .= '
doc .= ' width="'.$data['width'].'"';
if($data['height']) $renderer->doc .= ' height="'.$data['height'].'"';
if($data['align'] == 'right') $renderer->doc .= ' align="right"';
if($data['align'] == 'left') $renderer->doc .= ' align="left"';
$renderer->doc .= '/>';
if ( ($data['height'] != 0) || ($data['width'] != 0) ) {
// Close the link
$renderer->doc .= '';
}
}
return true;
}
return false;
}
/**
* Wrapper which retruns the appropriate gathered data based on parameters.
*
* @param data plugin data
* @return gathered pages and media.
*/
function get_gathered_data( $data ) {
// Use cached pages?
$use_cached_pages = true;
if ( $data['use_cached_pages'] == 0 )
{
// Safeguard that cache is used if no namespaces were given
if ( ($data['namespaces'] == '') || ($data['namespaces'] == ':') ) {
$use_cached_pages = true;
}
else {
$use_cached_pages = false;
}
}
// Use first page header?
$use_first_header = false;
if ( $data['use_first_header'] == 1 ) {
$use_first_header = true;
}
//Make a namespace array
$namespaces = explode(',', $data['namespaces']);
// Gather page/media data
$gathered_data = $this->gather_data(
$namespaces, $data['depth'],
$data['include_media'],
$use_cached_pages,
$use_first_header
);
return $gathered_data;
}
/**
* Returns the GEXF xml file.
*
* @param data parameters.
*
* @return XML.
*/
function get_gexf_xml( $data ) {
global $conf;
$image = null;
$gathered_data = $this->get_gathered_data( $data );
$xml = $this->get_gexf( $gathered_data );
return $xml;
}
/**
* Returns the content of a graphviz image.
*
* @param data parameters.
*
* @return PNG image.
*/
function get_graphviz_image( $data ) {
global $conf;
$image = null;
$gathered_data = $this->get_gathered_data( $data );
$dot_input = $this->get_dot( $gathered_data );
// See if a manual path was given for graphviz
if ( $this->getConf('graphviz_path') ) {
// Local build
$cmd = $this->getConf('path');
$cmd .= ' -Tpng';
$cmd .= ' -K'.$data['layout'];
$cmd .= ' -o'.escapeshellarg($image); //output
$cmd .= ' '.escapeshellarg($dot_input); //input
exec($cmd, $image, $error);
if ($error != 0){
if($conf['debug']) {
dbglog(join("\n",$image),'mindmap command failed: '.$cmd);
}
return false;
}
}
else {
// Remote via google chart tools
$http = new DokuHTTPClient();
$http->timeout=30;
$pass = array();
$pass['cht'] = 'gv:'.$data['format'];
$pass['chl'] = $dot_input;
$image = $http->post('http://chart.apis.google.com/chart',$pass,'&');
if(!$image) return false;
}
return $image;
}
/**
* Searches a namespace for media files and adds them to the media array.
*
* @param media pre-initialised media array.
* @param ns namespace in which to search for media files
* @param depth Depth of the search.
*/
function get_media( &$media, $ns, $depth=0 )
{
global $conf;
$search_results = array();
// Search all media files within the namespace
search($search_results,
$conf['mediadir'],
'search_universal',
array (
'depth' => $depth,
'listfiles' => true,
'listdirs' => false,
'pagesonly' => false,
'skipacl' => true,
'keeptxt' => true,
'meta' => true,
),
// Only search within the namespace
str_replace(':', '/', $ns)
);
// Loop through the results
while( $item = array_shift($search_results) ) {
// Make a new media[id]=>array(title,size,ns,time) for the item
$media[$item['id']] = array(
'title' => noNS($item['id']),
'size' => $item['size'],
'ns' => getNS($item['id']),
'time' => $item['mtime'],
);
}
}
/**
* Adds all pages of a specific namespace to the pages array.
*
* @param pages pre-initialised pages array.
* @param ns Namespace in which to look for pages.
* @param depth Search depth.
* @param use_first_header (optional) Includes the first header as page title.
*/
function get_pages( &$pages, $ns, $depth=0, $use_first_header=false )
{
global $conf;
// find pages
$search_results = array();
search($search_results,
$conf['datadir'],
'search_universal',
array(
'depth' => $depth,
'listfiles' => true,
'listdirs' => false,
'pagesonly' => true,
'skipacl' => true,
'firsthead' => true,
'meta' => true,
),
str_replace(':','/',$ns)
);
// Start page of the namespace
if ($ns && page_exists($ns)) {
// Add to the search results
$search_results[] = array(
'id' => $ns,
'ns' => getNS($ns),
'title' => p_get_first_heading($ns, false),
'size' => filesize(wikiFN($ns)),
'mtime' => filemtime(wikiFN($ns)),
'perm' => 16,
'type' => 'f',
'level' => 0,
'open' => 1,
);
}
// loop through the pages
while($item = array_shift($search_results)) {
// Check that the user is allowed to read the page
if ( (auth_quickaclcheck($item['id']) > AUTH_READ) ) {
continue;
}
// Check that the user is allowed to read the page
if ( (auth_quickaclcheck($item['ns']) > AUTH_READ) ) {
continue;
}
// Get the create time
$time = (int) p_get_metadata($item['id'], 'date created', false);
if(!$time) $time = $item['mtime'];
// Get specific language part
$lang = ($transplugin)?$transplugin->getLangPart($item['id']):'';
if($lang) {
$item['ns'] = preg_replace('/^'.$lang.'(:|$)/','',$item['ns']);
}
if ( $use_first_header ) {
$title = $item['title'];
}
else {
// Use the last part of the id for the name
$title = ucwords( substr(strrchr(strtr($item['id'],'_',' '), ':'), 1 ) );
}
// Add the page to the page list
$pages[$item['id']] = array (
'title' => $title,
'ns' => $item['ns'],
'size' => $item['size'],
'time' => $time,
'links' => array(),
'media' => array(),
'lang' => $lang
);
}
}
/**
* Gathers all page and media data for given namespaces.
*
* @namespaces array() of namespaces
* @depth Search depth
* @include_media Determines if media should be regarded, Values: 'ns','all','none'.
* @use_cached_pages Determines if only cached pages should be used. If this option is turned off, the operation will cache all non-cached pages within the namespace.
* @use_first_header Determines if the first header is used for title of the pages.
*
* @return array with pages and media: array('pages'=>pages, 'media'=>media).
*/
function gather_data($namespaces, $depth=0, $include_media='none', $use_cached_pages=true, $use_first_header=false) {
global $conf;
$transplugin = plugin_load('helper','translation');
$pages = array();
$media = array();
// Loop through the namespaces
foreach ($namespaces as $ns) {
// Get the media of the namespace
if( $include_media == 'ns' ) {
$this->get_media( $media, $ns, $depth );
}
// Get the pages of the namespace
$this->get_pages( $pages, $ns, $depth, $use_first_header );
}
// Loop through the pages to get links and media
foreach($pages as $pid => $item){
// get instructions
$ins = p_cached_instructions(wikiFN($pid), $use_cached_pages, $pid);
// find links and media usage
foreach ($ins as $i) {
$mid = null;
// Internal link?
if ($i[0] == 'internallink') {
$id = $i[1][0];
$exists = true;
resolve_pageid($item['ns'],$id,$exists);
list($id) = explode('#',$id,2);
if($id == $pid) continue; // skip self references
if($exists && isset($pages[$id])){
$pages[$pid]['links'][] = $id;
}
if(is_array($i[1][1]) && $i[1][1]['type'] == 'internalmedia'){
$mid = $i[1][1]['src']; // image link
}else{
continue; // we're done here
}
}
if($i[0] == 'internalmedia') {
$mid = $i[1][0];
}
if(is_null($mid)) continue;
if($include_media == 'none') continue; // no media wanted
$exists = true;
resolve_mediaid($item['ns'],$mid,$exists);
list($mid) = explode('#',$mid,2);
$mid = cleanID($mid);
if($exists){
if($include_media == 'all'){
if (!isset($media[$mid])) { //add node
$media[$mid] = array(
'size' => filesize(mediaFN($mid)),
'time' => filemtime(mediaFN($mid)),
'ns' => getNS($mid),
'title' => noNS($mid),
);
}
$pages[$pid]['media'][] = $mid;
} elseif(isset($media[$mid])){
$pages[$pid]['media'][] = $mid;
}
}
}
// clean up duplicates
$pages[$pid]['links'] = array_unique($pages[$pid]['links']);
$pages[$pid]['media'] = array_unique($pages[$pid]['media']);
}
return array('pages'=>$pages, 'media'=>$media);
}
/**
* Create a Graphviz dot representation
*/
function get_dot(&$data ) {
$pages =& $data['pages'];
$media =& $data['media'];
$output = "digraph G {\n";
// create all nodes first
foreach($pages as $id => $page) {
$output .= " \"page-$id\" [shape=note, label=\"{$page['title']}\\n{$id}\", color=lightblue, fontname=Helvetica];\n";
}
foreach($media as $id => $item) {
$output .= " \"media-$id\" [shape=box, label=\"$id\", color=sandybrown, fontname=Helvetica];\n";
}
// now create all the links
foreach($pages as $id => $page){
foreach($page['links'] as $link){
$output .= " \"page-$id\" -> \"page-$link\" [color=navy];\n";
}
foreach($page['media'] as $link){
$output .= " \"page-$id\" -> \"media-$link\" [color=firebrick];\n";
}
}
$output .= "}\n";
return $output;
}
/**
* Create a GEXF representation
*/
function get_gexf(&$data){
$pages =& $data['pages'];
$media =& $data['media'];
$output = "\n";
$output .= "\n";
$output .= " \n";
$output .= " DokuWiki\n";
$output .= " \n";
$output .= " \n";
// define attributes
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " page|media\n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
// create all nodes first
$output .= " \n";
foreach($pages as $id => $item){
$title = htmlspecialchars($item['title']);
$lang = htmlspecialchars($item['lang']);
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
}
foreach($media as $id => $item){
$title = htmlspecialchars($item['title']);
$lang = htmlspecialchars($item['lang']);
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
$output .= " \n";
}
$output .= " \n";
// now create all the edges
$output .= " \n";
$cnt = 0;
foreach($pages as $id => $page){
foreach($page['links'] as $link){
$cnt++;
$output .= " \n";
}
foreach($page['media'] as $link){
$cnt++;
$output .= " \n";
}
}
$output .= " \n";
$output .= " \n";
$output .= "\n";
return $output;
}
}