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