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