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