1<?php 2/** 3 * DokuWiki Target-Link Plugin 4 * 5 * Make links with specified targets not depending on the default configuration. 6 * e.g.: The links usual open in the same tab, but this link opens in a new tab. 7 * 8 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 9 * @author Hokkaidoperson <dosankomali@yahoo.co.jp> 10 * 11 */ 12 13// must be run within Dokuwiki 14if(!defined('DOKU_INC')) die(); 15 16class syntax_plugin_targetlink extends DokuWiki_Syntax_Plugin { 17 18 function getType(){ 19 return 'substition'; 20 } 21 22 function getSort(){ 23 return 295; // between Doku_Parser_Mode_camelcaselink (290) and Doku_Parser_Mode_internallink (300) 24 } 25 26 function connectTo($mode) { 27 $this->Lexer->addSpecialPattern('\[\[target=.*?\|.*?\]\](?!\])',$mode,'plugin_targetlink'); 28 $this->Lexer->addSpecialPattern('\[\[\+tab\|.*?\]\](?!\])',$mode,'plugin_targetlink'); 29 } 30 31 function handle($match, $state, $pos, Doku_Handler $handler) { 32 33 return explode('|', substr($match, 2, -2)); 34 35 } 36 37 function render($format, Doku_Renderer $renderer, $data) { 38 if ($data[0] == '+tab') { 39 $target = '_blank'; 40 } else { 41 $target = substr($data[0], strlen('target=')); 42 } 43 44 45 if($format == 'xhtml') { 46 47 //decide which kind of link it is 48 //(referred an idea from https://github.com/ironiemix/dokuwiki-plugin-menu/blob/master/syntax.php) 49 $ref = $data[1]; 50 if ( preg_match('/^[a-zA-Z0-9\.]+>{1}.*$/u',$ref) ) { 51 // Interwiki 52 $interwiki = explode('>',$ref,2); 53 $args = $this->interwikilink($renderer, $data[1], $data[2], $interwiki[0], $interwiki[1], $target); 54 55 } elseif ( preg_match('/^\\\\\\\\[\w.:?\-;,]+?\\\\/u',$ref) ) { 56 // Windows Share 57 $args = $this->windowssharelink($renderer, $data[1], $data[2], $target); 58 59 } elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$ref) ) { 60 // external link (accepts all protocols) 61 $args = $this->externallink($renderer, $data[1], $data[2], $target, $this->schemes); 62 63 } else { 64 // internal link 65 $args = $this->internallink($renderer, $data[1], $data[2], $target); 66 67 } 68 69 $renderer->doc .= $renderer->_formatLink($args); 70 71 } 72 73 if($format == 'metadata') { 74 //simply calls the default function 75 76 //decide which kind of link it is 77 //(referred an idea from https://github.com/ironiemix/dokuwiki-plugin-menu/blob/master/syntax.php) 78 $ref = $data[1]; 79 if ( preg_match('/^[a-zA-Z0-9\.]+>{1}.*$/u',$ref) ) { 80 // Interwiki 81 $interwiki = explode('>',$ref,2); 82 $renderer->interwikilink($data[1], $data[2], $interwiki[0], $interwiki[1]); 83 84 } elseif ( preg_match('/^\\\\\\\\[\w.:?\-;,]+?\\\\/u',$ref) ) { 85 // Windows Share 86 $renderer->windowssharelink($data[1], $data[2]); 87 88 } elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$ref) ) { 89 // external link (accepts all protocols) 90 $renderer->externallink($data[1], $data[2]); 91 92 } else { 93 // internal link 94 $renderer->internallink($data[1], $data[2]); 95 96 } 97 98 } 99 100 101 } 102 103 // Got an idea from https://github.com/rpeyron/plugin-button/blob/master/syntax.php 104 // and copied from original internallink/externallink/interwikilink/windowssharelink functions 105 // added $target 106 function internallink(&$xhtml, $id, $name = null, $target, $search = null, $returnonly = false, $linktype = 'content') { 107 global $conf; 108 global $ID; 109 global $INFO; 110 111 $params = ''; 112 $parts = explode('?', $id, 2); 113 if(count($parts) === 2) { 114 $id = $parts[0]; 115 $params = $parts[1]; 116 } 117 118 // For empty $id we need to know the current $ID 119 // We need this check because _simpleTitle needs 120 // correct $id and resolve_pageid() use cleanID($id) 121 // (some things could be lost) 122 if($id === '') { 123 $id = $ID; 124 } 125 126 // default name is based on $id as given 127 $default = $xhtml->_simpleTitle($id); 128 129 // now first resolve and clean up the $id 130 resolve_pageid(getNS($ID), $id, $exists, $xhtml->date_at, true); 131 132 $link = array(); 133 $name = $xhtml->_getLinkTitle($name, $default, $isImage, $id, $linktype); 134 if(!$isImage) { 135 if($exists) { 136 $class = 'wikilink1'; 137 } else { 138 $class = 'wikilink2'; 139 $link['rel'] = 'nofollow'; 140 } 141 } else { 142 $class = 'media'; 143 } 144 145 //keep hash anchor 146 @list($id, $hash) = explode('#', $id, 2); 147 if(!empty($hash)) $hash = $xhtml->_headerToLink($hash); 148 149 //prepare for formating 150 $link['target'] = $target; 151 $link['style'] = ''; 152 $link['pre'] = ''; 153 $link['suf'] = ''; 154 // highlight link to current page 155 if($id == $INFO['id']) { 156 $link['pre'] = '<span class="curid">'; 157 $link['suf'] = '</span>'; 158 } 159 $link['more'] = ''; 160 $link['class'] = $class; 161 if($xhtml->date_at) { 162 $params = $params.'&at='.rawurlencode($xhtml->date_at); 163 } 164 $link['url'] = wl($id, $params); 165 $link['name'] = $name; 166 $link['title'] = $id; 167 //add search string 168 if($search) { 169 ($conf['userewrite']) ? $link['url'] .= '?' : $link['url'] .= '&'; 170 if(is_array($search)) { 171 $search = array_map('rawurlencode', $search); 172 $link['url'] .= 's[]='.join('&s[]=', $search); 173 } else { 174 $link['url'] .= 's='.rawurlencode($search); 175 } 176 } 177 178 //keep hash 179 if($hash) $link['url'] .= '#'.$hash; 180 181 return $link; 182 183 //output formatted 184 //if($returnonly) { 185 // return $xhtml->_formatLink($link); 186 //} else { 187 // $this->doc .= $this->_formatLink($link); 188 //} 189 } 190 191 function externallink(&$xhtml, $url, $name = null, $target, $schemes, $returnonly = false) { 192 global $conf; 193 194 $name = $xhtml->_getLinkTitle($name, $url, $isImage); 195 196 // url might be an attack vector, only allow registered protocols 197 if(is_null($this->schemes)) $this->schemes = getSchemes(); 198 list($scheme) = explode('://', $url); 199 $scheme = strtolower($scheme); 200 if(!in_array($scheme, $this->schemes)) $url = ''; 201 202 // is there still an URL? 203 if(!$url) { 204 if($returnonly) { 205 return $name; 206 } else { 207 $xhtml->doc .= $name; 208 } 209 return; 210 } 211 212 // set class 213 if(!$isImage) { 214 $class = 'urlextern'; 215 } else { 216 $class = 'media'; 217 } 218 219 //prepare for formating 220 $link = array(); 221 $link['target'] = $target; 222 $link['style'] = ''; 223 $link['pre'] = ''; 224 $link['suf'] = ''; 225 $link['more'] = ''; 226 $link['class'] = $class; 227 $link['url'] = $url; 228 $link['rel'] = ''; 229 230 $link['name'] = $name; 231 $link['title'] = $xhtml->_xmlEntities($url); 232 if($conf['relnofollow']) $link['rel'] .= ' nofollow'; 233 if($target) $link['rel'] .= ' noopener'; 234 235 return $link; 236 237 //output formatted 238 //if($returnonly) { 239 // return $xhtml->_formatLink($link); 240 //} else { 241 // $this->doc .= $this->_formatLink($link); 242 //} 243 } 244 245 function interwikilink(&$xhtml, $match, $name = null, $wikiName, $wikiUri, $target, $returnonly = false) { 246 global $conf; 247 248 $link = array(); 249 $link['target'] = $target; 250 $link['pre'] = ''; 251 $link['suf'] = ''; 252 $link['more'] = ''; 253 $link['name'] = $xhtml->_getLinkTitle($name, $wikiUri, $isImage); 254 $link['rel'] = ''; 255 256 //get interwiki URL 257 $exists = null; 258 $url = $xhtml->_resolveInterWiki($wikiName, $wikiUri, $exists); 259 260 if(!$isImage) { 261 $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $wikiName); 262 $link['class'] = "interwiki iw_$class"; 263 } else { 264 $link['class'] = 'media'; 265 } 266 267 //do we stay at the same server? Use local target 268 //if(strpos($url, DOKU_URL) === 0 OR strpos($url, DOKU_BASE) === 0) { 269 // $link['target'] = $conf['target']['wiki']; 270 //} 271 if($exists !== null && !$isImage) { 272 if($exists) { 273 $link['class'] .= ' wikilink1'; 274 } else { 275 $link['class'] .= ' wikilink2'; 276 $link['rel'] .= ' nofollow'; 277 } 278 } 279 if($target) $link['rel'] .= ' noopener'; 280 281 $link['url'] = $url; 282 $link['title'] = htmlspecialchars($link['url']); 283 284 return $link; 285 286 //output formatted 287 //if($returnonly) { 288 // return $xhtml->_formatLink($link); 289 //} else { 290 // $this->doc .= $this->_formatLink($link); 291 //} 292 } 293 294 function windowssharelink(&$xhtml, $url, $name = null, $target, $returnonly = false) { 295 global $conf; 296 297 //simple setup 298 $link = array(); 299 $link['target'] = $target; 300 $link['pre'] = ''; 301 $link['suf'] = ''; 302 $link['style'] = ''; 303 304 $link['name'] = $xhtml->_getLinkTitle($name, $url, $isImage); 305 if(!$isImage) { 306 $link['class'] = 'windows'; 307 } else { 308 $link['class'] = 'media'; 309 } 310 311 $link['title'] = $xhtml->_xmlEntities($url); 312 $url = str_replace('\\', '/', $url); 313 $url = 'file:///'.$url; 314 $link['url'] = $url; 315 316 return $link; 317 318 //output formatted 319 //if($returnonly) { 320 // return $xhtml->_formatLink($link); 321 //} else { 322 // $this->doc .= $this->_formatLink($link); 323 //} 324 } 325 326} 327