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') . ' ' . $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>: ' . $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