1<?php 2/** 3 * Plugin runCommand: Give the ability of execute a script and display its output. 4 * Working with ajax, required 5 * 6 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 7 * @author Alessandro Celli <aelsantex@gmail.com> 8 * @ChangeLog: 9 * 2009/08/19: Added Custom text button from Steve 10 * 2009/08/20: Added Custom text and hidden value for cancel button 11 * 2013/05/27: Starting rebuild for Weatherwax release 12 * 2013/09/08: Rewrite debug function to use the dokuwiki's debug function. 13 * 2014/03/12: Fixed script.js: removed image after execution 14 * 2014/03/20: Completed render functions 15 * 2014/03/26: Added a newline if the output type is set to choice 16 * 2014/03/26: Fixed Slider value passing to post action 17 * 2014/03/31: Fixed bad space parse for date field 18 */ 19 20//---- CONSTANT and INCLUSION ------------------------------------------------------------------------------------------ 21// must be run within Dokuwiki 22if(!defined('DOKU_INC')) { 23 define('DOKU_INC','/'.realpath(dirname(__FILE__).'/../../').'/'); 24} 25if(!defined('DOKU_PLUGIN')) { 26 define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 27} 28 29// Run Command Constant 30if(!defined('RC_EXP_COMMAND')) define('RC_EXP_COMMAND','command'); 31if(!defined('RC_EXP_OUTPUT_TYPE')) define('RC_EXP_OUTPUT_TYPE','outputType'); 32if(!defined('RC_EXP_ARG')) define('RC_EXP_ARG','arg'); 33if(!defined('RC_EXP_RUNBUTTONTEXT')) define('RC_EXP_RUNBUTTONTEXT','runButtonText'); 34if(!defined('RC_EXP_CANCELBUTTONTEXT')) define('RC_EXP_CANCELBUTTONTEXT','cancelButtonText'); 35if(!defined('RC_ARG_HIDDEN')) define('RC_ARG_HIDDEN','hidden'); 36if(!defined('RC_ARG_TEXT')) define('RC_ARG_TEXT','text'); 37if(!defined('RC_ARG_LIST')) define('RC_ARG_LIST','list'); 38if(!defined('RC_ARG_AUTOCOMPLETE')) define('RC_ARG_AUTOCOMPLETE','autocomplete'); 39if(!defined('RC_ARG_SLIDER')) define('RC_ARG_SLIDER','slider'); 40if(!defined('RC_ARG_SPINNER')) define('RC_ARG_SPINNER','spinner'); 41if(!defined('RC_ARG_DATE')) define('RC_ARG_DATE','date'); 42if(!defined('RC_CONST_NO_CANCEL_BUTTON')) define('RC_CONST_NO_CANCEL_BUTTON','none'); 43 44require_once(DOKU_PLUGIN.'syntax.php'); 45 46/** 47 * All DokuWiki plugins to extend the parser/rendering mechanism 48 * need to inherit from this class 49 */ 50class syntax_plugin_runcommand extends DokuWiki_Syntax_Plugin { 51 var $currentCommand = 0; // Global variable 52 var $binaryOutput = False; // Type of output, as default is not a binary value 53 54 function debug($msg,$msgLevel) { // DEBUG 55 // Write log on data/cache/debug.log 56 if ($this->getConf('rc_debug_level') >= $msgLevel) { 57 dbglog("RC:".$msg); 58 } 59 } 60 61 62 // Runcommand analyze byself its component; it use only the main tag analyzer by dokuwiki 63 // BY_DOC: modes which have a start and end token but inside which no other modes should be applied 64 function getType() { return 'protected'; } 65 66 // BY_DOC: The plugin output will be inside a paragraph (or another block element), no paragraphs will be inside 67 function getPType() { return 'normal'; } 68 69 // BY_DOC: Returns a number used to determine in which order modes are added, also see parser, order of adding modes and getSort list. 70 function getSort() { return 432; } 71 72 // BY_DOC: This function is inherited from Doku_Parser_Mode 2). Here is the place to register the regular expressions needed to match your syntax. 73 function connectTo($mode) { 74 $this->Lexer->addSpecialPattern('<runcommand>.*?</runcommand>',$mode,'plugin_runcommand'); 75 } 76 77 78 // BY_DOC: to prepare the matched syntax for use in the renderer 79 // This plugin works with the state: DOKU_LEXER_ENTER, DOKU_LEXER_UNMATCH and DOKU_LEXER_EXIT 80 function handle($match, $state, $pos, &$handler){ 81 //if ($state <> DOKU_LEXER_ENTER) { exit; }; 82 $argId = 1; 83 84 $match = substr($match,12,-13); // remove form wrap 85 $this->debug("HANDLE:MATCH=".$match,3); 86 $lines = explode("\n",$match); 87 88 $cmds = array(); 89 // Process all lines from runcommand body 90 foreach($lines as $line){ 91 $line = trim($line); 92 // Skip empty lines 93 if(!$line) continue; 94 $this->debug("HANDLE:LINE=".$line,3); 95 96 //Split a line into its components 97 $objLine=explode("|",$line); 98 99 // Identify the base type for this line 100 switch($objLine[0]){ 101 case RC_EXP_COMMAND: 102 $field=array('key' => RC_EXP_COMMAND, 'value' => $objLine[1]); 103 break; 104 case RC_EXP_OUTPUT_TYPE: 105 $field=array('key' => RC_EXP_OUTPUT_TYPE, 'value' => $objLine[1]); 106 // Check if is a binary output 107 if ($objLine[1] == 'binary') { 108 $binaryOutput = True; 109 } 110 break; 111 case RC_EXP_RUNBUTTONTEXT: 112 $field=array('key' => RC_EXP_RUNBUTTONTEXT, 'value' => $objLine[1]); 113 break; 114 case RC_EXP_CANCELBUTTONTEXT: 115 $field=array('key' => RC_EXP_CANCELBUTTONTEXT, 'value' => $objLine[1]); 116 break; 117 default: // This line is an argument. 118 $argName = $objLine[0]; 119 $this->debug("HANDLE:argName=".$argName,2); 120 121 $argLabel = $objLine[1]; 122 $this->debug("HANDLE:argLabel=".$argLabel,2); 123 124 $argFlags=preg_split("/[\s,]+/", $objLine[2],-1,PREG_SPLIT_NO_EMPTY); 125 $this->debug("HANDLE:argFlags=".$argFlags,2); 126 127 $argField=explode("=",$objLine[3]); 128 $argType = $argField[0]; 129 $this->debug("HANDLE:argType=".$argType,2); 130 131 switch($argType) { 132 case RC_ARG_HIDDEN: 133 case RC_ARG_TEXT: 134 $argValue = $argField[1]; 135 break; 136 case RC_ARG_LIST: 137 $argValue = $this->parseFieldList($argField[1]); 138 break; 139 case RC_ARG_AUTOCOMPLETE: 140 $argValue = $this->parseFieldAutocomplete($argField[1]); 141 break; 142 case RC_ARG_SLIDER: 143 $argValue = $this->parseFieldSlider($argField[1]); 144 break; 145 case RC_ARG_SPINNER: 146 $argValue = $this->parseFieldSpinner($argField[1]); 147 break; 148 case RC_ARG_DATE: 149 $argValue = $this->parseFieldDate($argField[1]); 150 break; 151 default: 152 return null; 153 }; 154 $field=array('key' => RC_EXP_ARG, 'name' => $argName, 'label' => $argLabel, 'flags' => $argFlags, 'type' => $argType, 'value' => $argValue); 155 break; 156 }; 157 array_push($cmds, $field); 158 }; 159 $this->debug("HANDLE:---------- PARSING -------------------------------------",1); 160 $this->debug("HANDLE:".print_r($cmds,true),1); 161 return $cmds; 162 } 163 164 function parseFieldList($args){ 165 $fields = explode(";",$args); 166 $result = array(); 167 foreach ($fields as $field) { 168 if(!$field) continue; 169 $temp = explode(":",$field); 170 if ($temp[0] == 'default') { 171 $defaultValue=$temp[1]; 172 } else { 173 $result[] = array('item' => $temp[0], 'value' => $temp[1], 'default' => '0'); 174 } 175 } 176 return $result; 177 } 178 179 function parseFieldAutocomplete($args){ 180 return preg_split('/;/', $args,-1,PREG_SPLIT_NO_EMPTY); 181 } 182 183 function parseFieldSlider($args){ 184 preg_match_all("/([^;=]+):([^;=]+)/", $args, $r); 185 return array_combine($r[1], $r[2]); 186 } 187 188 function parseFieldSpinner($args){ 189 preg_match_all("/([^;=]+):([^;=]+)/", $args, $r); 190 return array_combine($r[1], $r[2]); 191 } 192 193 function parseFieldDate($args){ 194 preg_match_all("/([^;=]+):([^;=]+)/", $args, $r); 195 return array_combine($r[1], $r[2]); 196 } 197 198 199 function render($mode, &$renderer, $data) { 200 global $currentCommand; // Global variable who keep the unique Id of the runcommand form. 201 $currentCommand += 1; 202 203 // Rendering phase have 2 bocks, the jquery/javascript block and the form block where are defined the field objects. 204 $jqueryBlock="<script>\njQuery(function() {\n"; // JQuery block to rendered 205 //$jqueryBlock="<script type=\"text/javascript\">/*<![CDATA[*/\n"; 206 $htmlBlock="<form id=\"rcform".$currentCommand."\">\n"; // HTML block to rendered 207 208 if($mode == 'xhtml'){ 209 if ($data == null) { // If data is null, the user write block wrong. 210 $renderer->doc .= $this->renderWrongSyntax(); 211 return; 212 }; 213 214 $this->debug("RENDER:MODE=".$mode."\ncurrentCommand=".$currentCommand,2); 215 216 // Extract the default button label from config file. 217 $buttonSubmitText = $this->getLang('btn_submit'); 218 $buttonCancelText = $this->getLang('btn_cancel'); 219 220 // Rendering data objects 221 foreach ($data as $element) { 222 $this->debug("RENDER:Element:".print_r($element,true),2); 223 switch($element['key']){ 224 case RC_EXP_COMMAND: 225 if ($binaryOutput) { // It give back a binary file to download 226 // TODO 227 } else { // The output is text, html or a wiki syntax code. 228 $htmlBlock .= $this->renderFormHidden('command','','',$this->prepareCommand($element['value'])); 229 }; 230 break; 231 case RC_EXP_OUTPUT_TYPE: 232 if ($element['value'] == 'choice') { // If the type is required to the user it build a list. 233 $htmlBlock .= $this->renderFormListBox('outputType',$this->getLang('lbl_outputFormat'),array('newline'),array( 234 array( 'item' => 'text', 'value' => $this->getLang('fld_text')), 235 array( 'item' => 'html', 'value' => $this->getLang('fld_html')), 236 array( 'item' => 'wiki', 'value' => $this->getLang('fld_wiki')))); 237 } else { 238 $htmlBlock .= $this->renderFormHidden('outputType','outputType','',$element['value']); 239 }; 240 break; 241 case RC_EXP_RUNBUTTONTEXT: 242 $buttonSubmitText = $element['value']; 243 $jqueryBlock .= $this->renderJQueryButton("runCommand","ui-icon-check"); 244 break; 245 case RC_EXP_CANCELBUTTONTEXT: 246 $buttonCancelText = $element['value']; 247 $jqueryBlock .= $this->renderJQueryButton("clearDiv","ui-icon-arrowreturnthick-1-w"); 248 break; 249 case RC_EXP_ARG: 250 switch($element['type']){ 251 case RC_ARG_HIDDEN: 252 $htmlBlock .= $this->renderFormHidden($element['name'],$element['label'],$element['flags'],$element['value']); 253 break; 254 case RC_ARG_TEXT: 255 $htmlBlock .= $this->renderFormTextBox($element['name'],$element['label'],$element['flags'],$element['value']); 256 break; 257 case RC_ARG_LIST: 258 $htmlBlock .= $this->renderFormListBox($element['name'],$element['label'],$element['flags'],$element['value']); 259 break; 260 case RC_ARG_AUTOCOMPLETE: 261 $htmlBlock .= $this->renderFormAutoComplete($element['name'],$element['label'],$element['flags'],$element['value']); 262 $jqueryBlock .= $this->renderJQueryAutoComplete($element['name'],$element['label'],$element['value']); 263 break; 264 case RC_ARG_SLIDER: 265 $htmlBlock .= $this->renderFormSlider($element['name'],$element['label'],$element['flags'],$element['value']['value']); 266 $jqueryBlock .= $this->renderJQuerySlider($element['name'],$element['value']['min'],$element['value']['max'],$element['value']['value'],$element['value']['step']); 267 break; 268 case RC_ARG_SPINNER: 269 $htmlBlock .= $this->renderFormSpinner($element['name'],$element['label'],$element['flags'],$element['value']); 270 $jqueryBlock .= $this->renderJQuerySpinner($element['name'],$element['value']['min'],$element['value']['max'],$element['value']['value']); 271 break; 272 case RC_ARG_DATE: 273 $htmlBlock .= $this->renderFormDate($element['name'],$element['label'],$element['flags'],$element['value']); 274 $jqueryBlock .= $this->renderJQueryDate($element['name'],$element['value']['format']); 275 break; 276 }; 277 break; 278 }; 279 }; 280 if ($binaryOutput) { 281 // TODO 282 $htmlBlock .= $this->renderFormButton('runCommand',$buttonSubmitText,'',"rcDoDownload"); 283 } else { 284 $htmlBlock .= $this->renderFormButton('runCommand',$buttonSubmitText,'',"rcDoLoad"); 285 }; 286 $this->debug("RENDER:buttonCancelText=".$buttonCancelText,3); 287 if ($buttonCancelText != RC_CONST_NO_CANCEL_BUTTON) { 288 $this->debug("RENDER:Render di buttonCancelText",3); 289 $htmlBlock .= $this->renderFormButton('clearDiv',$buttonCancelText,'',"rcDoClear"); 290 }; 291 $htmlBlock .= "</form>\n"; 292 //$jqueryBlock .= " });\n/*!]]>*/</script>\n"; 293 $jqueryBlock .= " });\n</script>\n"; 294 295 $renderer->doc .= $jqueryBlock. $htmlBlock; 296 $renderer->doc .= "<div id=\"rcResult".$currentCommand."\" class=\"rcResult\"> </div><br>"; 297 } 298 } 299 300 //---- Button -------------------------------------------------------------------------------------------------------- 301 /** Create a button field */ 302 function renderFormButton($name, $label, $flags, $action) { 303 // $flags is actually unused for this field 304 global $currentCommand; 305 $id = str_replace(" ","_",$name).$currentCommand; 306 307 $this->debug("RENDER:FORMBUTTON:".$name."=".$value,3); 308 return "<button type=\"button\" id=\"".$id."\" onclick=\"".$action."(".$currentCommand.")\">".$label."</button>\n"; 309 } 310 311 /** Define JQuery button config */ 312 function renderJQueryButton($name, $style) { 313 global $currentCommand; 314 $id = str_replace(" ","_",$name).$currentCommand; 315 316 $this->debug("RENDER:JQUERYBUTTON:".$name."=".$value,3); 317 return "jQuery( \"#".$id."\" ).button({ icons: { primary: \"".$style."\" }});\n"; 318 } 319 320 //---- Hidden -------------------------------------------------------------------------------------------------------- 321 /** Create an hidden field */ 322 function renderFormHidden($name, $label, $flags, $value) { 323 // $label and $flags are actually unused for this field 324 global $currentCommand; 325 $id = str_replace(" ","_",$name).$currentCommand; 326 327 $this->debug("RENDER:FORMHIDDEN:".$name."=".$value,3); 328 return "<input type='hidden' id='".$id."' value='".$value."'>\n"; 329 } 330 331 //---- TextBox ------------------------------------------------------------------------------------------------------- 332 /** Create a text field */ 333 function renderFormTextBox($name, $label, $flags, $value) { 334 global $currentCommand; 335 $id = str_replace(" ","_",$name).$currentCommand; 336 337 $this->debug("RENDER:FORMTEXTBOX:".$name."=".$value,3); 338 $result = "<label>".$label."</label><input type=\"text\" id=\"".$id."\" value=\"".$value."\" />"; 339 foreach ($flags as $flag) { 340 if ($flag == "newline") $result = "<p>".$result."</p>\n"; 341 else $result = $result."\n"; 342 } 343 return $result; 344 } 345 346 //---- List ---------------------------------------------------------------------------------------------------------- 347 /** Create a list field */ 348 function renderFormListBox($name, $label, $flags, $value) { 349 global $currentCommand; 350 $id = str_replace(" ","_",$name).$currentCommand; 351 352 $this->debug("RENDER:FORMLISTBOX:".$name."=".$value,3); 353 $result = "<label>".$label."</label><SELECT id='".$id."'>"; 354 foreach($value as $listObj){ 355 $result .= "<option value=\"".$listObj['item']."\">".$listObj['value']."</option>"; 356 }; 357 $result .= "</SELECT>\n"; 358 359 foreach ($flags as $flag) { 360 if ($flag == "newline") $result = "<p>".$result."</p>\n"; 361 else $result = $result."\n"; 362 } 363 return $result; 364 } 365 366 //---- Autocomplete -------------------------------------------------------------------------------------------------- 367 function renderFormAutoComplete($name, $label, $flags, $value) { 368 global $currentCommand; 369 $id = str_replace(" ","_",$name).$currentCommand; 370 371 $this->debug("RENDER:FORMAUTOCOMPLETE:".$name."=".$value,3); 372 $result = "<label>".$label."</label><input id=\"".$id."\" />"; 373 374 foreach ($flags as $flag) { 375 if ($flag == "newline") $result = "<p>".$result."</p>\n"; 376 else $result = $result."\n"; 377 } 378 return $result; 379 } 380 381 function renderJQueryAutoComplete($name, $label, $value) { 382 // $flags is actually unused for this field 383 global $currentCommand; 384 $id = str_replace(" ","_",$name).$currentCommand; 385 386 $result = "var availableTags".$currentCommand." = [ "; 387 foreach($value as $listObj) { 388 $result .= "\"".$listObj."\", "; 389 } 390 $result = substr($result, 0, -1)." ];\n"; 391 $result .= "jQuery(\"#".$id."\" ).autocomplete({ source: availableTags".$currentCommand." });\n"; 392 393 return $result; 394 } 395 396 //---- Slider -------------------------------------------------------------------------------------------------------- 397 function renderJQuerySlider($name, $min, $max, $value, $step) { 398 global $currentCommand; 399 $id = str_replace(" ","_",$name).$currentCommand; 400 401 $result = "jQuery( \"#".$id."_sli\" ).slider({ "; 402 $result .= "min: ".$min.", max: ".$max.", value: ".$value.", step: ".$step.", orientation: \"horizontal\", animate: true, "; 403 $result .= "slide: function( event, ui ) { jQuery( \"#".$id."\" ).val(ui.value ); "; 404 $result .= "} });\n"; 405 return $result; 406 } 407 408 function renderFormSlider($name, $label, $flags, $value) { 409 global $currentCommand; 410 $id = str_replace(" ","_",$name).$currentCommand; 411 412 $this->debug("RENDER:FORMSLIDER:".$name."=".$value,3); 413 $result = "<label>".$label."</label>"; 414 $result .= "<input type=\"text\" id=\"".$id."\" style=\"border: 0; color: #f6931f; font-weight: bold; align: right;\" value=\"".$value."\"/>"; 415 $result .= "<div id=\"".$id."_sli\" style=\"width: 260px; margin: 15px; \"></div>"; 416 417 foreach ($flags as $flag) { 418 if ($flag == "newline") $result = "<p>".$result."</p>\n"; 419 else $result = $result."\n"; 420 } 421 return $result; 422 } 423 424 //---- Spinner ------------------------------------------------------------------------------------------------------- 425 function renderJQuerySpinner($name, $min, $max, $value) { 426 global $currentCommand; 427 $id = str_replace(" ","_",$name).$currentCommand; 428 return "jQuery( \"#".$id."\" ).spinner({ min: \"".$min."\", max: \"".$max."\" }).val(\"".$value."\");\n"; 429 } 430 431 function renderFormSpinner($name, $label, $flags, $value) { 432 // $flags is actually unused for this field 433 global $currentCommand; 434 $id = str_replace(" ","_",$name).$currentCommand; 435 436 $this->debug("RENDER:FORMSPINNER:".$name."=".$value,3); 437 $result = "<label>".$label."</label>"; 438 $result .= "<input id=\"".$id."\" name=\"value\"/>"; 439 440 foreach ($flags as $flag) { 441 if ($flag == "newline") $result = "<p>".$result."</p>\n"; 442 else $result = $result."\n"; 443 } 444 return $result; 445 } 446 447 //---- Date ---------------------------------------------------------------------------------------------------------- 448 function renderJQueryDate($name, $dateFormat) { 449 global $currentCommand; 450 $id = str_replace(" ","_",$name).$currentCommand; 451 452 if ($dateFormat == null) { // If the user don't give the date format it use the default format. 453 $dateFormat = $this->getConf('rc_default_dateformat'); 454 }; 455 return "jQuery( \"#".$id."\" ).datepicker({ dateFormat: '".$dateFormat."' }); \n"; 456 457 } 458 459 function renderFormDate($name, $label, $flags, $value) { 460 global $currentCommand; 461 $id = str_replace(" ","_",$name).$currentCommand; 462 463 $this->debug("RENDER:FORMDATE:".$name."=".$value,3); 464 $result = "<label>".$label."</label>"; 465 $result .= "<input type=\"text\" id=\"".$id."\" />"; 466 467 foreach ($flags as $flag) { 468 if ($flag == "newline") $result = "<p>".$result."</p>"; 469 else $result = $result."\n"; 470 } 471 return $result; 472 } 473 474 475 476 /** 477 * Prepare the command for execution 478 */ 479 function prepareCommand($cmd) { 480 if ($this->getConf('safe_scripts') == 0) { 481 return $cmd; 482 } else { 483 $base_dir= DOKU_INC.$this->getConf('script_dir')."/"; 484 $result = ""; 485 $cmdRows = explode(';',$cmd); 486 //if ($debugLevel) { $this->debug($cmdRows); }; 487 foreach ($cmdRows as $cmdRow){ 488 //if ($debugLevel) { $this->debug("row=".$cmdRow); }; 489 $cmdRow = ltrim($cmdRow); 490 $result .= $base_dir.$cmdRow."; "; 491 }; 492 return substr($result, 0, -2); 493 } 494 } 495 496// /** 497// * Create javascript line that load value of an input box 498// */ 499// function renderScriptInput($id, $name) { 500// //if ($debugLevel) { $this->debug("=> renderScriptInput(".$id.", ".$name.")"); }; 501// return "var ".$name." = jQuery(\"form[id='rcform".$id."']>input[id='".$name."']\").val();\n"; 502// } 503 504// /** 505// * Create javascript line that load value of a combo box 506// */ 507// function renderScriptCombo($id, $name) { 508// if ($debugLevel) { $this->debug("=> renderScriptCombo(".$id.", ".$name.")"); }; 509// return "var ".$name." = jQuery(\"form[id='rcform".$id."']>select[id='".$name."']\").val();\n"; 510// } 511// 512// function renderWrongSyntax() { 513// return "<div class='error'>".$this->getLang('msg_wrongsyntax')."<br></div>"; 514// } 515 516} // End of class 517 518