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