1<?php 2/** 3 * DokuWiki Plugin DocNavigation (Syntax Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Gerrit Uitslag <klapinklapin@gmail.com> 7 */ 8 9use dokuwiki\Extension\SyntaxPlugin; 10 11/** 12 * Handles document navigation syntax 13 */ 14class syntax_plugin_docnavigation_pagenav extends SyntaxPlugin 15{ 16 17 /** 18 * Stores data of navigation per page (for preview) 19 * 20 * @var array with entries: 21 * '<pageid>' => [ 22 * 'previous' => [ 23 * 'link' => string, 24 * 'title' => null|string, 25 * 'rawlink' => string 26 * ], 27 * 'toc' => [...], 28 * 'next' => [...] 29 * ] 30 */ 31 public array $data = []; 32 33 /** 34 * Syntax Type 35 * 36 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 37 * 38 * @return string 39 */ 40 public function getType() 41 { 42 return 'substition'; 43 } 44 45 /** 46 * Paragraph Type 47 * 48 * Defines how this syntax is handled regarding paragraphs. This is important 49 * for correct XHTML nesting. Should return one of the following: 50 * 51 * 'normal' - The plugin can be used inside paragraphs 52 * 'block' - Open paragraphs need to be closed before plugin output 53 * 'stack' - Special case. Plugin wraps other paragraphs. 54 * 55 * @return string 56 * @see \dokuwiki\Parsing\Handler\Block 57 * 58 */ 59 public function getPType() 60 { 61 return 'block'; 62 } 63 64 /** 65 * Sort for applying this mode 66 * 67 * @return int 68 */ 69 public function getSort() 70 { 71 return 150; 72 } 73 74 /** 75 * @param string $mode 76 */ 77 public function connectTo($mode) 78 { 79 $this->Lexer->addSpecialPattern('<-[^\n]*\^[^\n]*\^[^\n]*->', $mode, 'plugin_docnavigation_pagenav'); 80 $this->Lexer->addSpecialPattern('<<[^\n]*\^[^\n]*\^[^\n]*>>', $mode, 'plugin_docnavigation_pagenav'); 81 } 82 83 /** 84 * Handler to prepare matched data for the rendering process 85 * 86 * Usually you should only need the $match param. 87 * 88 * @param string $match The text matched by the patterns 89 * @param int $state The lexer state for the match 90 * @param int $pos The character position of the matched text 91 * @param Doku_Handler $handler The Doku_Handler object 92 * @return array Return an array with all data you want to use in render, false don't add an instruction 93 */ 94 public function handle($match, $state, $pos, Doku_Handler $handler) 95 { 96 global $conf, $ID; 97 98 // links are: 0=previous, 1=toc, 2=next 99 $linkstrs = explode("^", substr($match, 2, -2), 3); 100 $links = []; 101 foreach ($linkstrs as $index => $linkstr) { 102 // Split title from URL 103 [$link, $title] = array_pad(explode('|', $linkstr, 2), 2, null); 104 if (isset($title) && preg_match('/^\{\{[^}]+}}$/', $title)) { 105 // If the title is an image, convert it to an array containing the image details 106 $title = Doku_Handler_Parse_Media($title); 107 } 108 109 $link = trim($link); 110 111 //look for an existing headpage when toc is empty 112 if ($index == 1 && empty($link)) { 113 $ns = getNS($ID); 114 if (page_exists($ns . ':' . $conf['start'])) { 115 // start page inside namespace 116 $link = $ns . ':' . $conf['start']; 117 } elseif (page_exists($ns . ':' . noNS($ns))) { 118 // page named like the NS inside the NS 119 $link = $ns . ':' . noNS($ns); 120 } elseif (page_exists($ns)) { 121 // page like namespace exists 122 $link = (!getNS($ns) ? ':' : '') . $ns; 123 } 124 } 125 //store original link with special chars and upper cases 126 $rawlink = $link; 127 128 // resolve and clean up the $id 129 // Igor and later 130 if (class_exists('dokuwiki\File\PageResolver')) { 131 $resolver = new dokuwiki\File\PageResolver($ID); 132 $link = $resolver->resolveId($link); 133 } else { 134 // Compatibility with older releases 135 resolve_pageid(getNS($ID), $link, $exists); 136 } 137 //ignore hash 138 [$link,$hash] = array_pad(explode('#', $link, 2), 2, ''); 139 140 //previous or next should not point to itself 141 if ($index !== 1 && $link == $ID) { 142 $link = ''; 143 } 144 145 $links[] = [ 146 'link' => $link, 147 'title' => $title, 148 'rawlink' => $rawlink, 149 'hash' => $hash 150 ]; 151 } 152 153 $data = [ 154 'previous' => $links[0], 155 'toc' => $links[1], 156 'next' => $links[2] 157 ]; 158 159 // store data for preview 160 $this->data[$ID] = $data; 161 162 // return instruction data for renderers 163 return $data; 164 } 165 166 /** 167 * Handles the actual output creation. 168 * 169 * @param string $format output format being rendered 170 * @param Doku_Renderer $renderer the current renderer object 171 * @param array $data data created by handler() 172 * @return boolean rendered correctly? (however, returned value is not used at the moment) 173 */ 174 public function render($format, Doku_Renderer $renderer, $data) 175 { 176 if ($format == 'metadata') { 177 /** @var Doku_Renderer_metadata $renderer */ 178 $renderer->meta['docnavigation'] = $data; 179 180 foreach ($data as $url) { 181 if ($url) { 182 if ($url['title'] === null) { 183 $defaulttitle = $renderer->_simpleTitle($url['rawlink']); 184 $url['title'] = $renderer->_getLinkTitle(null, $defaulttitle, $url['link']); 185 } 186 $renderer->internallink($url['link'], $url['title']); 187 } 188 } 189 return true; 190 } 191 192 return false; 193 } 194 195 /** 196 * Get data for a pageid 197 * 198 * @param string $pageId 199 * @return array|null 200 */ 201 public function getPageData(string $pageId): ?array 202 { 203 return $this->data[$pageId] ?? null; 204 } 205} 206