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