]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/plugin/WikiFormRich.php
Updated (c) year
[SourceForge/phpwiki.git] / lib / plugin / WikiFormRich.php
1 <?php // -*-php-*-
2 rcs_id('$Id: WikiFormRich.php,v 1.18 2007-01-20 11:41:47 rurban Exp $');
3 /*
4  Copyright 2004,2006,2007 $ThePhpWikiProgrammingTeam
5
6  This file is part of PhpWiki.
7
8  PhpWiki is free software; you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation; either version 2 of the License, or
11  (at your option) any later version.
12
13  PhpWiki is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  GNU General Public License for more details.
17
18  You should have received a copy of the GNU General Public License
19  along with PhpWiki; if not, write to the Free Software
20  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 /**
24  * This is another replacement for MagicPhpWikiURL forms.
25  * Previously encoded with the "phpwiki:" syntax.
26  *
27  * Enhanced WikiForm to be more generic:
28  * - editbox[]          name=.. value=.. text=.. autocomplete=1
29  * - checkbox[]         name=.. value=0|1 checked text=..
30  * - radio[]            name=.. value=.. text=..
31  * - pulldown[]         name=.. value=.. selected=.. text=.. autocomplete=1
32  * - combobox[]         name=.. value=.. text=.. method=.. args=.. 
33  * - hidden[]           name=.. value=..
34  * - submit[]
35  * - action, submit buttontext, optional cancel button (bool)
36  * - method=get or post, Default: post.
37  
38  * Valid arguments for pulldown and editbox: autocomplete=1, Default: 0
39  * If autocomplete=1, additional arguments method and args may be used. 
40  * If no method is given, value will be used to fill in the valid values.
41  * method="xmlrpc:server:name" or "url:http://server/wiki/method" or "array:jsvariable"
42  * or "plugin:pluginname"
43  * args are optional arguments, space seperated, for the method.
44  * A combobox is a pulldown with autocomplete=1.
45  *
46  * @Author: Reini Urban
47
48  * Values which are constants are evaluated.
49  * The cancel button must be supported by the action. 
50  *   (just some wikiadmin actions so far)
51  * improve layout by: nobr=1
52  * some allow values as list from from <!plugin-list !>
53
54  Samples:
55    <?plugin WikiFormRich action=dumpserial method=get 
56             checkbox[] name=include value="all" 
57             editbox[] name=directory value=DEFAULT_DUMP_DIR
58             editbox[] name=pages value=*
59             editbox[] name=exclude value="" ?>
60    <?plugin WikiFormRich action=dumphtml method=get 
61             editbox[] name=directory value=HTML_DUMP_DIR
62             editbox[] name=pages value="*"
63             editbox[] name=exclude value="" ?>
64    <?plugin WikiFormRich action=loadfile method=get 
65             editbox[]  name=source value=DEFAULT_WIKI_PGSRC
66             checkbox[] name=overwrite value=1
67             editbox[]  name=exclude value="" ?>
68   <?plugin WikiFormRich action=TitleSearch method=get class=wikiadmin nobr=1
69            editbox[] name=s text=""
70            submit[]
71            checkbox[] name=case_exact
72            checkbox[] name=regex ?>
73   <?plugin WikiFormRich action=FullTextSearch method=get class=wikiadmin nobr=1
74            editbox[] name=s text=""
75            submit[]
76            checkbox[] name=case_exact
77            checkbox[] name=regex ?>
78   <?plugin WikiFormRich action=FuzzyPages method=get class=wikiadmin nobr=1
79            editbox[] name=s text=""
80            submit[]
81            checkbox[] name=case_exact ?>
82   <?plugin WikiFormRich action=AppendText buttontext="AddPlugin"
83            radio[] name=s value=<!plugin-list BackLinks page=WikiPlugin limit=10 !>
84            ?>
85   <?plugin WikiFormRich action=AppendText buttontext="AddPlugin"
86            pulldown[] name=s text="Plugins: " value=<!plugin-list BackLinks page=WikiPlugin !>
87            ?>
88   <?plugin WikiFormRich action=AppendText buttontext="AddCategory"
89            pulldown[] name=s text="Categories: " value=<!plugin-list TitleSearch s=Category !>
90            ?>
91   <?plugin WikiFormRich action=SemanticSearch buttontext="AddRelation"
92            combobox[] name=relation text="Relation: " method=listRelations
93            ?>
94   <?plugin WikiFormRich action=AppendText buttontext="InsertTemplate"
95            combobox[] name=s text="Template: " method=titleSearch args="Template/"
96            ?>
97 */
98
99 class WikiPlugin_WikiFormRich
100 extends WikiPlugin
101 {
102     function getName () {
103         return "WikiFormRich";
104     }
105     function getDescription () {
106         return _("Provide generic WikiForm input buttons");
107     }
108     function getVersion() {
109         return preg_replace("/[Revision: $]/", '',
110                             "\$Revision: 1.18 $");
111     }
112     function getDefaultArguments() {
113         return array('action' => false,     // required argument
114                      'method' => 'post',    // or get
115                      'class'  => 'wikiaction',
116                      'buttontext' => false, // for the submit button. default: action
117                      'cancel' => false,     // boolean if the action supports cancel also
118                      'nobr' => false,       // "no break": linebreaks or not
119                      );
120     }
121
122     /* TODO: support better block alignment: <br>, tables, indent
123      */
124     function handle_plugin_args_cruft($argstr, $args) {
125         $allowed = array("editbox", "hidden", "checkbox", "radiobutton"/*deprecated*/,
126                          "radio", "pulldown", "submit", "reset", "combobox");
127         // no editbox[] = array(...) allowed (space)
128         $arg_array = preg_split("/\n/", $argstr);
129         // for security we should check this better
130         $arg = '';
131         for ($i = 0; $i < count($arg_array); $i++) {
132             //TODO: we require an name=value pair here, but submit may go without also.
133             if (preg_match("/^\s*(".join("|",$allowed).")\[\](.*)$/", $arg_array[$i], $m)) {
134                 $name = $m[1]; // one of the allowed input types
135                 $this->inputbox[][$name] = array(); $j = count($this->inputbox) - 1;
136                 $curargs = trim($m[2]);
137                 // must match name=NAME and also value=<!plugin-list name !>
138                 while (preg_match("/^(\w+?)=((?:\".*?\")|(?:\w+)|(?:\"?<!plugin-list.+?!>\"?))\s*/", 
139                                   $curargs, $m)) {
140                     $attr = $m[1]; $value = $m[2];
141                     $curargs = substr($curargs, strlen($m[0]));
142                     if (preg_match("/^\"(.*)\"$/", $value, $m))
143                         $value = $m[1];
144                     if (in_array($name, array("pulldown","checkbox","radio","radiobutton","combobox"))
145                             and preg_match('/^<!plugin-list.+!>$/', $value, $m))
146                     // like pulldown[] name=test value=<!plugin-list BackLinks page=HomePage!>
147                     {
148                         $loader = new WikiPluginLoader();
149                         $markup = null;
150                         $basepage = null;
151                         $plugin_str = preg_replace(array("/^<!/","/!>$/"),array("<?","?>"), $value);
152                         // will return a pagelist object! pulldown,checkbox,radiobutton
153                         $value = $loader->expandPI($plugin_str, $GLOBALS['request'], $markup, $basepage);
154                         if (isa($value, 'PageList')) 
155                             $value = $value->pageNames(); // apply limit
156                         elseif (!is_array($value))
157                             trigger_error(sprintf("Invalid argument %s ignored", htmlentities($arg_array[$i])), 
158                                           E_USER_WARNING);
159                     }
160                     elseif (defined($value))
161                         $value = constant($value);
162                     $this->inputbox[$j][$name][$attr] = $value;
163                 }
164                 //trigger_error("not yet finished");
165                 //eval('$this->inputbox[]["'.$m[1].'"]='.$m[2].';');
166             } else {
167                 trigger_error(sprintf("Invalid argument %s ignored", htmlentities($arg_array[$i])), 
168                               E_USER_WARNING);
169             }
170         }
171         return;
172     }
173
174     function run($dbi, $argstr, &$request, $basepage) {
175         extract($this->getArgs($argstr, $request));
176         if (empty($action)) {
177             return $this->error(fmt("A required argument '%s' is missing.", "action"));
178         }
179         $form = HTML::form(array('action' => $request->getPostURL(),
180                                  'method' => strtolower($method),
181                                  'class'  => 'wikiaction',
182                                  'accept-charset' => $GLOBALS['charset']),
183                            HiddenInputs(array('action' => $action)));
184         $nbsp = HTML::Raw('&nbsp;');
185         $already_submit = 0;
186         foreach ($this->inputbox as $inputbox) {
187             foreach ($inputbox as $inputtype => $input) {
188               if ($inputtype == 'radiobutton') $inputtype = 'radio'; // convert from older versions
189               $input['type'] = $inputtype;
190               $text = '';
191               if ($inputtype != 'submit') {
192                   if (empty($input['name']))
193                       return $this->error(fmt("A required argument '%s' is missing.",
194                                             $inputtype."[][name]"));
195                   if (!isset($input['text'])) $input['text'] = gettext($input['name']);
196                   $text = $input['text'];
197                   unset($input['text']);
198               }
199               switch($inputtype) {
200               case 'checkbox':
201               case 'radio':
202                 if (empty($input['value'])) $input['value'] = 1;
203                 if (is_array($input['value'])) {
204                     $div = HTML::div(array('class' => $class));
205                     $values = $input['value'];
206                     $name = $input['name'];
207                     $input['name'] = $inputtype == 'checkbox' ? $name."[]" : $name;
208                     foreach ($values as $val) {
209                         $input['value'] = $val;
210                         if ($request->getArg($name)) {
211                             if ($request->getArg($name) == $val)
212                                 $input['checked'] = 'checked';
213                             else 
214                                 unset($input['checked']);
215                         }
216                         $div->pushContent(HTML::input($input), $nbsp, $val, $nbsp, "\n");
217                         if (!$nobr)
218                             $div->pushContent(HTML::br());
219                     }
220                     $form->pushContent($div);
221                 } else {
222                     if (empty($input['checked'])) {
223                         if ($request->getArg($input['name']))
224                             $input['checked'] = 'checked';
225                     } else {
226                         $input['checked'] = 'checked';
227                     }
228                     if ($nobr)
229                         $form->pushContent(HTML::input($input), $nbsp, $text, $nbsp);
230                     else
231                         $form->pushContent(HTML::div(array('class' => $class), HTML::input($input), $nbsp, $text));
232                 }
233                 break;
234               case 'editbox':
235                   $input['type'] = 'text';
236                   if (empty($input['value']) and ($s = $request->getArg($input['name'])))
237                       $input['value'] = $s;
238                   if (!empty($input['autocomplete']))
239                       $this->_doautocomplete($form, $inputtype, $input, $input['value']);
240                   if ($nobr)
241                       $form->pushContent(HTML::input($input), $nbsp, $text, $nbsp);
242                   else
243                       $form->pushContent(HTML::div(array('class' => $class), HTML::input($input), $nbsp, $text));
244                   break;
245               case 'combobox':
246                   $input['autocomplete'] = 1;
247               case 'pulldown':
248                   $values = @$input['value'];
249                   unset($input['value']);
250                   unset($input['type']);
251                   if (is_string($values)) $values = explode(",", $values);
252                   if (!empty($input['autocomplete']))
253                       $this->_doautocomplete($form, $inputtype, $input, $values);
254                   $select = HTML::select($input);
255                   if (empty($values) and ($s = $request->getArg($input['name']))) {
256                       $select->pushContent(HTML::option(array('value'=> $s), $s));
257                   } elseif (is_array($values)) {
258                       $name = $input['name'];
259                       unset($input['name']);
260                       foreach ($values as $val) {
261                           $input = array('value' => $val);
262                           if ($request->getArg($name)) {
263                               if ($request->getArg($name) == $val)
264                                   $input['selected'] = 'selected';
265                               else
266                                   unset($input['selected']);
267                           }
268                           //TODO: filter uneeded attributes
269                           $select->pushContent(HTML::option($input, $val));
270                       }
271                   } else { // force empty option
272                       $select->pushContent(HTML::option(array(), ''));
273                   }
274                   $form->pushContent($text, $nbsp, $select);
275                   break;
276               case 'reset':
277               case 'hidden':
278                   $form->pushContent(HTML::input($input));
279                   break;
280               // change the order of inputs, by explicitly placing a submit button here.
281               case 'submit':
282                   //$input['type'] = 'submit';
283                   if (empty($input['value'])) $input['value'] = $buttontext ? $buttontext : $action;
284                   unset($input['text']);
285                   if (empty($input['class'])) $input['class'] = $class;
286                   if ($nobr)
287                       $form->pushContent(HTML::input($input), $nbsp, $text, $nbsp);
288                   else
289                       $form->pushContent(HTML::div(array('class' => $class), HTML::input($input), $text));
290                   // unset the default submit button
291                   $already_submit = 1;
292                   break;
293               }
294             }
295         }
296         if ($request->getArg('start_debug'))
297             $form->pushContent(HTML::input
298                                (array('name' => 'start_debug',
299                                       'value' =>  $request->getArg('start_debug'),
300                                       'type'  => 'hidden')));
301         if (!USE_PATH_INFO)
302             $form->pushContent(HiddenInputs(array('pagename' => $basepage)));
303         if (!$already_submit) {
304             if (empty($buttontext)) $buttontext = $action;
305             $submit = Button('submit:', $buttontext, $class);
306             if ($cancel) {
307                 $form->pushContent(HTML::span
308                                    (array('class' => $class),
309                                     $submit, 
310                                     Button('submit:cancel', _("Cancel"), $class)));
311             } else {
312                 $form->pushContent(HTML::span(array('class' => $class),
313                                               $submit));
314             }
315         }
316         return $form;
317     }
318
319     function _doautocomplete(&$form, $inputtype, &$input, &$values) {
320         global $request;
321         $input['class'] = "dropdown";
322         $input['acdropdown'] = "true"; 
323         //$input['autocomplete'] = "OFF";
324         $input['autocomplete_complete'] = "true";
325         // only match begin: autocomplete_matchbegin, or
326         $input['autocomplete_matchsubstring'] = "true";
327         if (empty($values)) {
328             if ($input['method']) {
329                 if (empty($input['args'])) {
330                     if (preg_match("/^(.*?) (.*)$/",$input['method'],$m)) {
331                         $input['method'] = $m[1];
332                         $input['args'] = $m[2];
333                     } else 
334                         $input['args'] = null;
335                 }
336                 static $tmpArray = 'tmpArray00';
337                 // deferred remote xmlrpc call
338                 if (string_starts_with($input['method'], "dynxmlrpc:")) {
339                     // how is server + method + args encoding parsed by acdropdown? 
340                     $input['autocomplete_list'] = substr($input['method'],3);
341                     if ($input['args']) 
342                         $input['autocomplete_list'] .= (" ".$input['args']);
343                 // static xmlrpc call, local only
344                 } elseif (string_starts_with($input['method'], "xmlrpc:")) {
345                     include_once("lib/XmlRpcClient.php");
346                     $values = wiki_xmlrpc_post(substr($input['method'],7), $input['args']);
347                 } elseif (string_starts_with($input['method'], "url:")) {
348                     include_once("lib/HttpClient.php");
349                     $html = HttpClient::quickGet(substr($input['method'],4));
350                     //TODO: how to parse the HTML result into a list?
351                 } elseif (string_starts_with($input['method'], "dynurl:")) {
352                     $input['autocomplete_list'] = substr($input['method'],3);
353                 } elseif (string_starts_with($input['method'], "plugin:")) {
354                     $dbi = $request->getDbh();
355                     $pluginName = substr($input['method'],7);
356                     $basepage = '';
357                     require_once("lib/WikiPlugin.php");
358                     $w = new WikiPluginLoader;
359                     $p = $w->getPlugin($pluginName, false); // second arg?
360                     if (!is_object($p))
361                         trigger_error("invalid input['method'] ".$input['method'], E_USER_WARNING);
362                     $pagelist = $p->run($dbi, @$input['args'], $request, $basepage);
363                     $values = array();
364                     if (is_object($pagelist) and isa($pagelist, 'PageList')) {
365                         foreach ($pagelist->_pages as $page) {
366                             if (is_object($page))
367                                 $values[] = $page->getName();
368                             else
369                                 $values[] = (string)$page;
370                         }
371                     }
372                 } elseif (string_starts_with($input['method'], "array:")) {
373                     // some predefined values (e.g. in a template or themeinfo.php)
374                     $input['autocomplete_list'] = $input['method'];
375                 } else {
376                     trigger_error("invalid input['method'] ".$input['method'], E_USER_WARNING);
377                 }
378                 if (empty($input['autocomplete_list'])) 
379                 {
380                     $tmpArray++;
381                     $input['autocomplete_list']="array:".$tmpArray;
382                     $svalues = empty($values) ? "" : join("','", $values);
383                     $form->pushContent(JavaScript("var $tmpArray = new Array('".$svalues."')"));
384                 }
385                 if (count($values) == 1)
386                     $input['value'] = $values[0];
387                 else
388                     $input['value'] = "";    
389                 unset($input['method']);
390                 unset($input['args']);
391                 //unset($input['autocomplete']);
392             }
393             elseif ($s = $request->getArg($input['name']))
394                 $input['value'] = $s;
395         }
396         return true;
397     }
398 };
399
400 // $Log: not supported by cvs2svn $
401 // Revision 1.17  2007/01/03 21:24:33  rurban
402 // remove debugging cruft.
403 //
404 // Revision 1.16  2007/01/02 13:23:38  rurban
405 // support more WikiFormRich method arguments: url, dynurl, xmlrpc, dynxmlrpc, the autocomplete option for pulldown[] and editbox[] and the new combobox[] widget. fix name=value parsing. fix limit with plugin-list invocations
406 //
407 // Revision 1.15  2004/11/26 18:25:33  rurban
408 // pulldown[] values="val1,val2,val3,..." simple comma seperated values
409 //
410 // Revision 1.14  2004/11/25 17:20:52  rurban
411 // and again a couple of more native db args: backlinks
412 //
413 // Revision 1.13  2004/11/25 12:04:17  rurban
414 // support extra submit[] and reste[] buttons to place it before. renamed radiobutton to radio
415 //
416 // Revision 1.12  2004/11/24 15:21:19  rurban
417 // docs
418 //
419 // Revision 1.11  2004/11/24 15:19:57  rurban
420 // allow whitespace in quoted text args
421 //
422 // Revision 1.10  2004/11/24 15:07:49  rurban
423 // added pulldown support, fixed plugin-list whitespace splitting
424 //
425 // Revision 1.9  2004/11/24 13:55:42  rurban
426 // omit unneccessary pagename arg
427 //
428 // Revision 1.8  2004/11/24 10:58:50  rurban
429 // just docs
430 //
431 // Revision 1.7  2004/11/24 10:40:04  rurban
432 // better nobr, allow empty text=""
433 //
434 // Revision 1.5  2004/11/24 10:14:36  rurban
435 // fill-in request args as with plugin-form
436 //
437 // Revision 1.4  2004/11/23 15:17:20  rurban
438 // better support for case_exact search (not caseexact for consistency),
439 // plugin args simplification:
440 //   handle and explode exclude and pages argument in WikiPlugin::getArgs
441 //     and exclude in advance (at the sql level if possible)
442 //   handle sortby and limit from request override in WikiPlugin::getArgs
443 // ListSubpages: renamed pages to maxpages
444 //
445 // Revision 1.3  2004/07/09 13:05:34  rurban
446 // just aesthetics
447 //
448 // Revision 1.2  2004/07/09 10:25:52  rurban
449 // fix the args parser
450 //
451 // Revision 1.1  2004/07/02 11:03:53  rurban
452 // renamed WikiFormMore to WikiFormRich: better syntax, no eval (safer)
453 //
454 // Revision 1.3  2004/07/01 13:59:25  rurban
455 // enhanced to allow arbitrary order of args and stricter eval checking
456 //
457 // Revision 1.2  2004/07/01 13:14:01  rurban
458 // desc only
459 //
460 // Revision 1.1  2004/07/01 13:11:53  rurban
461 // more generic forms
462 //
463
464 // For emacs users
465 // Local Variables:
466 // mode: php
467 // tab-width: 8
468 // c-basic-offset: 4
469 // c-hanging-comment-ender-p: nil
470 // indent-tabs-mode: nil
471 // End:
472 ?>