1<?php 2/** 3 * DokuWiki Plugin imapmarkers (Syntax Component) 4 * 5 * @license MIT https://en.wikipedia.org/wiki/MIT_License 6 * @author Kai Thoene <k.git.thoene@gmx.net> 7 */ 8if (!defined('DOKU_INC')) { 9 die(); 10} 11 12#declare(strict_types=1); 13 14use dokuwiki\Extension\SyntaxPlugin; 15use dokuwiki\Logger; 16 17include(dirname(__FILE__) . "/imapmarkers_simple_html_dom.php"); 18 19 20class syntax_plugin_imapmarkers_map extends \dokuwiki\Extension\SyntaxPlugin { 21 private const MATCH_IS_UNKNOWN = 0; 22 private const MATCH_IS_AREA = 1; 23 private const MATCH_IS_CONFIG = 2; 24 private const MATCH_IS_LOCATION = 3; 25 26 private int $nr_imagemap_handler; 27 private int $nr_imagemap_render; 28 private array $a_areas; 29 private array $a_cfg; 30 private bool $is_debug; 31 private string $component; 32 33 function __construct() { 34 $this->is_debug = false; 35 global $ID; 36 if ($this->is_debug) { 37 dbglog(sprintf("syntax_plugin_imapmarkers_map.__construct ID='%s' PLUGIN='%s'", cleanID($ID), $this->getPluginName())); 38 } 39 $this->nr_imagemap_handler = -1; 40 $this->nr_imagemap_render = -1; 41 $this->a_areas = array(); 42 $this->a_cfg = array(); 43 $this->component = sprintf("plugin_%s_%s", $this->getPluginName(), $this->getPluginComponent()); 44 } 45 46 public function getType() { 47 return 'container'; 48 } 49 public function getPType() { 50 return 'block'; 51 } 52 public function getSort() { 53 return 185; 54 } 55 public function getAllowedTypes() { 56 return array('formatting', 'substition', 'disabled', 'protected', 'container', 'paragraphs'); 57 } 58 59 /** 60 * Connect pattern to lexer 61 */ 62 public function connectTo($mode) { 63 if ($mode == "base") { 64 $this->Lexer->addEntryPattern('\{{2}(?i)IMAPMARKERS>[^\}]+\}{2}', $mode, $this->component); 65 $this->Lexer->addPattern('\s*\{{2}(?i)CFG>\}{2}.*?\{{2}<CFG\s*\}{2}\s*', $this->component); 66 $this->Lexer->addPattern('\s*\[{2}.+?\]{2}\s*', $this->component); 67 } 68 } 69 70 /** 71 * Connect exit pattern to lexer 72 */ 73 public function postConnect() { 74 $this->Lexer->addExitPattern('\{{2}<(?i)IMAPMARKERS\}{2}', $this->component); 75 } 76 77 /** 78 * Handle the match 79 */ 80 public function handle($match, $state, $pos, Doku_Handler $handler) { 81 global $conf; 82 global $ID; 83 $args = array($state); 84 switch ($state) { 85 case DOKU_LEXER_ENTER: 86 $this->nr_imagemap_handler++; 87 if ($this->is_debug) { 88 dbglog(sprintf("syntax_plugin_imapmarkers_map.handle::DOKU_LEXER_ENTER: [%d] MATCH='%s' HANDLER='%s'", $this->nr_imagemap_handler, $match, substr($match, 14, -2))); 89 } 90 $img = Doku_Handler_Parse_Media(substr($match, 14, -2)); 91 if ($this->is_debug) { 92 dbglog(sprintf("syntax_plugin_imapmarkers_map.handle::DOKU_LEXER_ENTER: [%d] IMG='%s'", $this->nr_imagemap_handler, $img)); 93 } 94 $args = array($state, $img['type'], $img['src'], $img['title'], $img['align'], $img['width'], $img['height'], $img['cache']); 95 if ($this->is_debug) { 96 dbglog(sprintf("syntax_plugin_imapmarkers_map.handle::DOKU_LEXER_ENTER: ARGS=[ %s ]"), implode(", ", $args)); 97 } 98 break; 99 case DOKU_LEXER_MATCHED: 100 $is_correct = false; 101 $is_match_ok = false; 102 $err_msg = ""; 103 $matches = array(); 104 $match = trim($match); 105 if ($this->is_debug) { 106 dbglog(sprintf("syntax_plugin_imapmarkers_map.handle::DOKU_LEXER_MATCHED: [%d] MATCH='%s' POS=%s", $this->nr_imagemap_handler, $match, $pos)); 107 } 108 //---------- 109 // check for area with or without identifier: 110 if (preg_match("/\[{2}\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*@\s*([\d,\s]+)\s*\]{2}/", $match, $matches) 111 or preg_match("/\[{2}\s*([^|]*?)\s*\|\s*([^|]*?)\s*@\s*([\d,\s]+)\s*\]{2}/", $match, $matches)) { 112 switch (count($matches)) { 113 case 5: // with identifier. 114 $link = $matches[1]; 115 $loc_id = $matches[2]; 116 $text = $matches[3]; 117 $coordinates = $matches[4]; 118 $is_match_ok = true; 119 break; 120 case 4: // without identifier. 121 $link = $matches[1]; 122 $loc_id = ""; 123 $text = $matches[2]; 124 $coordinates = $matches[3]; 125 $is_match_ok = true; 126 break; 127 default: 128 $err_msg = sprintf("Invalid area! AREA='%s'", $match); 129 } 130 if ($is_match_ok) { 131 $a_coords = explode(",", trim(strval($coordinates))); 132 foreach ($a_coords as $key => $value) { 133 $a_coords[$key] = intval(trim(strval($value))); 134 } 135 $num_coords = count($a_coords); 136 switch ($num_coords) { 137 case 3: 138 case 4: 139 case 6: 140 $is_correct = true; 141 break; 142 default: 143 if ((count($a_coords) >= 6) and ((count($a_coords) % 2) == 0)) { 144 $is_correct = true; 145 break; 146 } 147 $err_msg = sprintf("Invalid number of coordinates! COUNT=%d", count($a_coords)); 148 } 149 $a_coords_s = $is_correct ? implode(",", $a_coords) : "0,0,0,0,0,0"; 150 $num_coords = $is_correct ? $num_coords : 6; 151 $uri = $link; 152 $classes = ""; 153 if ($link != "") { 154 // analyse link. 155 $dokuwiki_link = sprintf("[[%s|%s]]", $link, $text); 156 $rendered_result = $this->render_text($dokuwiki_link); 157 $dom = imapmarkers\str_get_html($rendered_result); 158 $a = $dom->find('a', 0); 159 $uri = $a->href; 160 $classes = $a->class; 161 } 162 if ($this->is_debug) { 163 dbglog(sprintf("syntax_plugin_imapmarkers_map.handle::DOKU_LEXER_MATCHED: URL='%s' CLASS='%s'", $uri, $classes)); 164 } 165 $args = array($state, self::MATCH_IS_AREA, $is_correct, $err_msg, $link, $loc_id, $text, $num_coords, $a_coords_s, $uri, $classes); 166 } 167 break; 168 } else { 169 if (preg_match("/^\{{2}(?i)CFG>\}{2}\s*(.*?)\s*\{{2}<CFG\s*\}{2}$/s", $match, $matches)) { 170 if (count($matches) == 2) { 171 $cfg = $matches[1]; 172 // test JSON from configuration: 173 if (json_decode($cfg)) { 174 $is_correct = true; 175 $args = array($state, self::MATCH_IS_CONFIG, $is_correct, $err_msg, $cfg); 176 break; 177 } else { 178 $err_msg = sprintf("Invalid JSON in configuration! JSON='%s'", $cfg); 179 } 180 } else { 181 $err_msg = sprintf("Invalid configuration! CONFIG='%s'", $match); 182 } 183 184 } else { 185 $err_msg = sprintf("Invalid expression! EXPRESSION='%s'", $match); 186 } 187 } 188 $args = array($state, self::MATCH_IS_UNKNOWN, $is_correct, $err_msg); 189 break; 190 } 191 return $args; 192 } // public function handle 193 194 /** 195 * Create output 196 */ 197 public function render($mode, Doku_Renderer $renderer, $data) { 198 if ($mode == 'xhtml') { 199 global $conf; 200 global $ID; 201 $state = $data[0]; 202 static $has_content = false; 203 switch ($state) { 204 case DOKU_LEXER_ENTER: 205 $this->nr_imagemap_render++; 206 if ($this->is_debug) { 207 dbglog(sprintf("syntax_plugin_imapmarkers_map.render::DOKU_LEXER_ENTER: [%d] DATA='%s'", $this->nr_imagemap_render, implode(", ", $data))); 208 } 209 list($state, $type, $src, $title, $align, $width, $height, $cache) = $data; 210 if ($type == 'internalmedia') { 211 $exists = null; 212 resolve_mediaid(getNS($ID), $src, $exists); 213 } 214 $renderer->doc .= sprintf('<p id="imapmarkers-container-%d" class="imapmarkers imapmarkers-container">%s', $this->nr_imagemap_render, DOKU_LF); 215 $src = ml($src, array('w' => $width, 'h' => $height, 'cache' => $cache)); 216 $renderer->doc .= sprintf(' <img src="%s" id="imapmarkers-img-%d" class="imapmarkers imapmarkers-image media%s imap" usemap="#imapmarkers-map-%d"', $src, $this->nr_imagemap_render, $align, $this->nr_imagemap_render); 217 if ($align == 'right' || $align == 'left') 218 $renderer->doc .= sprintf(' align="%s"', $align); 219 if (!is_null($title)) { 220 $title = $renderer->_xmlEntities($title); 221 $renderer->doc .= sprintf(' title="%s" alt="%s"', $title, $title); 222 } else { 223 $renderer->doc .= ' alt=""'; 224 } 225 if (!is_null($width)) 226 $renderer->doc .= sprintf(' width="%s"', $renderer->_xmlEntities($width)); 227 if (!is_null($height)) 228 $renderer->doc .= sprintf(' height="%s"', $renderer->_xmlEntities($height)); 229 $renderer->doc .= sprintf(' />%s', DOKU_LF); 230 $renderer->doc .= sprintf('</p>%s', DOKU_LF); 231 break; 232 case DOKU_LEXER_MATCHED: 233 $match_type = self::MATCH_IS_UNKNOWN; 234 $is_correct = false; 235 $err_msg = ""; 236 list($state, $match_type, $is_correct, $err_msg) = $data; 237 if ($is_correct) { 238 switch ($match_type) { 239 case self::MATCH_IS_AREA: 240 if (!array_key_exists($this->nr_imagemap_render, $this->a_areas)) { 241 $this->a_areas[$this->nr_imagemap_render] = array(); 242 } 243 array_push($this->a_areas[$this->nr_imagemap_render], $data); 244 break; 245 case self::MATCH_IS_CONFIG: 246 if (!array_key_exists($this->nr_imagemap_render, $this->a_cfg)) { 247 $this->a_cfg[$this->nr_imagemap_render] = array(); 248 } 249 array_push($this->a_cfg[$this->nr_imagemap_render], $data); 250 break; 251 } 252 if ($this->is_debug) { 253 dbglog(sprintf("syntax_plugin_imapmarkers_map.render::DOKU_LEXER_MATCHED: [%d] DATA='%s'", $this->nr_imagemap_render, implode(", ", $data))); 254 } 255 } else { 256 $renderer->doc .= sprintf(' <br /><span style="color:white; background-color:red;">ERROR -- %s</span>%s', $err_msg, DOKU_LF); 257 } 258 break; 259 case DOKU_LEXER_UNMATCHED: 260 if ($this->is_debug) { 261 dbglog(sprintf("syntax_plugin_imapmarkers_map.render::DOKU_LEXER_UNMATCHED: [%d] DATA='%s'", $this->nr_imagemap_render, implode(", ", $data))); 262 } 263 break; 264 case DOKU_LEXER_EXIT: 265 $is_all_ok = true; 266 $err_msg = ""; 267 $nr_areas = 0; 268 $nr_cfgs = 0; 269 if (array_key_exists($this->nr_imagemap_render, $this->a_areas)) { 270 $nr_areas = count($this->a_areas[$this->nr_imagemap_render]); 271 } 272 if (array_key_exists($this->nr_imagemap_render, $this->a_cfg)) { 273 $nr_cfgs = 1; 274 } 275 if ($this->is_debug) { 276 dbglog(sprintf("syntax_plugin_imapmarkers_map.render::DOKU_LEXER_EXIT: [%d] DATA='%s' #AREAS=%d #CFGS=%d", $this->nr_imagemap_render, implode(", ", $data), $nr_areas, $nr_cfgs)); 277 } 278 if ($nr_areas > 0) { 279 foreach ($this->a_areas[$this->nr_imagemap_render] as $value) { 280 list($state, $match_type, $is_correct, $err_msg) = $value; 281 if (!$is_correct) { 282 $renderer->doc .= sprintf(' <br /><span style="color:white; background-color:red;">ERROR -- %s</span>%s', $err_msg, DOKU_LF); 283 $is_all_ok = false; 284 } 285 } 286 if ($is_all_ok) { 287 $renderer->doc .= sprintf(' <map name="imapmarkers-map-%d" class="imapmarkers imapmarkers-map">%s', $this->nr_imagemap_render, DOKU_LF); 288 foreach ($this->a_areas[$this->nr_imagemap_render] as $key => $value) { 289 list($state, $match_type, $is_correct, $err_msg, $link, $loc_id, $text, $num_coords, $a_coords_s, $uri, $classes) = $value; 290 $link = ($link == "") ? "#" : $link; 291 $shape = ""; 292 switch ($num_coords) { 293 case 3: 294 $shape = "circle"; 295 break; 296 case 4: 297 $shape = "rect"; 298 break; 299 default: 300 $shape = "poly"; 301 } 302 $renderer->doc .= sprintf(' <area id="imapmarkers-area-%d-%d" location_id="%s" class="imapmarkers" shape="%s" coords="%s" alt="%s" title="%s" href="%s" />%s', $this->nr_imagemap_render, $key, $loc_id, $shape, $a_coords_s, $text, $text, $uri, DOKU_LF); 303 } 304 $renderer->doc .= sprintf(' <div style="display:none;" class="imapcontent">%s', DOKU_LF); 305 $renderer->doc .= sprintf(' <p>%s', DOKU_LF); 306 foreach ($this->a_areas[$this->nr_imagemap_render] as $key => $value) { 307 list($state, $match_type, $is_correct, $err_msg, $link, $loc_id, $text, $a_coords) = $value; 308 $link = ($link == "") ? "#" : $link; 309 $renderer->doc .= sprintf(' <a id="imapmarkers-link-%d-%d" title="%s" href="%s" class="%s" rel="ugc nofollow">%s</a>%s', $this->nr_imagemap_render, $key, $link, $uri, $classes, $text, DOKU_LF); 310 } 311 $renderer->doc .= sprintf(' </p>%s', DOKU_LF); 312 $renderer->doc .= sprintf(' </div>%s', DOKU_LF); 313 $renderer->doc .= sprintf(' </map>%s', DOKU_LF); 314 } 315 } 316 if ($nr_cfgs == 1) { 317 foreach ($this->a_cfg[$this->nr_imagemap_render] as $value) { 318 list($state, $match_type, $is_correct, $err_msg) = $value; 319 if (!$is_correct) { 320 $renderer->doc .= sprintf(' <br /><span style="color:white; background-color:red;">ERROR -- %s</span>%s', $err_msg, DOKU_LF); 321 $is_all_ok = false; 322 } 323 } 324 if ($is_all_ok) { 325 foreach ($this->a_cfg[$this->nr_imagemap_render] as $key => $value) { 326 list($state, $match_type, $is_correct, $err_msg, $cfg) = $value; 327 $renderer->doc .= sprintf(' <div id="imapmarkers-config-%d" class="imapmarkers imapmarkers-config" style="display: none;">%s</div>%s', $this->nr_imagemap_render, $cfg, DOKU_LF); 328 } 329 } 330 } 331 break; 332 } 333 } 334 return true; 335 } // public function render 336} // class syntax_plugin_imapmarkers_map