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\">&nbsp;</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