diff --git a/action.php b/action.php new file mode 100644 --- /dev/null +++ b/action.php @@ -0,0 +1,76 @@ + + */ + +if(!defined('DOKU_INC')) die(); + +class action_plugin_linksenhanced extends DokuWiki_Action_Plugin { + + function register(Doku_Event_Handler $controller) { + $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax_call_unknown'); + $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'add_jsinfo_information'); + } + + /** + * Add the language variable to the JSINFO variable + */ + function add_jsinfo_information(Doku_Event $event, $param) { + global $JSINFO; + + $JSINFO['plugin']['linksenhanced']['sectok'] = getSecurityToken(); + } + + function handle_ajax_call_unknown(&$event, $param) { + if($event->data != 'plugin_linksenhanced') return; + + $event->preventDefault(); + $event->stopPropagation(); + global $INPUT; + + $action = trim($INPUT->post->str('action')); + $url = trim($INPUT->post->str('url')); + + if(!checkSecurityToken()) + { + echo "CSRF Attack."; + return; + } + + $data = array(); + + $data['result'] = false; + + if($action === 'check') + { + $client = new DokuHTTPClient(); + $client->sendRequest($url); + $httpcode = $client->status; + + $data['error'] = $client->error; + $data['httpcode'] = $httpcode; + + if($httpcode>=200 && $httpcode<300) + $data['result'] = true; + } + else + { + echo "Unknown operation."; + return; + } + + // If we are still here, JSON output is requested + + //json library of DokuWiki + require_once DOKU_INC . 'inc/JSON.php'; + $json = new JSON(); + + //set content type + header('Content-Type: application/json'); + echo $json->encode($data); + } + +} diff --git a/conf/default.php b/conf/default.php new file mode 100644 --- /dev/null +++ b/conf/default.php @@ -0,0 +1,9 @@ + + */ + +$conf['check_all_external'] = 0; +$conf['check_only_namespace'] = ''; diff --git a/conf/metadata.php b/conf/metadata.php new file mode 100644 --- /dev/null +++ b/conf/metadata.php @@ -0,0 +1,10 @@ + + */ + + +$meta['check_all_external'] = array('onoff'); +$meta['check_only_namespace'] = array('string'); diff --git a/plugin.info.txt b/plugin.info.txt --- a/plugin.info.txt +++ b/plugin.info.txt @@ -1,7 +1,7 @@ base linksenhanced author Andreas Boehler email dev@aboehler.at -date 2015-03-10 +date 2019-01-23 name linksenhanced plugin desc Enhanced Link Syntax url http://www.aboehler.at diff --git a/script.js b/script.js new file mode 100644 --- /dev/null +++ b/script.js @@ -0,0 +1,33 @@ +jQuery(function() { + + jQuery('div.dokuwiki a.plugin_linksenhanced_pending').each(function() { + var $link = jQuery(this).prop('href'); + if(!$link) + return; + var $obj = jQuery(this); + // If we are still here, we found our target link + jQuery.post( + DOKU_BASE + 'lib/exe/ajax.php', + { + call: 'plugin_linksenhanced', + url: $link, + action: 'check', + sectok: JSINFO.plugin.linksenhanced['sectok'], + }, + function(data) + { + var result = data['result']; + if(result === true) + { + $obj.removeClass('plugin_linksenhanced_pending'); + $obj.addClass('plugin_linksenhanced_online'); + } + else + { + $obj.removeClass('plugin_linksenhanced_pending'); + $obj.addClass('plugin_linksenhanced_offline'); + } + } + ); + }); +}); diff --git a/style.css b/style.css new file mode 100644 --- /dev/null +++ b/style.css @@ -0,0 +1,35 @@ +div.dokuwiki a.plugin_linksenhanced_pending::before{ + content: ''; + display: inline-block; + width: 1em; + height: 1em; + margin-right:3px; + -moz-border-radius: 0.5em; + -webkit-border-radius: 0.5em; + border-radius: 0.5em; + background-color: #e7793c; +} + +div.dokuwiki a.plugin_linksenhanced_online::before{ + content: ''; + display: inline-block; + width: 1em; + height: 1em; + margin-right:3px; + -moz-border-radius: 0.5em; + -webkit-border-radius: 0.5em; + border-radius: 0.5em; + background-color: #1ac423; +} + +div.dokuwiki a.plugin_linksenhanced_offline::before{ + content: ''; + display: inline-block; + width: 1em; + height: 1em; + margin-right:3px; + -moz-border-radius: 0.5em; + -webkit-border-radius: 0.5em; + border-radius: 0.5em; + background-color: #e52020; +} \ No newline at end of file diff --git a/syntax.php b/syntax.php --- a/syntax.php +++ b/syntax.php @@ -1,396 +1,415 @@ FontAwesome Code]] * [[render^http://www.dokuwiki.org|//Formatted Output, probably within a paragraph//]] * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Andreas Böhler */ if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); require_once(DOKU_PLUGIN.'syntax.php'); /** * All DokuWiki plugins to extend the parser/rendering mechanism * need to inherit from this class */ class syntax_plugin_linksenhanced extends DokuWiki_Syntax_Plugin { function getType() { return 'substition'; } function getPType() { return 'normal'; } function getAllowedTypes() { return array('container','substition','protected','disabled','paragraphs','formatting'); } function getSort() { return 202; } function connectTo($mode) { $this->Lexer->addSpecialPattern('\[\[(?:(?:[^[\]]*?\[.*?\])|.*?)\]\]',$mode,'plugin_linksenhanced'); } /** * Handle the match. Use either the standard linking mechanism or, when enabled, * pass the title through the parser */ function handle($match, $state, $pos, Doku_Handler $handler) { + global $ID; + $match = substr($match, 2, -2); $link = explode('|',$match,2); $nopara = false; $optionsSet = explode('^', $link[0], 2); $options = array('render' => false, 'noparagraph' => false, + 'check' => false, 'class' => false, 'target' => false); if(isset($optionsSet[1])) { $link[0] = $optionsSet[1]; $opts = explode(' ', $optionsSet[0]); foreach($opts as $opt) { if(strpos($opt, 'class') === 0) $options['class'] = explode('=', $opt)[1]; elseif(strpos($opt, 'target') === 0) $options['target'] = explode('=', $opt)[1]; else $options[$opt] = true; } } - + + if($this->getConf('check_all_external') == 1) + { + if($this->getConf('check_only_namespace') != '') + { + if(getNS($ID) == $this->getConf('check_only_namespace')) + $options['check'] = true; + } + else + { + $options['check'] = true; + } + } if($options['render'] && isset($link[1])) { $link[1] = $this->render_text($link[1]); if($options['noparagraph']) { $link[1] = str_replace('

', '', str_replace('

', '', $link[1])); } if($link[1] === '') $link[1] = null; } else if ( isset($link[1]) && preg_match('/^\{\{[^\}]+\}\}$/',$link[1]) ) { // If the title is an image, convert it to an array containing the image details $link[1] = Doku_Handler_Parse_Media($link[1]); } else if(!isset($link[1])) { $link[1] = null; } $link[0] = trim($link[0]); //decide which kind of link it is if ( preg_match('/^[a-zA-Z0-9\.]+>{1}.*$/u',$link[0]) ) { $type = 'interwiki'; }elseif ( preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u',$link[0]) ) { $type = 'windowsshare'; }elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$link[0]) ) { $type = 'external'; }elseif ( preg_match('<'.PREG_PATTERN_VALID_EMAIL.'>',$link[0]) ) { $type = 'email'; }elseif ( preg_match('!^#.+!',$link[0]) ){ $type = 'local'; $link[0] = substr($link[0],1); }else{ $type = 'internal'; } return array($type, $link, $options); } /** * Create output. This is largely based on the internal linking mechanism. */ function render($mode, Doku_Renderer $renderer, $data) { if (empty($data)) return false; global $conf; global $ID; global $INFO; global $lang; list($type, $link, $options) = $data; $url = $link[0]; $name = $link[1]; - if($mode == 'xhtml') { switch($type) { case 'interwiki': $interwiki = explode('>',$link[0],2); $wikiName = strtolower($interwiki[0]); $wikiUri = $interwiki[1]; $link = array(); $link['target'] = $conf['target']['interwiki']; $link['pre'] = ''; $link['suf'] = ''; $link['more'] = ''; if($name === null || !$options['render']) $link['name'] = $renderer->_getLinkTitle($name, $wikiUri, $isImage); else $link['name'] = $name; //get interwiki URL $exists = null; $url = $renderer->_resolveInterWiki($wikiName, $wikiUri, $exists); if(!$isImage) { $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $wikiName); $link['class'] = "interwiki iw_$class"; } else { $link['class'] = 'media'; } //do we stay at the same server? Use local target if(strpos($url, DOKU_URL) === 0 OR strpos($url, DOKU_BASE) === 0) { $link['target'] = $conf['target']['wiki']; } if($options['target'] !== false) $link['target'] = $options['target']; if($exists !== null && !$isImage) { if($exists) { $link['class'] .= ' wikilink1'; } else { $link['class'] .= ' wikilink2'; $link['rel'] = 'nofollow'; } } if($options['class'] !== false) $link['class'] = $options['class']; $link['url'] = $url; $link['title'] = htmlspecialchars($link['url']); //output formatted $renderer->doc .= $renderer->_formatLink($link); break; case 'windowsshare': //simple setup $link['target'] = $conf['target']['windows']; $link['pre'] = ''; $link['suf'] = ''; $link['style'] = ''; if($options['target'] !== false) $link['target'] = $options['target']; if($name === null || !$options['render']) $link['name'] = $renderer->_getLinkTitle($name, $url, $isImage); else $link['name'] = $name; if ( !$isImage ) { $link['class'] = 'windows'; } else { $link['class'] = 'media'; } if($options['class'] !== false) $link['class'] = $options['class']; $link['title'] = $renderer->_xmlEntities($url); $url = str_replace('\\','/',$url); $url = 'file:///'.$url; $link['url'] = $url; //output formatted $renderer->doc .= $renderer->_formatLink($link); break; case 'external': if($name === null || !$options['render']) $name = $renderer->_getLinkTitle($name, $url, $isImage); // url might be an attack vector, only allow registered protocols $schemes = getSchemes(); list($scheme) = explode('://',$url); $scheme = strtolower($scheme); if(!in_array($scheme,$schemes)) $url = ''; // is there still an URL? if(!$url){ $renderer->doc .= $name; return; } // set class if ( !$isImage ) { $class='urlextern'; } else { $class='media'; } if($options['class'] !== false) $class = $options['class']; //prepare for formating $link['target'] = $conf['target']['extern']; $link['style'] = ''; $link['pre'] = ''; $link['suf'] = ''; $link['more'] = ''; $link['class'] = $class; $link['url'] = $url; if($options['target'] !== false) $link['target'] = $options['target']; - + + if($options['check'] !== false) + { + $link['class'] = 'plugin_linksenhanced_pending'; + } $link['name'] = $name; $link['title'] = $renderer->_xmlEntities($url); if($conf['relnofollow']) $link['more'] .= ' rel="nofollow"'; //output formatted $renderer->doc .= $renderer->_formatLink($link); break; case 'email': $link = array(); $link['target'] = ''; $link['pre'] = ''; $link['suf'] = ''; $link['style'] = ''; $link['more'] = ''; if($name === null || !$options['render']) $name = $renderer->_getLinkTitle($name, '', $isImage); if ( !$isImage ) { $link['class']='mail'; } else { $link['class']='media'; } if($options['class'] !== false) $link['class'] = $options['class']; $url = $renderer->_xmlEntities($url); $url = obfuscate($url); $title = $url; if(empty($name)){ $name = $url; } if($conf['mailguard'] == 'visible') $url = rawurlencode($url); $link['url'] = 'mailto:'.$url; $link['name'] = $name; $link['title'] = $title; //output formatted $renderer->doc .= $renderer->_formatLink($link); break; case 'local': if($name === null || !$options['render']) $name = $renderer->_getLinkTitle($name, $url, $isImage); $url = $renderer->_headerToLink($url); $title = $ID.' ↵'; if($options['class'] !== false) $class = $options['class']; else $class = "wikilink1"; $renderer->doc .= ''; $renderer->doc .= $name; $renderer->doc .= ''; break; case 'internal': $id = $url; $params = ''; $linktype = 'content'; $parts = explode('?', $id, 2); if (count($parts) === 2) { $id = $parts[0]; $params = $parts[1]; } // For empty $id we need to know the current $ID // We need this check because _simpleTitle needs // correct $id and resolve_pageid() use cleanID($id) // (some things could be lost) if ($id === '') { $id = $ID; } // default name is based on $id as given $default = $renderer->_simpleTitle($id); // now first resolve and clean up the $id resolve_pageid(getNS($ID),$id,$exists); if($name === null || !$options['render']) $name = $renderer->_getLinkTitle($name, $default, $isImage, $id, $linktype); if ( !$isImage ) { if ( $exists ) { $class='wikilink1'; } else { $class='wikilink2'; $link['rel']='nofollow'; } } else { $class='media'; } if($options['class'] !== false) $class = $options['class']; //keep hash anchor @list($id,$hash) = explode('#',$id,2); if(!empty($hash)) $hash = $renderer->_headerToLink($hash); //prepare for formating $link['target'] = $conf['target']['wiki']; $link['style'] = ''; $link['pre'] = ''; $link['suf'] = ''; // highlight link to current page if ($id == $INFO['id']) { $link['pre'] = ''; $link['suf'] = ''; } $link['more'] = ''; $link['class'] = $class; $link['url'] = wl($id, $params); $link['name'] = $name; $link['title'] = $id; if($options['target'] !== false) $link['target'] = $options['target']; //add search string if($search){ ($conf['userewrite']) ? $link['url'].='?' : $link['url'].='&'; if(is_array($search)){ $search = array_map('rawurlencode',$search); $link['url'] .= 's[]='.join('&s[]=',$search); }else{ $link['url'] .= 's='.rawurlencode($search); } } //keep hash if($hash) $link['url'].='#'.$hash; //output formatted if($returnonly){ return $renderer->_formatLink($link); }else{ $renderer->doc .= $renderer->_formatLink($link); } break; } } return false; } }