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