1<?php 2/** 3 * Repository Plugin: show files from a remote repository with GesHi syntax highlighting 4 * Syntax: {{repo>[url] [cachetime]|[title]}} 5 * [url] - (REQUIRED) base URL of the code repository 6 * [cachetime] - (OPTIONAL) how often the cache should be refreshed; 7 * a number followed by one of these chars: 8 * d for day, h for hour or m for minutes; 9 * the minimum accepted value is 10 minutes. 10 * [title] - (OPTIONAL) a string to display as the base path 11 * of the repository. 12 * 13 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 14 * @author Esther Brunner <wikidesign@gmail.com> 15 * @author Christopher Smith <chris@jalakai.co.uk> 16 * @author Doug Daniels <Daniels.Douglas@gmail.com> 17 * @author Myron Turner <turnermm02@shaw.ca> 18 */ 19 20// must be run inside DokuWiki 21if(!defined('DOKU_INC')) die(); 22 23if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 24require_once(DOKU_PLUGIN.'syntax.php'); 25 26/** 27 * All DokuWiki plugins to extend the parser/rendering mechanism 28 * need to inherit from this class 29 */ 30class syntax_plugin_repo extends DokuWiki_Syntax_Plugin { 31 function getType() { return 'substition'; } 32 function getSort() { return 301; } 33 function getPType() { return 'block'; } 34 function connectTo($mode) { 35 $this->Lexer->addSpecialPattern("{{repo>.+?}}", $mode, 'plugin_repo'); 36 } 37 38 /** 39 * Handle the match 40 */ 41 function handle($match, $state, $pos, Doku_Handler $handler) { 42 43 $match = substr($match, 7, -2); 44 list($base, $title) = explode('|', $match, 2); 45 list($base, $refresh) = explode(' ', $base, 2); 46 47 if (preg_match('/(\d+)([dhm])/', $refresh, $match)) { 48 $period = array('d' => 86400, 'h' => 3600, 'm' => 60); 49 // n * period in seconds, minimum 10 minutes 50 $refresh = max(600, $match[1] * $period[$match[2]]); 51 } else { 52 // default to 4 hours 53 $refresh = 14400; 54 } 55 56 return array(trim($base), trim($title), $pos, $refresh); 57 } 58 59 /** 60 * Create output 61 */ 62 function render($mode, Doku_Renderer $renderer, $data) { 63 64 // construct requested URL 65 $base = hsc($data[0]); 66 $title = ($data[1] ? hsc($data[1]) : $base); 67 $path = hsc($_REQUEST['repo']); 68 $url = $base.$path; 69 70 if ($mode == 'xhtml') { 71 72 // prevent caching to ensure the included page is always fresh 73 $renderer->info['cache'] = false; 74 75 // output 76 $renderer->header($title.$path, 5, $data[2]); 77 $renderer->section_open(5); 78 if ($url[strlen($url) - 1] == '/') { // directory 79 $this->_directory($base, $renderer, $path, $data[3]); 80 } elseif (preg_match('/(jpe?g|gif|png)$/i', $url)) { // image 81 $this->_image($url, $renderer); 82 } else { // source code file 83 $this->_codefile($url, $renderer, $data[3]); 84 } 85 if ($path) $this->_location($path, $title, $renderer); 86 $renderer->section_close(); 87 88 // for metadata renderer 89 } elseif ($mode == 'metadata') { 90 $renderer->meta['relation']['haspart'][$url] = 1; 91 } 92 93 return $ok; 94 } 95 96 /** 97 * Handle remote directories 98 */ 99 function _directory($url, &$renderer, $path, $refresh) { 100 global $conf; 101 102 $cache = getCacheName($url.$path, '.repo'); 103 $mtime = @filemtime($cache); // 0 if it doesn't exist 104 105 if (($mtime != 0) && !$_REQUEST['purge'] && ($mtime > time() - $refresh)) { 106 $idx = io_readFile($cache, false); 107 if ($conf['allowdebug']) $idx .= "\n<!-- cachefile $cache used -->\n"; 108 } else { 109 $items = $this->_index($url, $path); 110 $idx = html_buildlist($items, 'idx', 'repo_list_index', 'html_li_index'); 111 112 io_saveFile($cache, $idx); 113 if ($conf['allowdebug']) $idx .= "\n<!-- no cachefile used, but created -->\n"; 114 } 115 116 $renderer->doc .= $idx; 117 } 118 119 /** 120 * Extract links and list them as directory contents 121 */ 122 function _index($url, $path, $base = '', $lvl = 0) { 123 124 // download the index html file 125 $http = new DokuHTTPClient(); 126 $http->timeout = 25; //max. 25 sec 127 $data = $http->get($url.$base); 128 preg_match_all('/<li><a href="(.*?)">/i', $data, $results); 129 130 $lvl++; 131 foreach ($results[1] as $result) { 132 if ($result == '../') continue; 133 134 $type = ($result[strlen($result) - 1] == '/' ? 'd' : 'f'); 135 $open = (($type == 'd') && (strpos($path, $base.$result) === 0)); 136 $items[] = array( 137 'level' => $lvl, 138 'type' => $type, 139 'path' => $base.$result, 140 'open' => $open, 141 ); 142 if ($open) { 143 $items = array_merge($items, $this->_index($url, $path, $base.$result, $lvl)); 144 } 145 } 146 return $items; 147 } 148 149 /** 150 * Handle remote images 151 */ 152 function _image($url, &$renderer) { 153 $renderer->p_open(); 154 $renderer->externalmedia($url, NULL, NULL, NULL, NULL, 'recache'); 155 $renderer->p_close(); 156 } 157 158 /** 159 * Handle remote source code files: display as code box with link to file at the end 160 */ 161 function _codefile($url, &$renderer, $refresh) { 162 163 // output the code box with syntax highlighting 164 $renderer->doc .= $this->_cached_geshi($url, $refresh); 165 166 // and show a link to the original file 167 $renderer->p_open(); 168 $renderer->externallink($url); 169 $renderer->p_close(); 170 } 171 172 /** 173 * Wrapper for GeSHi Code Highlighter, provides caching of its output 174 * Modified to calculate cache from URL so we don't have to re-download time and again 175 * 176 * @author Christopher Smith <chris@jalakai.co.uk> 177 * @author Esther Brunner <wikidesign@gmail.com> 178 */ 179 function _cached_geshi($url, $refresh) { 180 global $conf; 181 182 $cache = getCacheName($url, '.code'); 183 $mtime = @filemtime($cache); // 0 if it doesn't exist 184 185 if (($mtime != 0) && !$_REQUEST['purge'] && 186 ($mtime > time() - $refresh) && 187 ($mtime > filemtime(DOKU_INC.'vendor/geshi/geshi/src/geshi.php'))) { 188 189 $hi_code = io_readFile($cache, false); 190 if ($conf['allowdebug']) $hi_code .= "\n<!-- cachefile $cache used -->\n"; 191 192 } else { 193 require_once(DOKU_INC .'vendor/geshi/geshi/src/geshi.php'); 194 195 // get the source code language first 196 $search = array('/^htm/', '/^js$/'); 197 $replace = array('html4strict', 'javascript'); 198 $lang = preg_replace($search, $replace, substr(strrchr($url, '.'), 1)); 199 200 // download external file 201 $http = new DokuHTTPClient(); 202 $http->timeout = 25; //max. 25 sec 203 $code = $http->get($url); 204 205 $geshi = new GeSHi($code, strtolower($lang), DOKU_INC .'vendor/geshi/geshi/src/geshi'); 206 $geshi->set_encoding('utf-8'); 207 $geshi->enable_classes(); 208 $geshi->set_header_type(GESHI_HEADER_PRE); 209 $geshi->set_overall_class("code $language"); 210 $geshi->set_link_target($conf['target']['extern']); 211 212 $hi_code = $geshi->parse_code(); 213 214 io_saveFile($cache, $hi_code); 215 if ($conf['allowdebug']) $hi_code .= "\n<!-- no cachefile used, but created -->\n"; 216 } 217 218 return $hi_code; 219 } 220 221 /** 222 * Show where we are with link back to main repository 223 */ 224 function _location($path, $title, &$renderer) { 225 global $ID; 226 227 $renderer->p_open(); 228 $renderer->internallink($ID, $title); 229 230 $base = ''; 231 $dirs = explode('/', $path); 232 $n = count($dirs); 233 for ($i = 0; $i < $n-1; $i++) { 234 $base .= hsc($dirs[$i]).'/'; 235 $renderer->doc .= '<a href="'.wl($ID, 'repo='.$base).'" class="idx_dir">'. 236 hsc($dirs[$i]).'/</a>'; 237 } 238 239 $renderer->doc .= hsc($dirs[$n-1]); 240 $renderer->p_close(); 241 } 242 243} 244 245/** 246 * For html_buildlist() 247 */ 248function repo_list_index($item) { 249 global $ID; 250 251 if ($item['type'] == 'd') { 252 $title = substr($item['path'], 0, -1); 253 $class = 'idx_dir'; 254 } else { 255 $title = $item['path']; 256 $class = 'wikilink1'; 257 } 258 $title = substr(strrchr('/'.$title, '/'), 1); 259 return '<a href="'.wl($ID, 'repo='.$item['path']).'" class="'.$class.'">'.$title.'</a>'; 260} 261// vim:ts=4:sw=4:et:enc=utf-8: 262