1<?php
2
3/**
4 * DokuWiki Plugin gh (Syntax Component)
5 *
6 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
7 * @author  Andreas Gohr <andi@splitbrain.org>
8 */
9class syntax_plugin_gh extends DokuWiki_Syntax_Plugin
10{
11
12    /**
13     * Extension to highlighting language mapping
14     *
15     * When a extenison is not found here it's assumed the extension name equals the language
16     *
17     * @var array
18     */
19    protected $ext2lang = array(
20        'as' => 'actionscript3',
21        'bas' => 'gwbasic',
22        'h' => 'c',
23        'hpp' => 'cpp',
24        'hs' => 'haskell',
25        'htm' => 'html5',
26        'html' => 'html5',
27        'js' => 'javascript',
28        'pas' => 'pascal',
29        'pl' => 'perl6',
30        'py' => 'python',
31        'rb' => 'ruby',
32        'sh' => 'bash',
33        'yml' => 'yaml',
34    );
35
36    /**
37     * @return string Syntax mode type
38     */
39    public function getType()
40    {
41        return 'substition';
42    }
43
44    /**
45     * @return string Paragraph type
46     */
47    public function getPType()
48    {
49        return 'block';
50    }
51
52    /**
53     * @return int Sort order - Low numbers go before high numbers
54     */
55    public function getSort()
56    {
57        return 155;
58    }
59
60    /**
61     * Connect lookup pattern to lexer.
62     *
63     * @param string $mode Parser mode
64     */
65    public function connectTo($mode)
66    {
67        $this->Lexer->addSpecialPattern('{{gh>[^}]*}}', $mode, 'plugin_gh');
68
69    }
70
71    /** @inheritDoc */
72    public function handle($match, $state, $pos, Doku_Handler $handler)
73    {
74        $match = trim(substr($match, 5, -2));
75        list($url, $lines) = explode(' ', $match, 2);
76        list($from, $to) = explode('-', $lines, 2);
77
78        $data = array(
79            'from' => (int)$from,
80            'to' => (int)$to
81        );
82
83        if (preg_match('/([\w.\-]+)\/([\w-]+\/[\w-]+(\/[\w-]+)?)\/blob\/([\w-]+)\/(.*)$/', $url, $m)) {
84            $data['base'] = $m[1];
85            $data['repo'] = $m[2];
86            $data['blob'] = $m[4];
87            $data['file'] = $m[5];
88        }
89
90        return $data;
91    }
92
93    /** @inheritDoc */
94    public function render($mode, Doku_Renderer $renderer, $data)
95    {
96        if ($mode != 'xhtml') return false;
97
98        if (!$data['base']) return false;
99        if (!$data['repo']) return false;
100        if (!$data['blob']) return false;
101        if (!$data['file']) return false;
102
103        global $ID;
104        global $INPUT;
105
106        if ($data['base'] == 'github.com') {
107            $raw = 'https://raw.githubusercontent.com/' . $data['repo'] . '/' . $data['blob'] . '/' . $data['file'];
108        } else {
109            $raw = 'https://' . $data['base'] . '/' . $data['repo'] . '/raw/' . $data['blob'] . '/' . $data['file'];
110        }
111        $url = 'https://' . $data['base'] . '/' . $data['repo'] . '/blob/' . $data['blob'] . '/' . $data['file'];
112
113        // check if there's a usable cache
114        $text = false;
115        $cache = getCacheName($raw, '.ghplugin');
116        $tcache = @filemtime($cache);
117        $tpage = @filemtime(wikiFN($ID));
118        if ($tcache && $tpage && !$INPUT->bool('purge')) {
119            $now = time();
120            if ($now - $tcache < ($now - $tpage) * 2) {
121                // use cache when it's younger than twice the age of the page
122                $text = io_readFile($cache);
123            }
124        }
125
126        // no cache loaded, get from HTTP
127        if (!$text) {
128            $http = new DokuHTTPClient();
129            $text = $http->get($raw);
130
131            if ($text) {
132                // save to cache
133                io_saveFile($cache, $text);
134            } else if ($tcache) {
135                // HTTP failed but there's an old cache - use it
136                $text = io_readFile($cache);
137            }
138        }
139
140        // WTF? there's nothing. we're done here
141        if (!$text) return true;
142
143        // apply line ranges
144        if ($data['from'] || $data['to']) {
145            $len = $data['to'] - $data['from'];
146            if ($len <= 0) $len = null;
147
148            $lines = explode("\n", $text);
149            $lines = array_slice($lines, $data['from'], $len);
150            $text = join("\n", $lines);
151        }
152
153        // add icon
154        list($ext) = mimetype($data['file'], false);
155        $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext);
156        $class = 'mediafile mf_' . $class;
157
158        // add link
159        $renderer->doc .= '<dl class="file">' . DOKU_LF;
160        $renderer->doc .= '<dt><a href="' . $url . '" class="' . $class . '">';
161        $renderer->doc .= hsc($data['file']);
162        $renderer->doc .= '</a></dt>' . DOKU_LF . '<dd>';
163
164        if (isset($this->ext2lang[$ext])) {
165            $lang = $this->ext2lang[$ext];
166        } else {
167            $lang = $ext;
168        }
169
170        $renderer->file($text, $lang);
171        $renderer->doc .= '</dd>';
172        $renderer->doc .= '</dl>';
173        return true;
174    }
175}
176