7 function getDefaultArguments()
9 return array('description' => $this->getDescription());
12 /** Does the plugin manage its own HTTP validators?
14 * This should be overwritten by (some) individual plugins.
16 * If the output of the plugin is static, depending only
17 * on the plugin arguments, query arguments and contents
18 * of the current page, this can (and should) return true.
20 * If the plugin can deduce a modification time, or equivalent
21 * sort of tag for it's content, then the plugin should
22 * call $request->appendValidators() with appropriate arguments,
23 * and should override this method to return true.
25 * When in doubt, the safe answer here is false.
26 * Unfortunately, returning false here will most likely make
27 * any page which invokes the plugin uncacheable (by HTTP proxies
30 function managesValidators()
36 function run($dbi, $argstr, &$request, $basepage)
38 trigger_error("WikiPlugin::run: pure virtual function", E_USER_ERROR);
42 /** Get wiki-pages linked to by plugin invocation.
44 * A plugin may override this method to add pages to the
45 * link database for the invoking page.
47 * For example, the IncludePage plugin should override this so
48 * that the including page shows up in the backlinks list for the
51 * Not all plugins which generate links to wiki-pages need list
54 * Note also that currently the links are calculated at page save
55 * time, so only static page links (e.g. those dependent on the PI
56 * args, not the rest of the wikidb state or any request query args)
57 * will work correctly here.
59 * @param string $argstr The plugin argument string.
60 * @param string $basepage The pagename the plugin is invoked from.
61 * @return array List of pagenames linked to (or false).
63 function getWikiPageLinks($argstr, $basepage)
71 * This is used (by default) by getDefaultLinkArguments and
72 * getDefaultFormArguments to compute the default link/form
75 * If you override this method in your plugin class,
76 * you MUST NOT translate the name.
78 * function getName() { return "MyPlugin"; }
81 * @return string plugin name/target.
85 return preg_replace('/^WikiPlugin_/', '', get_class($this));
89 * Get description of plugin.
91 * This method should be overriden in your plugin class, like:
93 * function getDescription() { return _("MyPlugin does this..."); }
96 * @return string plugin description
99 function getDescription()
101 return _('This plugin has no description.');
105 * @param string $argstr
106 * @param WikiRequest $request
107 * @param array $defaults
110 function getArgs($argstr, $request = false, $defaults = array())
112 if (empty($defaults)) {
113 $defaults = $this->getDefaultArguments();
115 //Fixme: on POST argstr is empty
116 list ($argstr_args, $argstr_defaults) = $this->parseArgStr($argstr);
118 if (!empty($defaults))
119 foreach ($defaults as $arg => $default_val) {
120 if (isset($argstr_args[$arg])) {
121 $args[$arg] = $argstr_args[$arg];
122 } elseif ($request and ($argval = $request->getArg($arg)) !== false) {
123 $args[$arg] = $argval;
124 } elseif (isset($argstr_defaults[$arg])) {
125 $args[$arg] = (string)$argstr_defaults[$arg];
127 $args[$arg] = $default_val;
130 if ($request and is_string($args[$arg]) and strstr($args[$arg], "[")) {
131 $args[$arg] = $this->expandArg($args[$arg], $request);
134 unset($argstr_args[$arg]);
135 unset($argstr_defaults[$arg]);
138 foreach (array_merge($argstr_args, $argstr_defaults) as $arg => $val) {
139 if ($this->allow_undeclared_arg($arg, $val)) {
144 // Add special handling of pages and exclude args to accept <! plugin-list !>
145 // and split explodePageList($args['exclude']) => array()
146 // TODO : handle p[] pagehash
147 foreach (array('pages', 'exclude') as $key) {
148 if (!empty($args[$key]) and array_key_exists($key, $defaults)) {
149 $args[$key] = is_string($args[$key])
150 ? explodePageList($args[$key])
151 : $args[$key]; // <! plugin-list !>
159 // Expand [arg] to $request->getArg("arg") unless preceded by ~
160 function expandArg($argval, &$request)
162 // Replace the arg unless it is preceded by a ~
163 $ret = preg_replace_callback('/([^~]|^)\[(\w[\w\d]*)\]/',
166 return "$m[1]" . $request->getArg("$m[2]");
169 // Ditch the ~ so later versions can be expanded if desired
170 return preg_replace('/~(\[\w[\w\d]*\])/', '$1', $ret);
173 function parseArgStr($argstr)
178 return array($args, $defaults);
181 $op_p = '(?:\|\|)?=';
184 $qq_p = '" ( (?:[^"\\\\]|\\\\.)* ) "';
185 //"<--kludge for brain-dead syntax coloring
186 $q_p = "' ( (?:[^'\\\\]|\\\\.)* ) '";
187 $gt_p = "_\\( $opt_ws $qq_p $opt_ws \\)";
188 $argspec_p = "($arg_p) $opt_ws ($op_p) $opt_ws (?: $qq_p|$q_p|$gt_p|($word_p))";
190 // handle plugin-list arguments separately
191 $plugin_p = '<!plugin-list\s+\w+.*?!>';
192 while (preg_match("/^($arg_p) $opt_ws ($op_p) $opt_ws ($plugin_p) $opt_ws/x", $argstr, $m)) {
193 @ list(, $arg, $op, $plugin_val) = $m;
194 $argstr = substr($argstr, strlen($m[0]));
195 $loader = new WikiPluginLoader();
198 $plugin_val = preg_replace(array("/^<!/", "/!>$/"), array("<?", "?>"), $plugin_val);
199 $val = $loader->expandPI($plugin_val, $GLOBALS['request'], $markup, $basepage);
201 $args[$arg] = $val; // comma delimited pagenames or array()?
203 assert($op == '||=');
204 $defaults[$arg] = $val;
207 while (preg_match("/^$opt_ws $argspec_p $opt_ws/x", $argstr, $m)) {
216 list(, $arg, $op, $qq_val, $q_val, $gt_val, $word_val) = $m;
217 } elseif ($count == 6) {
218 list(, $arg, $op, $qq_val, $q_val, $gt_val) = $m;
219 } elseif ($count == 5) {
220 list(, $arg, $op, $qq_val, $q_val) = $m;
221 } elseif ($count == 4) {
222 list(, $arg, $op, $qq_val) = $m;
224 $argstr = substr($argstr, strlen($m[0]));
225 // Remove quotes from string values.
227 $val = stripslashes($qq_val);
228 elseif ($count > 4 and $q_val)
229 $val = stripslashes($q_val); elseif ($count >= 6 and $gt_val)
230 $val = _(stripslashes($gt_val)); elseif ($count >= 7)
231 $val = $word_val; else
237 // NOTE: This does work for multiple args. Use the
238 // separator character defined in your webserver
239 // configuration, usually & or & (See
240 // http://www.htmlhelp.com/faq/cgifaq.4.html)
241 // e.g. <plugin RecentChanges days||=1 show_all||=0 show_minor||=0>
242 // url: RecentChanges?days=1&show_all=1&show_minor=0
243 assert($op == '||=');
244 $defaults[$arg] = $val;
249 $this->handle_plugin_args_cruft($argstr, $args);
252 return array($args, $defaults);
255 /* A plugin can override this function to define how any remaining text is handled */
256 function handle_plugin_args_cruft($argstr, $args)
258 trigger_error(sprintf(_("trailing cruft in plugin args: ā%sā"),
259 $argstr), E_USER_NOTICE);
262 /* A plugin can override this to allow undeclared arguments.
263 Or to silence the warning.
265 function allow_undeclared_arg($name, $value)
267 trigger_error(sprintf(_("Argument ā%sā not declared by plugin."),
268 $name), E_USER_NOTICE);
272 /* handle plugin-list argument: use run(). */
273 function makeList($plugin_args, $request, $basepage)
275 $dbi = $request->getDbh();
276 $pagelist = $this->run($dbi, $plugin_args, $request, $basepage);
278 if (is_object($pagelist) and isa($pagelist, 'PageList'))
279 return $pagelist->pageNames();
280 elseif (is_array($pagelist))
286 function getDefaultLinkArguments()
288 return array('targetpage' => $this->getName(),
289 'linktext' => $this->getName(),
290 'description' => $this->getDescription(),
291 'class' => 'wikiaction');
294 function getDefaultFormArguments()
296 return array('targetpage' => $this->getName(),
297 'buttontext' => _($this->getName()),
298 'class' => 'wikiaction',
301 'description' => $this->getDescription(),
305 function makeForm($argstr, $request)
307 $form_defaults = $this->getDefaultFormArguments();
308 $defaults = array_merge($form_defaults,
309 array('start_debug' => $request->getArg('start_debug')),
310 $this->getDefaultArguments());
312 $args = $this->getArgs($argstr, $request, $defaults);
313 $textinput = $args['textinput'];
314 assert(!empty($textinput) && isset($args['textinput']));
316 $form = HTML::form(array('action' => WikiURL($args['targetpage']),
317 'method' => $args['method'],
318 'class' => $args['class'],
319 'accept-charset' => 'UTF-8'));
320 if (!USE_PATH_INFO) {
321 $pagename = $request->get('pagename');
322 $form->pushContent(HTML::input(array('type' => 'hidden',
323 'name' => 'pagename',
324 'value' => $args['targetpage'])));
326 if ($args['targetpage'] != $this->getName()) {
327 $form->pushContent(HTML::input(array('type' => 'hidden',
329 'value' => $this->getName())));
331 $contents = HTML::div();
332 $contents->setAttr('class', $args['class']);
334 foreach ($args as $arg => $val) {
335 if (isset($form_defaults[$arg]))
337 if ($arg != $textinput && $val == $defaults[$arg])
340 $i = HTML::input(array('name' => $arg, 'value' => $val));
342 if ($arg == $textinput) {
343 //if ($inputs[$arg] == 'file')
344 // $attr['type'] = 'file';
346 $i->setAttr('type', 'text');
347 $i->setAttr('size', $args['formsize']);
348 if ($args['description'])
349 $i->addTooltip($args['description']);
351 $i->setAttr('type', 'hidden');
353 $contents->pushContent($i);
356 if ($i->getAttr('type') == 'file') {
357 $form->setAttr('enctype', 'multipart/form-data');
358 $form->setAttr('method', 'post');
359 $contents->pushContent(HTML::input(array('name' => 'MAX_FILE_SIZE',
360 'value' => MAX_UPLOAD_SIZE,
361 'type' => 'hidden')));
365 if (!empty($args['buttontext']))
366 $contents->pushContent(HTML::input(array('type' => 'submit',
368 'value' => $args['buttontext'])));
369 $form->pushContent($contents);
373 // box is used to display a fixed-width, narrow version with common header
374 function box($args = false, $request = false, $basepage = false)
376 if (!$request) $request =& $GLOBALS['request'];
377 $dbi = $request->getDbh();
378 return $this->makeBox('', $this->run($dbi, $args, $request, $basepage));
381 function makeBox($title, $body)
383 if (!$title) $title = $this->getName();
384 return HTML::div(array('class' => 'box'),
385 HTML::div(array('class' => 'box-title'), $title),
386 HTML::div(array('class' => 'box-data'), $body));
389 function error($message)
391 return HTML::span(array('class' => 'error'),
392 HTML::strong(fmt("Plugin %s failed.", $this->getName())), ' ',
396 function disabled($message = '')
398 $html[] = HTML::div(array('class' => 'title'),
399 fmt("Plugin %s disabled.", $this->getName()),
401 $html[] = HTML::pre($this->_pi);
402 return HTML::div(array('class' => 'disabled-plugin'), $html);
405 // TODO: Not really needed, since our plugins generally initialize their own
406 // PageList object, which accepts options['types'].
407 // Register custom PageList types for special plugins, like
408 // 'hi_content' for WikiAdminSearcheplace, 'renamed_pagename' for WikiAdminRename, ...
409 function addPageListColumn($array)
411 global $customPageListColumns;
412 if (empty($customPageListColumns)) $customPageListColumns = array();
413 foreach ($array as $column => $obj) {
414 $customPageListColumns[$column] = $obj;
418 // provide a sample usage text for automatic edit-toolbar insertion
421 $args = $this->getDefaultArguments();
422 $string = '<<' . $this->getName() . ' ';
424 foreach ($args as $key => $value) {
425 $string .= ($key . "||=" . (string)$value . " ");
428 return $string . '>>';
431 function getArgumentsDescription()
434 foreach ($this->getDefaultArguments() as $arg => $default) {
435 // Work around UserPreferences plugin to avoid error
436 if ((is_array($default))) {
437 $default = '(array)';
438 // This is a bit flawed with UserPreferences object
439 //$default = sprintf("array('%s')",
440 // implode("', '", array_keys($default)));
442 if (stristr($default, ' '))
443 $default = "'$default'";
444 $arguments->pushcontent("$arg=$default", HTML::br());
451 class WikiPluginLoader
455 function expandPI($pi, &$request, &$markup, $basepage = false)
457 if (!($ppi = $this->parsePi($pi)))
459 list($pi_name, $plugin, $plugin_args) = $ppi;
461 if (!is_object($plugin)) {
462 return new HtmlElement('div',
463 array('class' => 'error'),
464 $this->getErrorDetail());
468 // FIXME: change API for run() (no $dbi needed).
469 $dbi = $request->getDbh();
470 // pass the parsed CachedMarkup context in dbi to the plugin
471 // to be able to know about itself, or even to change the markup XmlTree (CreateToc)
472 $dbi->_markup = &$markup;
473 // FIXME: could do better here...
474 if (!$plugin->managesValidators()) {
475 // Output of plugin (potentially) depends on
476 // the state of the WikiDB (other than the current
479 // Lacking other information, we'll assume things
480 // changed last time the wikidb was touched.
482 // As an additional hack, mark the ETag weak, since,
483 // for all we know, the page might depend
484 // on things other than the WikiDB (e.g. PhpWeather,
487 $timestamp = $dbi->getTimestamp();
488 $request->appendValidators(array('dbi_timestamp' => $timestamp,
489 '%mtime' => (int)$timestamp,
492 return $plugin->run($dbi, $plugin_args, $request, $basepage);
494 return $plugin->makeList($plugin_args, $request, $basepage);
496 return $plugin->makeForm($plugin_args, $request);
501 function getWikiPageLinks($pi, $basepage)
503 if (!($ppi = $this->parsePi($pi)))
505 list($pi_name, $plugin, $plugin_args) = $ppi;
506 if (!is_object($plugin))
508 if ($pi_name != 'plugin')
510 return $plugin->getWikiPageLinks($plugin_args, $basepage);
513 function parsePI($pi)
515 if (!preg_match('/^\s*<\?(plugin(?:-form|-link|-list)?)\s+(\w+)\s*(.*?)\s*\?>\s*$/s', $pi, $m))
516 return $this->_error(sprintf("Bad %s", 'PI'));
518 list(, $pi_name, $plugin_name, $plugin_args) = $m;
519 $plugin = $this->getPlugin($plugin_name, $pi);
521 return array($pi_name, $plugin, $plugin_args);
524 function getPlugin($plugin_name, $pi = false)
526 global $ErrorManager;
527 global $AllAllowedPlugins;
529 if (in_array($plugin_name, $AllAllowedPlugins) === false) {
530 return $this->_error(sprintf(_("Plugin ā%sā does not exist."),
534 // Note that there seems to be no way to trap parse errors
535 // from this include. (At least not via set_error_handler().)
536 $plugin_source = "lib/plugin/$plugin_name.php";
538 $ErrorManager->pushErrorHandler(new WikiMethodCb($this, '_plugin_error_filter'));
539 $plugin_class = "WikiPlugin_$plugin_name";
540 if (!class_exists($plugin_class)) {
541 // $include_failed = !@include_once("lib/plugin/$plugin_name.php");
542 $include_failed = !include_once($plugin_source);
543 $ErrorManager->popErrorHandler();
545 if (!class_exists($plugin_class)) {
546 if ($include_failed) {
547 return $this->_error(sprintf(_("Plugin ā%sā does not exist."),
550 return $this->_error(sprintf(_("%s: no such class"), $plugin_class));
553 $ErrorManager->popErrorHandler();
554 $plugin = new $plugin_class;
555 if (!is_subclass_of($plugin, "WikiPlugin"))
556 return $this->_error(sprintf(_("%s: not a subclass of WikiPlugin."),
563 function _plugin_error_filter($err)
565 if (preg_match("/Failed opening '.*' for inclusion/", $err->errstr))
566 return true; // Ignore this error --- it's expected.
570 function getErrorDetail()
572 return $this->_errors;
575 function _error($message)
577 $this->_errors = $message;
586 // c-hanging-comment-ender-p: nil
587 // indent-tabs-mode: nil