1<?php
2/**
3 * DokuWiki Syntax Plugin InlineJS Preloader
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Satoshi Sahara <sahara.satoshi@gmail.com>
7 *
8 * @see also: https://www.dokuwiki.org/devel:javascript
9 *
10 * Allow inline JavaScript in DW page.
11 * Make specified files to be loaded in head section of HTML by action component.
12 *
13 * SYNTAX:
14 *         <PRELOAD debug>
15 *           /path/to/javascript.js
16 *           /path/to/style.css
17 *           <script src="//example.con/javascript.js"></script>
18 *           <link rel="stylesheet" href="//example.com/style.css">
19 *           <script>...</script>
20 *           <style>...</style>
21 *         </PRELOAD>
22 */
23
24// must be run within Dokuwiki
25if (!defined('DOKU_INC')) die();
26
27class syntax_plugin_inlinejs_preloader extends DokuWiki_Syntax_Plugin
28{
29    public function getType()
30    {   // Syntax Type
31        return 'protected';
32    }
33
34    public function getPType()
35    {   // Paragraph Type
36        return 'block';
37    }
38
39    /**
40     * Connect pattern to lexer
41     */
42    protected $mode, $pattern;
43
44    public function preConnect()
45    {
46        // drop 'syntax_' from class name
47        $this->mode = substr(get_class($this), 7);
48
49        // syntax pattern
50        $this->pattern[1] = '<PRELOAD\b[^\n\r]*?>(?=.*?</PRELOAD>)';
51        $this->pattern[21] = '<link [^\n\r]*?>';
52        $this->pattern[22] = '<style>.*?</style>';
53        $this->pattern[23] = '<script\b[^\n\r]*?>.*?</script>';
54        $this->pattern[4] = '</PRELOAD>';
55    }
56
57    public function connectTo($mode)
58    {
59        $this->Lexer->addEntryPattern($this->pattern[1], $mode, $this->mode);
60    }
61
62    public function postConnect()
63    {
64        $this->Lexer->addExitPattern($this->pattern[4], $this->mode);
65        $this->Lexer->addPattern($this->pattern[21], $this->mode);
66        $this->Lexer->addPattern($this->pattern[22], $this->mode);
67        $this->Lexer->addPattern($this->pattern[23], $this->mode);
68    }
69
70    public function getSort()
71    {   // sort number used to determine priority of this mode
72        return 110;
73    }
74
75
76    /**
77     * Plugin features
78     */
79    protected $entries = null;
80    protected $opts    = null;
81
82    /**
83     * add an entry to dedicated class property
84     */
85    private function _add_entry($tag, $data='')
86    {
87        switch ($tag) {
88            case 'link':
89                $this->entries[] = array(
90                             '_tag' => 'link',
91                             'rel'  => 'stylesheet',
92                          // 'type' => 'text/css',
93                             'href' => $data,
94                );
95                break;
96            case 'style':
97                $this->entries[] = array(
98                             '_tag' => 'style',
99                          // 'type' => 'text/css',
100                             '_data' => $data,
101                );
102                break;
103            case 'script':
104                $this->entries[] = array(
105                             '_tag' => 'script',
106                          // 'type' => 'text/javascript',
107                             '_data'=> $data,
108                );
109                break;
110            case 'js':
111                $this->entries[] = array(
112                             '_tag' => 'script',
113                          // 'type' => 'text/javascript',
114                             'src'  => $data,
115                          // '_data'=> '',
116                );
117                break;
118        }
119        return count($this->entries);
120    }
121
122
123    /**
124     * Handle the match
125     */
126    public function handle($match, $state, $pos, Doku_Handler $handler)
127    {
128        global $conf;
129
130        switch ($state) {
131            case DOKU_LEXER_ENTER:
132                // initialize class property
133                $this->opts    = array();
134                $this->entries = array();
135
136                // check whether optional parameter exists
137                $this->opts['debug'] = (preg_match('/debug/',$match)) ? true : false;
138                if ($match != '<PRELOAD>') { $this->opts['debug'] = true; }
139                return false;
140
141            case DOKU_LEXER_MATCHED:
142                // identify syntax
143                if (preg_match('/\w+/', substr($match, 1, 6), $matches)) {
144                    $tag = $matches[0];
145                }
146                switch ($tag) {
147                    case 'link':
148                        // assume rel="stylesheet", lazy handling of external css
149                        if (preg_match('/\bhref=\"([^\"]*)\" ?/', $match, $attrs)) {
150                            $this->_add_entry('link', $attrs[1]);
151                        }
152                        break;
153                    case 'style':
154                        $css = substr($match, 7, -8);
155                        if (!empty($css)) {
156                            $this->_add_entry('style', $css);
157                        }
158                        break;
159                    case 'script':
160                        if (preg_match('/\bsrc=\"([^\"]*)\" ?/', $match, $attrs)) {
161                            $this->_add_entry('js', $attrs[1]);
162                        } else {
163                            $source = substr($match, 8, -9);
164                            if (!empty($source)) {
165                                $this->_add_entry('script', $source);
166                            }
167                        }
168                        break;
169                }
170                return false;
171
172            case DOKU_LEXER_UNMATCHED:
173                $matches = explode("\n", $match);
174                foreach ($matches as $entry) {
175                    // remove comment line after "#"
176                    list($pathname, $comment) = explode('#', $entry, 2);
177                    $pathname = trim($pathname);
178
179                    // check entry type for local file path
180                    $entrytype = strtolower(pathinfo($pathname, PATHINFO_EXTENSION));
181                    if (in_array($entrytype, array('css','js'))) {
182                        $tag = ($entrytype == 'css') ? 'link' : 'js';
183                        $this->_add_entry($tag, $pathname);
184                    }
185                }
186                return false;
187
188            case DOKU_LEXER_EXIT:
189                $data = array($this->opts, $this->entries);
190                $this->opts    = null;
191                $this->entries = null;
192
193                if ($this->getConf('follow_htmlok') && !$conf['htmlok']) {
194                    $msg = $this->getPluginComponent().' is disabled.';
195                    msg($this->getPluginName().': '.$msg, -1);
196                    return false;
197                }
198                return $data;
199        }
200    }
201
202    /**
203     * Create output
204     */
205    public function render($format, Doku_Renderer $renderer, $data)
206    {
207        list($opts, $entries) = $data;
208
209        switch ($format) {
210            case 'metadata' :
211                // metadata will be treated by action plugin
212                $renderer->meta['plugin_inlinejs'] = $entries;
213                return true;
214
215            case 'xhtml' :
216                if ($opts['debug']) {
217                    // debug: show html code to be added in head section
218                    $html = '<div class="notify">';
219                    $html.= hsc($this->getLang('preloader-intro')).DOKU_LF;
220                    $html.= '<ol>';
221                    foreach ($entries as $entry) {
222                        $attr = buildAttributes($entry);
223                        $out = '<'.$entry['_tag'].($attr ? ' '.$attr : '');
224                        if (isset($entry['_data'])) {
225                            $out.= '>'.$entry['_data'].'</'.$entry['_tag'].'>';
226                        } else {
227                            $out.= '>';
228                        }
229                        $html.= '<li style="white-space:pre;">'.hsc($out).'</li>';
230                    }
231                    $html.= '</ol></div>'.DOKU_LF;
232                    $renderer->doc .= $html;
233                }
234                return true;
235        }
236        return false;
237    }
238}
239