1<?php
2/**
3 * Image Map
4 *
5 * @license  GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author   Tom N Harris <tnharris@whoopdedo.org>
7 */
8
9// must be run within Dokuwiki
10if (!defined('DOKU_INC')) die();
11
12if (!defined('DOKU_LF')) define('DOKU_LF', "\n");
13if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/');
14
15require_once(DOKU_PLUGIN.'syntax.php');
16
17class syntax_plugin_imagemap extends DokuWiki_Syntax_Plugin {
18
19  function getType(){ return 'container'; }
20  function getSort(){ return 316; }
21  function getPType(){ return 'block';}
22  function getAllowedTypes() {
23    return array('formatting','substition','disabled','protected','container','paragraphs');
24  }
25
26  function connectTo($mode) {
27    $this->Lexer->addEntryPattern('\{\{map>[^\}]+\}\}', $mode, 'plugin_imagemap');
28  }
29  function postConnect() {
30    $this->Lexer->addExitPattern('\{\{<map\}\}', 'plugin_imagemap');
31  }
32
33  function handle($match, $state, $pos, &$handler){
34    global $conf;
35    global $ID;
36    $args = array($state);
37    switch ($state) {
38    case DOKU_LEXER_ENTER:
39      $img = Doku_Handler_Parse_Media(substr($match, 6, -2));
40      if ($img['title']) {
41        $mapname = str_replace(':','',cleanID($img['title']));
42        $mapname = ltrim($mapname, '0123456789._-');
43      }
44      if (empty($mapname)) {
45        if ($img['type'] == 'internalmedia') {
46          $src = $img['src'];
47          resolve_mediaid(getNS($ID),$src, $exists);
48          $nssep = ($conf['useslash']) ? '[:;/]' : '[:;]';
49          $mapname = preg_replace('!.*'.$nssep.'!','',$src);
50        } else {
51          $src = parse_url($img['src']);
52          $mapname = str_replace(':','',cleanID($src['host'].$src['path'].$src['query']));
53          $mapname = ltrim($mapname, '0123456789._-');
54        }
55        if (empty($mapname)) {
56          $mapname = 'imagemap'.$pos;
57        }
58      }
59      $args = array($state, $img['type'], $img['src'], $img['title'], $mapname,
60                    $img['align'], $img['width'], $img['height'],
61                    $img['cache']);
62
63      $ReWriter =& new ImageMap_Handler($mapname, $handler->CallWriter);
64      $handler->CallWriter =& $ReWriter;
65      break;
66    case DOKU_LEXER_EXIT:
67      $handler->CallWriter->process();
68      $ReWriter =& $handler->CallWriter;
69      $handler->CallWriter =& $ReWriter->CallWriter;
70      break;
71    case DOKU_LEXER_MATCHED:
72      break;
73    case DOKU_LEXER_UNMATCHED:
74      $args[] = $match;
75      break;
76    }
77    return $args;
78  }
79
80  function render($format, &$renderer, $data) {
81    global $conf;
82    global $ID;
83    static $has_content=false;
84    $state = $data[0];
85    if (substr($format,0,5) == 'xhtml') {
86      switch ($state) {
87      case DOKU_LEXER_ENTER:
88        list($state,$type,$src,$title,$name,$align,$width,$height,$cache) = $data;
89        if ($type=='internalmedia') {
90          resolve_mediaid(getNS($ID),$src, $exists);
91        }
92        $renderer->doc .= '<p>'.DOKU_LF;
93        $src = ml($src,array('w'=>$width,'h'=>$height,'cache'=>$cache));
94        $renderer->doc .= ' <img src="'.$src.'" class="media'.$align.' imap" usemap="#'.$name.'"';
95        if($align == 'right' || $align == 'left')
96          $renderer->doc .= ' align="'.$align.'"';
97        if (!is_null($title)) {
98          $title = $renderer->_xmlEntities($title);
99          $renderer->doc .= ' title="'.$title.'"';
100          $renderer->doc .= ' alt="'.$title.'"';
101        } else {
102          $renderer->doc .= ' alt=""';
103        }
104        if (!is_null($width))
105          $renderer->doc .= ' width="'.$renderer->_xmlEntities($width).'"';
106        if (!is_null($height))
107            $renderer->doc .= ' height="'.$renderer->_xmlEntities($height).'"';
108        $renderer->doc .= ' />'.DOKU_LF;
109        $renderer->doc .= '</p>'.DOKU_LF;
110        $renderer->doc .= '<map name="'.$name.'">'.DOKU_LF;
111        $has_content = false;
112        break;
113      case DOKU_LEXER_MATCHED:
114        if ($data[1]=='area') {
115          @list($state,$match,$shape,$coords,$type,$title,$url,$extra) = $data;
116          $target = '';
117          switch ($type) {
118          case 'internallink':
119            if ($url === '') $url = $ID;
120            $default = $renderer->_simpleTitle($url);
121            resolve_pageid(getNS($ID),$url,$exists);
122            $title = $renderer->_getLinkTitle($title, $default, $isImg, $url);
123            list($url,$hash) = explode('#',$url,2);
124            if (!empty($hash)) $hash = $renderer->_headerToLink($hash);
125            $url = wl($url);
126            if ($hash) $url .= '#'.$hash;
127            $target = $conf['target']['wiki'];
128          break;
129          case 'locallink':
130            $title = $renderer->_getLinkTitle($title, $url, $isImg);
131            $url = $renderer->_headerToLink($url);
132            $url = '#'.$url;
133          break;
134          case 'externallink':
135            $title = $renderer->_getLinkTitle($title, $url, $isImg);
136            // url might be an attack vector, only allow registered protocols
137            if(is_null($this->schemes)) $this->schemes = getSchemes();
138            list($scheme) = explode('://',$url);
139            $scheme = strtolower($scheme);
140            if(!in_array($scheme,$this->schemes)) $url = '';
141            $target = $conf['target']['extern'];
142          break;
143          case 'interwikilink':
144            $title = $renderer->_getLinkTitle($title, $url, $isImg);
145            $url = $renderer->_resolveInterWiki($extra,$url);
146            if (strpos($url,DOKU_URL) === 0)
147              $target = $conf['target']['wiki'];
148            else
149              $target = $conf['target']['interwiki'];
150          break;
151          case 'emaillink':
152            $url = $renderer->_xmlEntities($url);
153            $url = obfuscate($url);
154            $title = $renderer->_getLinkTitle($title, $url, $isImg);
155            if ($conf['mailguard'] == 'visible')
156              $url = rawurlencode($url);
157            $url = 'mailto:'.$url;
158          break;
159          case 'windowssharelink':
160            $title = $renderer->_getLinkTitle($title, $url, $isImg);
161            $url = str_replace('\\','/',$url);
162            $url = 'file:///'.$url;
163            $target = $conf['target']['windows'];
164          break;
165          case 'externalmedia':
166            list($url,$hash) = explode('#',$url,2);
167            $title = $renderer->_media($url, $title, null,null,null,null, false);
168            $url = ml($url, array('cache'=>$extra[0]));
169            if ($hash) $url .= '#'.$hash;
170            $target = $conf['target']['extern'];
171          break;
172          case 'internalmedia':
173            list($url,$hash) = explode('#',$url,2);
174            resolve_mediaid(getNS($ID), $url, $exists);
175            $title = $renderer->_media($url, $title, null,null,null,null, false);
176            $url = ml($url, array('id'=>$ID,'cache'=>$extra[0]), ($extra[1]=='direct'));
177            if ($hash) $url .= '#'.$hash;
178          break;
179          }
180          if($url){
181            $renderer->doc .= '<area href="'.$url.'"';
182            if (!empty($target))
183              $renderer->doc .= ' target="'.$target.'"';
184            $renderer->doc .= ' title="'.$title.'" alt="'.$title.'"';
185            $renderer->doc .= ' shape="'.$shape.'" coords="'.$coords.'" />';
186          }
187        } elseif ($data[1]=='divstart') {
188          $renderer->doc .= DOKU_LF.'<div class="imapcontent">'.DOKU_LF;
189          $has_content = true;
190        } elseif ($data[1]=='divend') {
191          $renderer->doc .= DOKU_LF;//.'</div>'.DOKU_LF;
192        }
193        break;
194      case DOKU_LEXER_EXIT:
195        if ($has_content) $renderer->doc .= '</div>'.DOKU_LF;
196        $renderer->doc .= '</map>';
197        break;
198      case DOKU_LEXER_UNMATCHED:
199        $renderer->doc .= $renderer->_xmlEntities($data[1]);
200        break;
201      }
202      return true;
203    }
204    elseif ($format == 'metadata') {
205      switch ($state) {
206      case DOKU_LEXER_ENTER:
207        list($state,$type,$src,$title,$name) = $data;
208        if ($type=='internalmedia') {
209          resolve_mediaid(getNS($ID),$src, $exists);
210          $renderer->meta['relation']['media'][$src] = $exists;
211        }
212        if (is_null($title))
213          $title = $name;
214        if ($renderer->capture && $title)
215          $renderer->doc .= '['.$title.']';
216        break;
217      case DOKU_LEXER_EXIT:
218        break;
219      case DOKU_LEXER_UNMATCHED:
220        if ($renderer->capture)
221          $renderer->doc .= $data[1];
222        break;
223      }
224      return true;
225    }
226    return false;
227  }
228
229}
230
231class ImageMap_Handler {
232
233  var $CallWriter;
234
235  var $calls = array();
236  var $areas = array();
237  var $mapname;
238
239  function ImageMap_Handler($name, &$CallWriter) {
240    $this->CallWriter =& $CallWriter;
241    $this->mapname = $name;
242  }
243
244  function writeCall($call) {
245    $this->calls[] = $call;
246  }
247
248  function writeCalls($calls) {
249    $this->calls = array_merge($this->calls, $calls);
250  }
251
252  function finalise() {
253    $last_call = end($this->calls);
254    $this->process();
255    $this->_addPluginCall(array(DOKU_LEXER_EXIT), $last_call[2]);
256    $this->CallWriter->finalise();
257  }
258
259  function process() {
260    $last_call = end($this->calls);
261    $first_call = array_shift($this->calls);
262
263    $this->CallWriter->writeCall($first_call);
264    $this->_processLinks($first_call[2]);
265
266    if (!empty($this->calls)) {
267      $this->_addPluginCall(array(DOKU_LEXER_MATCHED,'divstart'), $first_call[2]);
268      //Force a new paragraph
269      $this->CallWriter->writeCall(array('eol',array(),$this->calls[0][2]));
270      $this->CallWriter->writeCalls($this->calls);
271      $this->_addPluginCall(array(DOKU_LEXER_MATCHED,'divend'), $last_call[2]);
272    }
273  }
274
275  function _addPluginCall($args, $pos) {
276    $this->CallWriter->writeCall(array('plugin',
277                                 array('imagemap', $args, $args[0]),
278                                 $pos));
279  }
280
281  function _addArea($pos, $type, $title, $url, $extra=null) {
282    if (preg_match('/^(.*)@([^@]+)$/u', $title, $match)) {
283      $coords = explode(',',$match[2]);
284      if (count($coords) == 3) {
285        $shape = 'circle';
286      } elseif (count($coords) == 4) {
287        $shape = 'rect';
288      } elseif (count($coords) >= 6) {
289        $shape = 'poly';
290      } else {
291        return $title;
292      }
293      $coords = array_map('trim', $coords);
294      $title = trim($match[1]);
295      $this->_addPluginCall(array(DOKU_LEXER_MATCHED, 'area', $shape, join(',',$coords),
296                                  $type, $title, $url, $extra), $pos);
297    }
298    return $title;
299  }
300
301  function _processLinks($pos) {
302    for ($n=0;$n<count($this->calls);$n++) {
303      $data =& $this->calls[$n][1];
304      $type = $this->calls[$n][0];
305      switch ($type) {
306      case 'internallink':
307      case 'locallink':
308      case 'externallink':
309      case 'emaillink':
310      case 'windowssharelink':
311        if (is_array($data[1])) {
312          $title = $data[1]['title'];
313        } else {
314          $title = $data[1];
315        }
316        $title = $this->_addArea($pos, $type, $title, $data[0]);
317        if (is_array($data[1])) {
318          $data[1]['title'] = $title;
319        } else {
320          $data[1] = $title;
321        }
322      break;
323      case 'interwikilink':
324        if (is_array($data[1])) {
325          $title = $data[1]['title'];
326        } else {
327          $title = $data[1];
328        }
329        $title = $this->_addArea($pos, $type, $title, $data[3], $data[2]);
330        if (is_array($data[1])) {
331          $data[1]['title'] = $title;
332        } else {
333          $data[1] = $title;
334        }
335      break;
336      case 'externalmedia':
337      case 'internalmedia':
338        if ($data[6] !== 'nolink') {
339          if (is_array($data[1])) {
340            $title = $data[1]['title'];
341          } else {
342            $title = $data[1];
343          }
344          $title = $this->_addArea($pos, $type, $title, $data[0], array($data[5],$data[6]));
345          if (is_array($data[1])) {
346            $data[1]['title'] = $title;
347          } else {
348            $data[1] = $title;
349          }
350        }
351      break;
352      }
353    }
354  }
355
356}
357
358//Setup VIM: ex: et ts=2 :
359