1<?php
2/**
3 * linksenhanced Plugin - Render Link Title using Wiki Markup
4 * Heavily based on the standard linking code.
5 *
6 * Usage:
7 *
8 * [[check^http://www.dokuwiki.org|www.dokuwiki.org]]
9 * [[render noparagraph^http://www.dokuwiki.org|<faicon fa fe-euro> FontAwesome Code]]
10 * [[render^http://www.dokuwiki.org|//Formatted Output, probably within a paragraph//]]
11 *
12 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
13 * @author     Andreas Böhler <dev@aboehler.at>
14 */
15
16if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
17if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
18require_once(DOKU_PLUGIN.'syntax.php');
19
20/**
21 * All DokuWiki plugins to extend the parser/rendering mechanism
22 * need to inherit from this class
23 */
24class syntax_plugin_linksenhanced_link extends DokuWiki_Syntax_Plugin {
25
26    function getType() {
27        return 'substition';
28    }
29
30    function getPType() {
31        return 'normal';
32    }
33
34    function getAllowedTypes() {
35        return array('container','substition','protected','disabled','paragraphs','formatting');
36    }
37
38    function getSort() {
39        return 202;
40    }
41
42    function connectTo($mode) {
43        $this->Lexer->addSpecialPattern('\[\[(?:(?:[^[\]]*?\[.*?\])|.*?)\]\]',$mode,'plugin_linksenhanced_link');
44    }
45
46   /**
47    * Handle the match. Use either the standard linking mechanism or, when enabled,
48    * pass the title through the parser
49    */
50    function handle($match, $state, $pos, Doku_Handler $handler) {
51        global $ID;
52
53        $match = substr($match, 2, -2);
54        $link = explode('|',$match,2);
55        $nopara = false;
56        $optionsSet = explode('^', $link[0], 2);
57        $options = array('render' => false,
58                   'noparagraph' => false,
59                   'check' => false,
60                   'class' => false,
61                   'target' => false);
62        if(isset($optionsSet[1]))
63        {
64          $link[0] = $optionsSet[1];
65          $opts = explode(' ', $optionsSet[0]);
66          foreach($opts as $opt)
67          {
68            if(strpos($opt, 'class') === 0)
69              $options['class'] = explode('=', $opt)[1];
70            elseif(strpos($opt, 'target') === 0)
71              $options['target'] = explode('=', $opt)[1];
72            else
73              $options[$opt] = true;
74          }
75        }
76
77        if($this->getConf('check_all_external') == 1)
78        {
79            if($this->getConf('check_only_namespace') != '')
80            {
81                $namespaces = explode(',', $this->getConf('check_only_namespace'));
82                foreach($namespaces as $namespace)
83                {
84                    if(getNS($ID) == $namespace)
85                        $options['check'] = true;
86                }
87            }
88            else
89            {
90                $options['check'] = true;
91            }
92        }
93
94        if($options['render'] && isset($link[1]))
95        {
96            $link[1] = $this->render_text($link[1]);
97            if($options['noparagraph'])
98            {
99                $link[1] = str_replace('</p>', '', str_replace('<p>', '', $link[1]));
100            }
101            if($link[1] === '')
102                $link[1] = null;
103        }
104        else if ( isset($link[1]) && preg_match('/^\{\{[^\}]+\}\}$/',$link[1]) ) {
105            // If the title is an image, convert it to an array containing the image details
106            $link[1] = Doku_Handler_Parse_Media($link[1]);
107        }
108        else if(!isset($link[1]))
109        {
110            $link[1] = null;
111        }
112
113        $link[0] = trim($link[0]);
114
115        //decide which kind of link it is
116
117        if ( preg_match('/^[a-zA-Z0-9\.]+>{1}.*$/u',$link[0]) ) {
118            $type = 'interwiki';
119        }elseif ( preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u',$link[0]) ) {
120            $type = 'windowsshare';
121        }elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$link[0]) ) {
122            $type = 'external';
123        }elseif ( preg_match('<'.PREG_PATTERN_VALID_EMAIL.'>',$link[0]) ) {
124            $type = 'email';
125        }elseif ( preg_match('!^#.+!',$link[0]) ){
126            $type = 'local';
127            $link[0] = substr($link[0],1);
128        }else{
129            $type = 'internal';
130        }
131
132        return array($type, $link, $options);
133    }
134
135   /**
136    * Create output. This is largely based on the internal linking mechanism.
137    */
138    function render($mode, Doku_Renderer $renderer, $data) {
139        if (empty($data)) return false;
140        global $conf;
141        global $ID;
142        global $INFO;
143        global $lang;
144
145        list($type, $link, $options) = $data;
146
147        $url = $link[0];
148        $name = $link[1];
149
150        if($mode == 'metadata')
151        {
152            if($type == 'external')
153            {
154                $schemes = getSchemes();
155                list($scheme) = explode('://',$url);
156                $scheme = strtolower($scheme);
157                if(!in_array($scheme,$schemes)) $url = '';
158
159                // is there still an URL?
160                if(!$url){
161                    return true;
162                }
163
164                $renderer->meta['plugin_linksenhanced']['links'][] = $url;
165            }
166            return true;
167        }
168
169        if($mode == 'xhtml') {
170          switch($type)
171          {
172            case 'interwiki':
173                $interwiki = explode('>',$link[0],2);
174                $wikiName = strtolower($interwiki[0]);
175                $wikiUri = $interwiki[1];
176                $link = array();
177                $link['target'] = $conf['target']['interwiki'];
178                $link['pre']    = '';
179                $link['suf']    = '';
180                $link['more']   = '';
181                if($name === null || !$options['render'])
182                    $link['name']   = $renderer->_getLinkTitle($name, $wikiUri, $isImage);
183                else
184                    $link['name'] = $name;
185
186                //get interwiki URL
187                $exists = null;
188                $url = $renderer->_resolveInterWiki($wikiName, $wikiUri, $exists);
189
190                if(!$isImage) {
191                    $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $wikiName);
192                    $link['class'] = "interwiki iw_$class";
193                } else {
194                    $link['class'] = 'media';
195                }
196
197                //do we stay at the same server? Use local target
198                if(strpos($url, DOKU_URL) === 0 OR strpos($url, DOKU_BASE) === 0) {
199                    $link['target'] = $conf['target']['wiki'];
200                }
201
202                if($options['target'] !== false)
203                    $link['target'] = $options['target'];
204
205                if($exists !== null && !$isImage) {
206                    if($exists) {
207                        $link['class'] .= ' wikilink1';
208                    } else {
209                        $link['class'] .= ' wikilink2';
210                        $link['rel'] = 'nofollow';
211                    }
212                }
213                if($options['class'] !== false)
214                    $link['class'] = $options['class'];
215
216                $link['url'] = $url;
217                $link['title'] = htmlspecialchars($link['url']);
218
219                //output formatted
220                $renderer->doc .= $renderer->_formatLink($link);
221            break;
222            case 'windowsshare':
223                    //simple setup
224                $link['target'] = $conf['target']['windows'];
225                $link['pre']    = '';
226                $link['suf']   = '';
227                $link['style']  = '';
228
229                if($options['target'] !== false)
230                    $link['target'] = $options['target'];
231
232                if($name === null || !$options['render'])
233                    $link['name'] = $renderer->_getLinkTitle($name, $url, $isImage);
234                else
235                    $link['name'] = $name;
236                if ( !$isImage ) {
237                    $link['class'] = 'windows';
238                } else {
239                    $link['class'] = 'media';
240                }
241
242                if($options['class'] !== false)
243                    $link['class'] = $options['class'];
244
245                $link['title'] = $renderer->_xmlEntities($url);
246                $url = str_replace('\\','/',$url);
247                $url = 'file:///'.$url;
248                $link['url'] = $url;
249
250                //output formatted
251                $renderer->doc .= $renderer->_formatLink($link);
252            break;
253            case 'external':
254                if($name === null || !$options['render'])
255                    $name = $renderer->_getLinkTitle($name, $url, $isImage);
256
257                // url might be an attack vector, only allow registered protocols
258                $schemes = getSchemes();
259                list($scheme) = explode('://',$url);
260                $scheme = strtolower($scheme);
261                if(!in_array($scheme,$schemes)) $url = '';
262
263                // is there still an URL?
264                if(!$url){
265                    $renderer->doc .= $name;
266                    return;
267                }
268
269                // set class
270                if ( !$isImage ) {
271                    $class='urlextern';
272                } else {
273                    $class='media';
274                }
275
276                if($options['class'] !== false)
277                    $class = $options['class'];
278
279                //prepare for formating
280                $link['target'] = $conf['target']['extern'];
281                $link['style']  = '';
282                $link['pre']    = '';
283                $link['suf']    = '';
284                $link['more']   = '';
285                $link['class']  = $class;
286                $link['url']    = $url;
287
288                if($options['target'] !== false)
289                    $link['target'] = $options['target'];
290
291                if($options['check'] !== false)
292                {
293                    $link['class'] = 'plugin_linksenhanced_pending';
294                }
295                $link['name']   = $name;
296                $link['title']  = $renderer->_xmlEntities($url);
297                if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"';
298
299                //output formatted
300                $renderer->doc .= $renderer->_formatLink($link);
301            break;
302            case 'email':
303                $link = array();
304                $link['target'] = '';
305                $link['pre']    = '';
306                $link['suf']   = '';
307                $link['style']  = '';
308                $link['more']   = '';
309
310                if($name === null || !$options['render'])
311                    $name = $renderer->_getLinkTitle($name, '', $isImage);
312                if ( !$isImage ) {
313                    $link['class']='mail';
314                } else {
315                    $link['class']='media';
316                }
317
318                if($options['class'] !== false)
319                    $link['class'] = $options['class'];
320
321                $url = $renderer->_xmlEntities($url);
322                $url = obfuscate($url);
323                $title   = $url;
324
325                if(empty($name)){
326                    $name = $url;
327                }
328
329                if($conf['mailguard'] == 'visible') $url = rawurlencode($url);
330
331                $link['url']   = 'mailto:'.$url;
332                $link['name']  = $name;
333                $link['title'] = $title;
334
335                //output formatted
336                $renderer->doc .= $renderer->_formatLink($link);
337            break;
338            case 'local':
339                if($name === null || !$options['render'])
340                    $name  = $renderer->_getLinkTitle($name, $url, $isImage);
341                $url  = $renderer->_headerToLink($url);
342                $title = $ID.' ↵';
343                if($options['class'] !== false)
344                    $class = $options['class'];
345                else
346                    $class = "wikilink1";
347                $renderer->doc .= '<a href="#'.$url.'" title="'.$title.'" class="'.$class.'">';
348                $renderer->doc .= $name;
349                $renderer->doc .= '</a>';
350            break;
351            case 'internal':
352                $id = $url;
353                $params = '';
354                $linktype = 'content';
355                $parts = explode('?', $id, 2);
356                if (count($parts) === 2) {
357                    $id = $parts[0];
358                    $params = $parts[1];
359                }
360
361                // For empty $id we need to know the current $ID
362                // We need this check because _simpleTitle needs
363                // correct $id and resolve_pageid() use cleanID($id)
364                // (some things could be lost)
365                if ($id === '') {
366                    $id = $ID;
367                }
368
369                // default name is based on $id as given
370                $default = $renderer->_simpleTitle($id);
371
372                // now first resolve and clean up the $id
373                resolve_pageid(getNS($ID),$id,$exists);
374
375                if($name === null || !$options['render'])
376                    $name = $renderer->_getLinkTitle($name, $default, $isImage, $id, $linktype);
377                if ( !$isImage ) {
378                    if ( $exists ) {
379                        $class='wikilink1';
380                    } else {
381                        $class='wikilink2';
382                        $link['rel']='nofollow';
383                    }
384                } else {
385                    $class='media';
386                }
387
388                if($options['class'] !== false)
389                    $class = $options['class'];
390
391                //keep hash anchor
392                @list($id,$hash) = explode('#',$id,2);
393                if(!empty($hash)) $hash = $renderer->_headerToLink($hash);
394
395                //prepare for formating
396                $link['target'] = $conf['target']['wiki'];
397                $link['style']  = '';
398                $link['pre']    = '';
399                $link['suf']    = '';
400                // highlight link to current page
401                if ($id == $INFO['id']) {
402                    $link['pre']    = '<span class="curid">';
403                    $link['suf']    = '</span>';
404                }
405                $link['more']   = '';
406                $link['class']  = $class;
407                $link['url']    = wl($id, $params);
408                $link['name']   = $name;
409                $link['title']  = $id;
410
411                if($options['target'] !== false)
412                    $link['target'] = $options['target'];
413                //add search string
414                if($search){
415                    ($conf['userewrite']) ? $link['url'].='?' : $link['url'].='&amp;';
416                    if(is_array($search)){
417                        $search = array_map('rawurlencode',$search);
418                        $link['url'] .= 's[]='.join('&amp;s[]=',$search);
419                    }else{
420                        $link['url'] .= 's='.rawurlencode($search);
421                    }
422                }
423
424                //keep hash
425                if($hash) $link['url'].='#'.$hash;
426
427                //output formatted
428                if($returnonly){
429                    return $renderer->_formatLink($link);
430                }else{
431                    $renderer->doc .= $renderer->_formatLink($link);
432                }
433            break;
434          }
435        }
436        return false;
437    }
438}
439