1<?php
2/*
3 * Copyright (c) 2011-2018 Mark C. Prins <mprins@users.sf.net>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18/**
19 * DokuWiki Plugin geotag (Syntax Component).
20 *
21 * Handles the rendering part of the geotag plugin.
22 *
23 * @license BSD license
24 * @author Mark C. Prins <mprins@users.sf.net>
25 */
26class syntax_plugin_geotag_geotag extends DokuWiki_Syntax_Plugin {
27    /**
28     *
29     * @see DokuWiki_Syntax_Plugin::getType()
30     */
31    public function getType() {
32        return 'substition';
33    }
34
35    /**
36     *
37     * @see DokuWiki_Syntax_Plugin::getPType()
38     */
39    public function getPType() {
40        return 'block';
41    }
42
43    /**
44     *
45     * @see Doku_Parser_Mode::getSort()
46     */
47    public function getSort() {
48        return 305;
49    }
50
51    /**
52     *
53     * @see Doku_Parser_Mode::connectTo()
54     */
55    public function connectTo($mode) {
56        $this->Lexer->addSpecialPattern('\{\{geotag>.*?\}\}', $mode, 'plugin_geotag_geotag');
57    }
58
59    /**
60     *
61     * @see DokuWiki_Syntax_Plugin::handle()
62     */
63    public function handle($match, $state, $pos, Doku_Handler $handler) {
64        $tags = trim(substr($match, 9, - 2));
65        // parse geotag content
66        preg_match("(lat[:|=]-?\d*\.\d*)", $tags, $lat);
67        preg_match("(lon[:|=]-?\d*\.\d*)", $tags, $lon);
68        preg_match("(alt[:|=]-?\d*\.?\d*)", $tags, $alt);
69        preg_match("/(region[:|=][\p{L}\s\w'-]*)/u", $tags, $region);
70        preg_match("/(placename[:|=][\p{L}\s\w'-]*)/u", $tags, $placename);
71        preg_match("/(country[:|=][\p{L}\s\w'-]*)/u", $tags, $country);
72        preg_match("(hide|unhide)", $tags, $hide);
73
74        $showlocation = $this->getConf('geotag_location_prefix');
75        if ($this->getConf('geotag_showlocation')) {
76            $showlocation = trim(substr($placename [0], 10));
77            if (strlen($showlocation) < 1) {
78                $showlocation = $this->getConf('geotag_location_prefix');
79            }
80        }
81        // read config for system setting
82        $style = '';
83        if ($this->getConf('geotag_hide')) {
84            $style = ' style="display: none;"';
85        }
86        // override config for the current tag
87        if (array_key_exists(0, $hide) && trim($hide [0]) == 'hide') {
88            $style = ' style="display: none;"';
89        } elseif (array_key_exists(0, $hide) && trim($hide [0]) == 'unhide') {
90            $style = '';
91        }
92
93        $data = array(
94                hsc(trim(substr($lat [0], 4))),
95                hsc(trim(substr($lon [0], 4))),
96                hsc(trim(substr(($alt[0] ?? ''), 4))),
97                $this->geohash(substr($lat [0], 4), substr($lon [0], 4)),
98                hsc(trim(substr(($region[0] ?? ''), 7))),
99                hsc(trim(substr(($placename[0] ?? ''), 10))),
100                hsc(trim(substr(($country [0] ?? ''), 8))),
101                hsc($showlocation),
102                $style
103        );
104        return $data;
105    }
106
107    /**
108     *
109     * @see DokuWiki_Syntax_Plugin::render()
110     */
111    public function render($mode, Doku_Renderer $renderer, $data) {
112        if ($data === false) {
113                    return false;
114        }
115        list ($lat, $lon, $alt, $geohash, $region, $placename, $country, $showlocation, $style) = $data;
116        $ddlat = $lat;
117        $ddlon = $lon;
118        if ($this->getConf('displayformat') === 'DMS') {
119            $lat = $this->convertLat($lat);
120            $lon = $this->convertLon($lon);
121        } else {
122            $lat .= 'º';
123            $lon .= 'º';
124        }
125
126        if ($mode == 'xhtml') {
127            if ($this->getConf('geotag_prevent_microformat_render')) {
128                return true;
129            }
130            $searchPre = '';
131            $searchPost = '';
132            if ($this->getConf('geotag_showsearch')) {
133                if ($spHelper = &plugin_load('helper', 'spatialhelper_search')) {
134                    $title = $this->getLang('findnearby') . '&nbsp;' . $placename;
135                    $url = wl(getID(), array(
136                            'do' => 'findnearby',
137                            'lat' => $ddlat,
138                            'lon' => $ddlon
139                    ));
140                    $searchPre = '<a href="' . $url . '" title="' . $title . '">';
141                    $searchPost = '<span class="a11y">' . $title . '</span></a>';
142                }
143            }
144
145            // render geotag microformat/schema.org microdata
146            $renderer->doc .= '<span class="geotagPrint">' . $this->getLang('geotag_desc') . '</span>';
147            $renderer->doc .= '<div class="h-geo geo"' . $style . ' title="' . $this->getLang('geotag_desc')
148                                . $placename . '" itemscope itemtype="http://schema.org/Place">';
149            $renderer->doc .= '<span itemprop="name">' . $showlocation . '</span>:&nbsp;' . $searchPre;
150            $renderer->doc .= '<span itemprop="geo" itemscope itemtype="http://schema.org/GeoCoordinates">';
151            $renderer->doc .= '<span class="p-latitude latitude" itemprop="latitude" data-latitude="' . $ddlat . '">'
152                                . $lat . '</span>;';
153            $renderer->doc .= '<span class="p-longitude longitude" itemprop="longitude" data-longitude="' . $ddlon
154                                . '">' . $lon . '</span>';
155            if (!empty ($alt)) {
156                $renderer->doc .= ', <span class="p-altitude altitude" itemprop="elevation" data-altitude="' . $alt
157                                    . '">' . $alt . 'm</span>';
158            }
159            $renderer->doc .= '</span>' . $searchPost . '</div>' . DOKU_LF;
160            return true;
161        } elseif ($mode == 'metadata') {
162            // render metadata (our action plugin will put it in the page head)
163            $renderer->meta ['geo'] ['lat'] = $ddlat;
164            $renderer->meta ['geo'] ['lon'] = $ddlon;
165            $renderer->meta ['geo'] ['placename'] = $placename;
166            $renderer->meta ['geo'] ['region'] = $region;
167            $renderer->meta ['geo'] ['country'] = $country;
168            $renderer->meta ['geo'] ['geohash'] = $geohash;
169            $renderer->meta ['geo'] ['alt'] = $alt;
170            return true;
171        } elseif ($mode == 'odt') {
172            if (!empty ($alt)) {
173                $alt = ', ' . $alt . 'm';
174            }
175            $renderer->p_open();
176            $renderer->_odtAddImage(DOKU_PLUGIN . 'geotag/images/geotag.png', null, null, 'left', '');
177            $renderer->cdata($this->getLang('geotag_desc') . ' ' . $placename);
178            $renderer->monospace_open();
179            $renderer->cdata($lat . ';' . $lon . $alt);
180            $renderer->monospace_close();
181            $renderer->p_close();
182            return true;
183        }
184        return false;
185    }
186
187    /**
188     * Calculate the geohash for this lat/lon pair.
189     *
190     * @param float $lat
191     * @param float $lon
192     */
193    private function geohash($lat, $lon) {
194        if (!$geophp = plugin_load('helper', 'geophp')) {
195            dbglog($geophp, 'syntax_plugin_geotag_geotag::geohash: geophp plugin is not available.');
196            return "";
197        }
198        $_lat = floatval($lat);
199        $_lon = floatval($lon);
200        $geometry = new Point($_lon, $_lat);
201        return $geometry->out('geohash');
202    }
203
204    /**
205     * Convert decimal degrees to degrees, minutes, seconds format
206     *
207     * @todo move this into a shared library
208     * @param float $decimaldegrees
209     * @return string dms
210     */
211    private function convertDDtoDMS($decimaldegrees) {
212        $dms = floor($decimaldegrees);
213        $secs = ($decimaldegrees - $dms) * 3600;
214        $min = floor($secs / 60);
215        $sec = round($secs - ($min * 60), 3);
216        $dms .= 'º' . $min . '\'' . $sec . '"';
217        return $dms;
218    }
219
220    /**
221     * convert latitude in decimal degrees to DMS+hemisphere.
222     *
223     * @todo move this into a shared library
224     * @param float $decimaldegrees
225     * @return string
226     */
227    private function convertLat($decimaldegrees) {
228        if (strpos($decimaldegrees, '-') !== false) {
229            $latPos = "S";
230        } else {
231            $latPos = "N";
232        }
233        $dms = $this->convertDDtoDMS(abs(floatval($decimaldegrees)));
234        return hsc($dms . $latPos);
235    }
236
237    /**
238     * convert longitude in decimal degrees to DMS+hemisphere.
239     *
240     * @todo move this into a shared library
241     * @param float $decimaldegrees
242     * @return string
243     */
244    private function convertLon($decimaldegrees) {
245        if (strpos($decimaldegrees, '-') !== false) {
246            $lonPos = "W";
247        } else {
248            $lonPos = "E";
249        }
250        $dms = $this->convertDDtoDMS(abs(floatval($decimaldegrees)));
251        return hsc($dms . $lonPos);
252    }
253}
254