2 rcs_id('$Id: WikiPlugin.php,v 1.48 2004-07-08 20:30:07 rurban Exp $');
6 function getDefaultArguments() {
7 return array('description' => $this->getDescription());
10 /** Does the plugin manage its own HTTP validators?
12 * This should be overwritten by (some) individual plugins.
14 * If the output of the plugin is static, depending only
15 * on the plugin arguments, query arguments and contents
16 * of the current page, this can (and should) return true.
18 * If the plugin can deduce a modification time, or equivalent
19 * sort of tag for it's content, then the plugin should
20 * call $request->appendValidators() with appropriate arguments,
21 * and should override this method to return true.
23 * When in doubt, the safe answer here is false.
24 * Unfortunately, returning false here will most likely make
25 * any page which invokes the plugin uncacheable (by HTTP proxies
28 function managesValidators() {
33 function run ($dbi, $argstr, &$request, $basepage) {
34 trigger_error("WikiPlugin::run: pure virtual function",
38 /** Get wiki-pages linked to by plugin invocation.
40 * A plugin may override this method to add pages to the
41 * link database for the invoking page.
43 * For example, the IncludePage plugin should override this so
44 * that the including page shows up in the backlinks list for the
47 * Not all plugins which generate links to wiki-pages need list
50 * Note also that currently the links are calculated at page save
51 * time, so only static page links (e.g. those dependent on the PI
52 * args, not the rest of the wikidb state or any request query args)
53 * will work correctly here.
55 * @param string $argstr The plugin argument string.
56 * @param string $basepage The pagename the plugin is invoked from.
57 * @return array List of pagenames linked to (or false).
59 function getWikiPageLinks ($argstr, $basepage) {
66 * This is used (by default) by getDefaultLinkArguments and
67 * getDefaultFormArguments to compute the default link/form
70 * If you want to gettextify the name (probably a good idea),
71 * override this method in your plugin class, like:
73 * function getName() { return _("MyPlugin"); }
76 * @return string plugin name/target.
79 return preg_replace('/^.*_/', '', get_class($this));
82 function getDescription() {
83 return $this->getName();
86 // plugins should override this with the commented-out code
87 function getVersion() {
89 //return preg_replace("/[Revision: $]/", '',
90 // "\$Revision: 1.48 $");
93 function getArgs($argstr, $request=false, $defaults = false) {
94 if ($defaults === false) {
95 $defaults = $this->getDefaultArguments();
97 //Fixme: on POST argstr is empty
98 list ($argstr_args, $argstr_defaults) = $this->parseArgStr($argstr);
100 if (!empty($defaults))
101 foreach ($defaults as $arg => $default_val) {
102 if (isset($argstr_args[$arg]))
103 $args[$arg] = $argstr_args[$arg];
104 elseif ( $request and ($argval = $request->getArg($arg)) !== false )
105 $args[$arg] = $argval;
106 elseif (isset($argstr_defaults[$arg]))
107 $args[$arg] = (string) $argstr_defaults[$arg];
109 $args[$arg] = $default_val;
111 // only expand given args
112 if ($request and !empty($argstr)) {
113 $args[$arg] = $this->expandArg($args[$arg], $request);
116 unset($argstr_args[$arg]);
117 unset($argstr_defaults[$arg]);
120 foreach (array_merge($argstr_args, $argstr_defaults) as $arg => $val) {
121 if ($request->getArg('pagename') == _("PhpWikiAdministration") and
125 trigger_error(sprintf(_("argument '%s' not declared by plugin"),
126 $arg), E_USER_NOTICE);
133 // Expand [arg] to $request->getArg("arg") unless preceded by ~
134 function expandArg($argval, $request) {
135 // return preg_replace('/\[(\w[\w\d]*)\]/e', '$request->getArg("$1")',
136 // Replace the arg unless it is preceded by a ~
137 $ret = preg_replace('/([^~]|^)\[(\w[\w\d]*)\]/e',
138 '"$1" . $request->getArg("$2")',
140 // Ditch the ~ so later versions can be expanded if desired
141 return preg_replace('/~(\[\w[\w\d]*\])/', '$1', $ret);
144 function parseArgStr($argstr) {
146 $op_p = '(?:\|\|)?=';
149 $qq_p = '" ( (?:[^"\\\\]|\\\\.)* ) "';
150 //"<--kludge for brain-dead syntax coloring
151 $q_p = "' ( (?:[^'\\\\]|\\\\.)* ) '";
152 $gt_p = "_\\( $opt_ws $qq_p $opt_ws \\)";
153 $argspec_p = "($arg_p) $opt_ws ($op_p) $opt_ws (?: $qq_p|$q_p|$gt_p|($word_p))";
158 return array($args,$defaults);
159 while (preg_match("/^$opt_ws $argspec_p $opt_ws/x", $argstr, $m)) {
160 @ list(,$arg,$op,$qq_val,$q_val,$gt_val,$word_val) = $m;
161 $argstr = substr($argstr, strlen($m[0]));
163 // Remove quotes from string values.
165 $val = stripslashes($qq_val);
167 $val = stripslashes($q_val);
169 $val = _(stripslashes($gt_val));
177 // NOTE: This does work for multiple args. Use the
178 // separator character defined in your webserver
179 // configuration, usually & or & (See
180 // http://www.htmlhelp.com/faq/cgifaq.4.html)
181 // e.g. <plugin RecentChanges days||=1 show_all||=0 show_minor||=0>
182 // url: RecentChanges?days=1&show_all=1&show_minor=0
183 assert($op == '||=');
184 $defaults[$arg] = $val;
189 $this->handle_plugin_args_cruft($argstr, $args);
192 return array($args, $defaults);
195 /* A plugin can override this function to define how any remaining text is handled */
196 function handle_plugin_args_cruft($argstr, $args) {
197 trigger_error(sprintf(_("trailing cruft in plugin args: '%s'"),
198 $argstr), E_USER_NOTICE);
201 function getDefaultLinkArguments() {
202 return array('targetpage' => $this->getName(),
203 'linktext' => $this->getName(),
204 'description' => $this->getDescription(),
205 'class' => 'wikiaction');
208 function makeLink($argstr, $request) {
209 $defaults = $this->getDefaultArguments();
210 $link_defaults = $this->getDefaultLinkArguments();
211 $defaults = array_merge($defaults, $link_defaults);
213 $args = $this->getArgs($argstr, $request, $defaults);
214 $plugin = $this->getName();
216 $query_args = array();
217 foreach ($args as $arg => $val) {
218 if (isset($link_defaults[$arg]))
220 if ($val != $defaults[$arg])
221 $query_args[$arg] = $val;
224 $link = Button($query_args, $args['linktext'], $args['targetpage']);
225 if (!empty($args['description']))
226 $link->addTooltip($args['description']);
231 function getDefaultFormArguments() {
232 return array('targetpage' => $this->getName(),
233 'buttontext' => $this->getName(),
234 'class' => 'wikiaction',
237 'description'=> $this->getDescription(),
241 function makeForm($argstr, $request) {
242 $form_defaults = $this->getDefaultFormArguments();
243 $defaults = array_merge($form_defaults,
244 array('start_debug' => false),
245 $this->getDefaultArguments());
247 $args = $this->getArgs($argstr, $request, $defaults);
248 $plugin = $this->getName();
249 $textinput = $args['textinput'];
250 assert(!empty($textinput) && isset($args['textinput']));
252 $form = HTML::form(array('action' => WikiURL($args['targetpage']),
253 'method' => $args['method'],
254 'class' => $args['class'],
255 'accept-charset' => $GLOBALS['charset']));
256 if (! USE_PATH_INFO ) {
257 $pagename = $request->get('pagename');
258 $form->pushContent(HTML::input(array('type' => 'hidden', 'name' => 'pagename',
259 'value' => $args['targetpage'])));
261 if ($args['targetpage'] != $this->getName()) {
262 $form->pushContent(HTML::input(array('type' => 'hidden',
264 'value' => $this->getName())));
266 $contents = HTML::div();
267 $contents->setAttr('class', $args['class']);
269 foreach ($args as $arg => $val) {
270 if (isset($form_defaults[$arg]))
272 if ($arg != $textinput && $val == $defaults[$arg])
275 $i = HTML::input(array('name' => $arg, 'value' => $val));
277 if ($arg == $textinput) {
278 //if ($inputs[$arg] == 'file')
279 // $attr['type'] = 'file';
281 $i->setAttr('type', 'text');
282 $i->setAttr('size', $args['formsize']);
283 if ($args['description'])
284 $i->addTooltip($args['description']);
287 $i->setAttr('type', 'hidden');
289 $contents->pushContent($i);
292 if ($i->getAttr('type') == 'file') {
293 $form->setAttr('enctype', 'multipart/form-data');
294 $form->setAttr('method', 'post');
295 $contents->pushContent(HTML::input(array('name' => 'MAX_FILE_SIZE',
296 'value' => MAX_UPLOAD_SIZE,
297 'type' => 'hidden')));
301 if (!empty($args['buttontext']))
302 $contents->pushContent(HTML::input(array('type' => 'submit',
304 'value' => $args['buttontext'])));
305 $form->pushContent($contents);
309 function makeBox($title,$body) {
310 if (!$title) $title = $this->_getName();
311 return HTML::div(array('class'=>'box'),
312 HTML::div(array('class'=>'box-title'),$title),
313 HTML::div(array('class'=>'box-data'),$body));
316 function error ($message) {
317 return HTML::div(array('class' => 'errors'),
318 HTML::strong(fmt("Plugin %s failed.", $this->getName())), ' ',
322 function disabled ($message='') {
323 $html[] = HTML::div(array('class' => 'title'),
324 fmt("Plugin %s disabled.", $this->getName()),
326 $html[] = HTML::pre($this->_pi);
327 return HTML::div(array('class' => 'disabled-plugin'), $html);
330 // TODO: Not really needed, since our plugins generally initialize their own
331 // PageList object, which accepts options['types'].
332 // Register custom PageList types for special plugins, like
333 // 'hi_content' for WikiAdminSearcheplace, 'renamed_pagename' for WikiAdminRename, ...
334 function addPageListColumn ($array) {
335 global $customPageListColumns;
336 if (empty($customPageListColumns)) $customPageListColumns = array();
337 foreach ($array as $column => $obj) {
338 $customPageListColumns[$column] = $obj;
342 // provide a sample usage text for automatic edit-toolbar insertion
343 function getUsage() {
344 $args = $this->getDefaultArguments();
345 $string = '<'.'?plugin '.$this->getName().' ';
347 foreach ($args as $key => $value) {
348 $string .= ($key."||=".(string)$value." ");
351 return $string . '?'.'>';
355 class WikiPluginLoader {
358 function expandPI($pi, &$request, &$markup, $basepage=false) {
359 if (!($ppi = $this->parsePi($pi)))
361 list($pi_name, $plugin, $plugin_args) = $ppi;
363 if (!is_object($plugin)) {
364 return new HtmlElement($pi_name == 'plugin-link' ? 'span' : 'p',
365 array('class' => 'plugin-error'),
366 $this->getErrorDetail());
370 // FIXME: change API for run() (no $dbi needed).
371 $dbi = $request->getDbh();
372 // pass the parsed CachedMarkup context in dbi to the plugin
373 // to be able to know about itself, or even to change the markup XmlTree (CreateToc)
374 $dbi->_markup = &$markup;
375 // FIXME: could do better here...
376 if (! $plugin->managesValidators()) {
377 // Output of plugin (potentially) depends on
378 // the state of the WikiDB (other than the current
381 // Lacking other information, we'll assume things
382 // changed last time the wikidb was touched.
384 // As an additional hack, mark the ETag weak, since,
385 // for all we know, the page might depend
386 // on things other than the WikiDB (e.g. PhpWeather,
389 $timestamp = $dbi->getTimestamp();
390 $request->appendValidators(array('dbi_timestamp' => $timestamp,
391 '%mtime' => (int)$timestamp,
394 return $plugin->run($dbi, $plugin_args, $request, $basepage);
396 return $plugin->makeLink($plugin_args, $request);
398 return $plugin->makeForm($plugin_args, $request);
402 function getWikiPageLinks($pi, $basepage) {
403 if (!($ppi = $this->parsePi($pi)))
405 list($pi_name, $plugin, $plugin_args) = $ppi;
406 if (!is_object($plugin))
408 if ($pi_name != 'plugin')
410 return $plugin->getWikiPageLinks($plugin_args, $basepage);
413 function parsePI($pi) {
414 if (!preg_match('/^\s*<\?(plugin(?:-form|-link)?)\s+(\w+)\s*(.*?)\s*\?>\s*$/s', $pi, $m))
415 return $this->_error(sprintf("Bad %s", 'PI'));
417 list(, $pi_name, $plugin_name, $plugin_args) = $m;
418 $plugin = $this->getPlugin($plugin_name, $pi);
420 return array($pi_name, $plugin, $plugin_args);
423 function getPlugin($plugin_name, $pi=false) {
424 global $ErrorManager;
426 // Note that there seems to be no way to trap parse errors
427 // from this include. (At least not via set_error_handler().)
428 $plugin_source = "lib/plugin/$plugin_name.php";
430 $ErrorManager->pushErrorHandler(new WikiMethodCb($this, '_plugin_error_filter'));
431 $plugin_class = "WikiPlugin_$plugin_name";
432 if (!class_exists($plugin_class)) {
433 // $include_failed = !@include_once("lib/plugin/$plugin_name.php");
434 $include_failed = !include_once("lib/plugin/$plugin_name.php");
435 $ErrorManager->popErrorHandler();
437 if (!class_exists($plugin_class)) {
439 return $this->_error(sprintf(_("Include of '%s' failed"),
441 return $this->_error(sprintf(_("%s: no such class"), $plugin_class));
444 $plugin = new $plugin_class;
445 if (!is_subclass_of($plugin, "WikiPlugin"))
446 return $this->_error(sprintf(_("%s: not a subclass of WikiPlugin"),
453 function _plugin_error_filter ($err) {
454 if (preg_match("/Failed opening '.*' for inclusion/", $err->errstr))
455 return true; // Ignore this error --- it's expected.
459 function getErrorDetail() {
460 return $this->_errors;
463 function _error($message) {
464 $this->_errors = $message;
469 // (c-file-style: "gnu")
474 // c-hanging-comment-ender-p: nil
475 // indent-tabs-mode: nil