1<?php
2/**
3 * Plugin google_maps: Generates embedded Google Maps frame or link to Google Maps.
4 *
5 * @license    GPLv2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Dmitry Katsubo <dma_k@mail.ru>
7 */
8
9if(!defined('DOKU_INC')) die();
10if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
11require_once(DOKU_PLUGIN.'syntax.php');
12
13/**
14 * All DokuWiki plugins to extend the parser/rendering mechanism
15 * need to inherit from this class
16 */
17class syntax_plugin_google_maps extends DokuWiki_Syntax_Plugin
18{
19	private $RE_NON_SYNTAX_SEQ = '[^\[\]{}|]+';
20	private $RE_PLUGIN_BODY;
21
22	function syntax_plugin_google_maps()
23	{
24		$this->RE_PLUGIN_BODY = $this->RE_NON_SYNTAX_SEQ . '(?:\\[' . $this->RE_NON_SYNTAX_SEQ . '\\])?';
25	}
26
27	function getInfo()
28	{
29		return array(
30			'author'	=> 'Dmitry Katsubo',
31			'email'		=> 'dma_k@mail.ru',
32			'date'		=> '2016-09-20',
33			'name'		=> 'Google Maps Plugin',
34			'desc'		=> 'Adds a Google Maps frame
35				 syntax: {{googlemaps>address1;address2;address3[zoom=16,size=small,control=hierarchical,overviewmap=true,width=800,height=600,type=embedded]|alternative text}}',
36			'url'    => 'http://centurion.dynalias.com/wiki/plugin/google_maps',
37		);
38	}
39
40	function getAllowedTypes()
41	{
42		return array('formatting');
43	}
44
45	function getType()
46	{
47		return 'substition';
48	}
49
50	function getSort()
51	{
52		return 159;
53	}
54
55	function connectTo($mode)
56	{
57		$this->Lexer->addSpecialPattern('{{googlemaps>' . $this->RE_PLUGIN_BODY . '}}', $mode, 'plugin_google_maps');
58		$this->Lexer->addEntryPattern('{{googlemaps>' . $this->RE_PLUGIN_BODY . '\|(?=' . $this->RE_NON_SYNTAX_SEQ . '}})', $mode, 'plugin_google_maps');
59	}
60
61	function postConnect()
62	{
63		$this->Lexer->addExitPattern('}}', 'plugin_google_maps');
64	}
65
66	private function getConfigValue($options, $option_name, $config_prefix = null)
67	{
68		// Also escape HTML to protect the page:
69		return(htmlspecialchars(
70			isset($options[$option_name]) ?
71				$options[$option_name] :
72				$this->getConf($config_prefix . $option_name)
73		));
74	}
75
76	function handle($match, $state, $pos, Doku_Handler $handler)
77	{
78		switch ($state)
79		{
80			case DOKU_LEXER_SPECIAL:
81			case DOKU_LEXER_ENTER:
82				$matches = array();
83
84				if (!preg_match('/{{googlemaps>(' . $this->RE_NON_SYNTAX_SEQ . ')(?:\\[(' . $this->RE_NON_SYNTAX_SEQ . ')\\])?/', $match, $matches))
85				{
86					return array('');  // this is an error
87				}
88
89				$options = array();
90
91				if (isset($matches[2]))
92				{
93					$entries = explode(',', $matches[2]);
94
95					foreach ($entries as $entry)
96					{
97						$key_value = explode('=', $entry);
98
99						$options[trim($key_value[0])] = trim($key_value[1]);
100					}
101				}
102
103				return array($state, array($matches[1], &$options));
104		}
105
106		return array($state, $match);
107	}
108
109	function render($mode, Doku_Renderer $renderer, $data)
110	{
111		if ($mode == 'xhtml')
112		{
113			list($state, $match) = $data;
114
115			switch($state)
116			{
117				case DOKU_LEXER_SPECIAL:
118				case DOKU_LEXER_ENTER:
119					list($text, $options) = $match;
120
121					// All locations are in this array:
122					$locations = array();
123					$i = 0;
124
125					foreach (explode(";", $text) as $q)
126					{
127						$q = trim($q);
128						if (strlen($q))
129						{
130							$locations[$i++] = htmlspecialchars(html_entity_decode($q));
131						}
132					}
133
134					// This type is available only in DOKU_LEXER_SPECIAL state:
135					if ($state == DOKU_LEXER_SPECIAL && $options['type'] == 'embedded')
136					{
137						// Dynamic injection of this script via JS causes FF to hang, so we have to include it for each map:
138						$renderer->doc .= "\n<script type='text/javascript' src='//maps.google.com/maps?file=api&v=2.x&key=" . $this->getConf('google_api_key') . "'></script>";
139
140						// Default values:
141						$size			= $this->getConfigValue($options, 'size');
142						$width  		= $this->getConfigValue($options, 'width', $size . '_') . "px";
143						$height 		= $this->getConfigValue($options, 'height', $size . '_') . "px";
144
145						// Embedded div:
146						$renderer->doc .= "\n<div class='gmaps_frame' style='width: $width; height: $height'";
147
148						foreach ($locations as $i => $q)
149						{
150							$renderer->doc .= " location$i='$q'";
151						}
152
153						// Copy values into attributes:
154						foreach (array('size', 'control', 'overviewmap', 'zoom') as $attr_name)
155						{
156							$attr_value = $this->getConfigValue($options, $attr_name);
157
158							if (strlen($attr_value))
159							{
160								$renderer->doc .= ' ' . $attr_name . '="' . $attr_value . '"';
161							}
162						}
163
164						// Important to leave one hanging node inside <div>, otherwise maps start overlappig.
165						$renderer->doc .= '></div>';
166
167						return true;
168					}
169
170					// If we are here it means:
171					// * state == DOKU_LEXER_SPECIAL and type != embedded ==> we render a link with a text equal to address, as there is no alternative text in this state
172					// * state == DOKU_LEXER_ENTER   and type != embedded ==> we start rendering a link; the alternative text will be rendered by dokuwiki renderer and may include any formatting
173					// * state == DOKU_LEXER_ENTER   and type == embedded ==> the is unsupported combination, but we render a link the same as with type != embedded
174
175					// Concat params:
176					$params = '&';
177					// If not defined, Google Maps engine will automatically select the best zoom:
178					if ($options['zoom'])
179					{
180						$params .= "z=" . $options['zoom'];
181					}
182
183					// Query is already escaped, params are taken from options:
184					$url = "//maps.google.com/maps?q=$locations[0]$params";
185
186					// External link:
187					$renderer->doc .= "<a href='$url' class='gmaps_link'>";
188
189					if ($state == DOKU_LEXER_SPECIAL)
190					{
191						 $renderer->doc .= "$text</a>";
192					}
193
194					return true;
195
196				case DOKU_LEXER_UNMATCHED:
197					$renderer->doc .= $renderer->_xmlEntities($match);
198					return true;
199
200				case DOKU_LEXER_EXIT:
201					$renderer->doc .= '</a>';
202					return true;
203
204				default:
205					//$renderer->doc .= "<div class='error'>Cannot handle mode $style</div>";
206			}
207		}
208
209		return false;
210	}
211}
212?>
213