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'] .= '&amp;';
170            if(is_array($search)) {
171                $search = array_map('rawurlencode', $search);
172                $link['url'] .= 's[]='.join('&amp;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