Source for file pnForm.php
Documentation is available at pnForm.php
* Base classes for pnForm system
* @copyright (c) 2006, Zikula Development Team
* @link http://www.zikula.org
* @license GNU/GPL - http://www.gnu.org/copyleft/gpl.html
* User interaction handler for pnForm system
* This class is the main entry point for using the pnForm system. It is expected to be used in Zikula's
* user files, such as "pnuser.php", like this:
* function modname_user_new($args)
* // Create instance of pnFormRender class
* $render = FormUtil::newpnForm('howtopnforms');
* // Execute form using supplied template and event handler
* return $render->pnFormExecute('modname_user_new.html', new modname_user_newHandler());
* See tutorials elsewhere for general introduction to pnForm.
* Variable saving all required state information
* List of included files required to recreate plugins (Smarty function.xxx.php files)
* List of instantiated plugins
* Stack with all instantiated blocks (push when starting block, pop when ending block)
* List of validators on page
* Flag indicating if validation has been done or not
* Indicates whether page is valid or not
* Current ID count - used to assign automatic ID's to all items
* Reference to the main user code event handler
* Error message has been set
* Set to true if pnFormRedirect was called. Means no HTML output should be returned.
* Use FormUtil::newpnForm() instead of instantiating pnFormRender directly.
// Construct and make normal Smarty use possible
/** Main event loop handler
* This is the function to call instead of the normal $render->fetch(...)
* @param bool $template Name of template file
* @param pnFormHandler $eventHandler Instance of object that inherits from pnFormHandler
* @return mixed False on errors, true on redirects, and otherwise it returns the HTML output for the page.
// Save handler for later use
if ($eventHandler->initialize($this) === false)
if ($eventHandler->initialize($this) === false)
// render event (calls registerPlugin)
$output = $this->fetch($template);
// Check redirection at this point, ignore any generated HTML if redirected is required.
// We cannot skip HTML generation entirely in case of pnRedirect since there might be
// some relevant code to execute in the plugins.
* This method registers a plugin used in a template. Plugins must beregistered to be used in pnForm
* (unlike Smarty plugins). The register call must be done inside the Smarty plugin function in a
* Smarty plugin file. Use like this:
* // In file "function.myplugin.php"
* class MyPlugin extends pnFormPlugin
* // Smarty plugin function
* function smarty_function_myplugin($params, &$render)
* return $render->pnFormRegisterPlugin('MyPlugin', $params);
* Registering a plugin ensures it is included in the plugin hierarchy of the current page, so that it's
* various event handlers can be called by the framework.
* Do dot use this function for registering Smarty blocks (the $isBlock parameter is for internal use).
* Use pnFormRegisterBlock instead.
* See also all the function.pnformXXX.php plugins for examples.
* @param string $pluginName Full class name of the plugin to register.
* @param array &$params Parameters passed from the Smarty plugin function
* @param bool $isBlock Indicates whether the plugin is a Smarty block or a Smarty function (internal)
* @return string Returns what the render() method of the plugin returns
// Make sure we have a suitable ID for the plugin
// A volatile block is a block that cannot be restored through view state
// This is the case for pnForm plugins inside <!--[if]--> and <!--[foreach]--> tags.
// So create new plugins for these blocks instead of relying on the existing plugins.
$plugin = & new $pluginName($this, $params);
// Make sure to store ID and render reference in plugin
// Store plugin for later reference
// Copy parameters to member variables and attribute set
$plugin->readParameters($this, $params);
$plugin->create($this, $params);
$plugin->load($this, $params);
// Remember which file this plugin came from in order to be able to restore it.
// Fetch plugin instance by ID
// It has already got it's initialize and decode event at this point
// Kill existing plugins beneath a volatile block
if (isset ($plugin->volatile) && $plugin->volatile)
$plugin->dataBound($this, $params);
// Ask plugin to render itself
$output = $plugin->renderBegin($this);
$output = $plugin->render($this);
* Use this like {@link pnFormRegisterPlugin} but for Smarty blocks instead of Smarty plugins.
* // In file "block.myblock.php"
* // pnForm plugin class (also used for blocks)
* class MyBlock extends pnFormPlugin
* // Smarty block function
* function smarty_block_myblock($params, $content, &$render)
* return return $render->pnFormRegisterBlock('MyBlock', $params, $content);
* @param string $pluginName Full class name of the plugin to register.
* @param array &$params Parameters passed from the Smarty block function
* @param string &$content Content passed from the Smarty block function
* pnFormRegisterBlockBegin
$plugin->blockBeginOutput = $output;
$output = $plugin->blockBeginOutput . $plugin->renderContent($this, $content) . $plugin->renderEnd($this);
$plugin->blockBeginOutput = null;
if (!isset ($params['id']))
for ($i= 0; $i< $lim; ++ $i)
$lim = count($plugin->plugins);
for ($i= 0; $i< $lim; ++ $i)
if ($subPlugin != null) {
return isset ($_POST['__pnFormSTATE']);
for ($i= 0; $i< $lim; ++ $i)
for ($i= 0; $i< $lim; ++ $i)
/* --- Public state management --- */
/* --- Error handling --- */
* function handleCommand(...)
* if (... it did not work ...)
* return $render->pnFormRegisterError('Operation X failed due to Y');
include_once 'system/pnRender/plugins/function.pngetstatusmsg.php';
* Register that we have used LogUtil::registerError() to set an error.
* function initialize(&$render)
* if (... not has access ...)
* return $render->pnFormRegisterError(LogUtil::registerPermissionError());
/* --- Redirection --- */
/* --- Event handling --- */
* Call this method to get a piece of code that will generate a postback event. The returned JavaScript code can
* be called at any time to generate the postback. The plugin that receives the postback must implement
* a function "raisePostBackEvent(&$render, $eventArgument)" that will handle the event.
* Example (taken from the {@link pnFormContextMenuItem} plugin):
* function render(&$render)
* $click = $render->pnFormGetPostBackEventReference($this, $this->commandName);
* $url = 'javascript:' . $click;
* $title = $render->pnFormTranslateForDisplay($this->title);
* $html = "<li><a href=\"$url\">$title</a></li>";
* function raisePostBackEvent(&$render, $eventArgument)
* $args = array('commandName' => $eventArgument, 'commandArgument' => null);
* $render->pnFormRaiseEvent($this->onCommand == null ? 'handleCommand' : $this->onCommand, $args);
* @param plugin object Reference to the plugin that should receive the postback event
* @param commandName string Command name to pass to the event handler
return "pnFormDoPostBack('$plugin->id', '$commandName');";
/// Raise event in the main user event handler
/// This method raises an event in the main user event handler. It is usually called from a plugin to signal
/// that something in that plugin needs attention.
if ($handlerClass->$eventHandlerName($this, $args) === false) {
/* --- Include list --- */
return "<input type=\"hidden\" name=\"__pnFormINCLUDES\" value=\"$base64\"/>";
$base64 = $_POST['__pnFormINCLUDES'];
return; // error handler required - drak
require_once $includeFilename;
/* --- Authentication key --- */
$html = "<input type=\"hidden\" name=\"authid\" value=\"$key\" id=\"pnFormAuthid\"/>";
/* --- State management --- */
//$this->dumpPlugins("Encode state", $this->pnFormPlugins);
for ($i= 0; $i< $lim; ++ $i)
$plugin = & $plugins[$i];
// Handle sub-plugins special and clear stuff not to be serialized
$plugin->parentPlugin = null;
$subPlugins = $plugin->plugins;
foreach ($varInfo as $name => $value)
$pluginState[] = $plugin->$name;
$plugin->plugins = & $subPlugins;
return "<input type=\"hidden\" name=\"__pnFormSTATE\" value=\"$base64\"/>";
$base64 = $_POST['__pnFormSTATE'];
return; // FIXME: error handler required - drak
//$this->dumpPlugins("Decoded state", $this->pnFormPlugins);
foreach ($state as $pluginInfo)
$pluginType = $pluginInfo[0];
$pluginState = &$pluginInfo[1];
$subState = &$pluginInfo[2];
$plugin = & new $pluginType($this,$dummy);
$varCount = count($vars);
if ($varCount != count($pluginState)) {
return pn_exit("Cannot restore pnForm plugin of type '$pluginType' since stored and actual number of member vars differ");
for ($i= 0; $i< $varCount; ++ $i)
$plugin->$var = $pluginState[$i];
$lim = count($plugin->plugins);
for ($i= 0; $i< $lim; ++ $i)
$plugin->plugins[$i]->parentPlugin = &$plugins[count($plugins)- 1];
$storedHandler = & $this->pnFormGetState('pnFormRender', 'eventHandler');
// Copy saved data into event handler (this is where form handler variables are restored)
foreach ($varInfo as $name => $value)
$currentHandler->$name = $storedHandler->$name;
/* --- plugin event generators --- */
for ($i= 0; $i< $lim; ++ $i)
$plugins[$i]->initialize($this);
for ($i= 0,$lim= count($plugins); $i< $lim; ++ $i)
$plugins[$i]->decode($this);
if ($targetPlugin != null)
$targetPlugin->raisePostBackEvent($this, $eventArgument);
for ($i= 0,$lim= count($plugins); $i< $lim; ++ $i)
$plugins[$i]->decodePostBackEvent($this);
for ($i= 0; $i< $lim; ++ $i)
$plugins[$i]->postRender($this);
/* --- Reading and settings submitted values --- */
* Read all values from form
* Use this function to read the values send by the browser on postback. The return
* value is an associative array of input names mapping to the posted values.
* array('title' => 'The posted title',
* 'text' => 'The posted text',
* Most input plugins supports grouping of posted data. These inputs allows you to
* write something similar to what you do on the pnformtextinput plugin:
* <!--[pnformtextinput id="title" group="A"]--><br/>
* <!--[pnformtextinput id="text" textMode=multiline group="A"]-->
* <!--[pnformintinput id="servings"]--><br/>
* Grouped data is combined into associative arrays with all the values in the group.
* The above example would give the data set:
* array('A' => array('title' => 'The posted title',
* 'text' => 'The posted text'),
for ($i= 0,$cou= $lim; $i< $cou; ++ $i)
$plugin->saveValue($this, $result);
for ($i= 0,$cou= $lim; $i< $cou; ++ $i)
$plugin->loadValue($this, $values);
echo "<pre style=\"background-color: #CFC; text-align: left;\">\n";
for ($i= 0,$cou= $lim; $i< $cou; ++ $i)
echo "\n(\n{$p->id}: { $p->parentPlugin}";
* Base form handler class
* This is the base class to inherit from when creating your own form handlers.
* Member variables in a form handler object is persisted accross different page requests. This means
* a member variable $this->X can be set on one request and on the next request it will still contain
* A form handler will be notified of various events that happens during it's life-cycle.
* When a specific event occurs then the corresponding event handler (class method) will be executed. Handlers
* are named exactly like their events - this is how the framework knows which methods to call.
* - <b>initialize</b>: this event fires before any of the events for the plugins and can be used to setup
* the form handler. The event handler typically takes care of reading URL variables, access control
* and reading of data from the database.
* - <b>handleCommand</b>: this event is fired by various plugins on the page. Typically it is done by the
* pnFormButton plugin to signal that the user activated a button.
* Initialize form handler
* function initialize(&$render)
* if (!HasAccess) // your access check here
* return $render->pnFormSetErrorMsg('No access');
* $id = FormUtil::getPassedValue('id');
* $data = pnModAPIFunc('MyModule', 'user', 'get',
* return $render->pnFormSetErrorMsg('Unknown data');
* $render->assign($data);
* @return bool False in case of initialization errors, otherwise true. If false is returned then the
* framework assumes that {@link pnFormRender::pnFormSetErrorMsg()} has been called with a suitable
* This event handler is called when a command is issued by the user. Commands are typically something
* that originates from a {@link pnFormButton} plugin. The passed args contains different properties
* depending on the command source, but you should at least find a <var>$args['commandName']</var>
* value indicating the name of the command. The command name is normally specified by the plugin
* that initiated the command.
* This is the base class to inherit from when creating new plugins for the pnForm framework.
* Every pnForm plugin is normally created in a Smarty plugin function file and registered in
* the pnForm framewith with the use of {@link pnFormRender::pnFormRegisterPlugin()}.
* Member variables in a plugin object is persisted accross different page requests. This means
* a member variable $this->X can be set on one request and on the next request it will still contain
* the same value. This probably removes 99% of the use of hidden HTML variables in your web forms.
* A member variable <i>must</i> be declared in order to be persisted:
* class MyPlugin inherits pnFormPlugin
* Member variables in a plugin will be assigned automatically from Smarty parameters when the variable
* and parameter names match. So assume you have a plugin like this:
* class MyPlugin inherits pnFormPlugin
* Then X will be set to 1234 through this template declaration:
* <!--[MyPlugin X="1234"]-->
* A registered plugin will be notified of various events that happens during it's life-cycle.
* When a specific event occurs then the corresponding event handler (class method) will be executed. Handlers
* are named exactly like their events - this is how the framework knows which methods to call.
* - <b>create</b>: Similar to a constructor since it is called directly after the plugin has been created.
* In this event handler you should set the various member variables your plugin requires. You can access
* Smarty parameters through the $params object. The automatic setting of member variables from Smarty
* parameters happens <i>after</i> the create event.
* This event is only fired the first time the plugin is instantiated,
* but not when restored from saved state.
* - <b>load</b>: Called immediately after member variables has been set from their Smarty parameters. So
* the plugin is assumed to be fully initialized when the load event is fired. During the load event the plugin
* is expected to load values from the render object.
* A typical load event handler will just call the loadValue
* handler and pass it the values of the render object (to improve reuse). The loadValue method will then take care of the rest.
* This is also the place where validators should be added to the list of validators.
* function load(&$render, &$params)
* $this->loadValue($render, $render->get_template_vars());
* $render->pnFormAddValidator($this);
* This event is only fired the first time the plugin is instantiated,
* but not when restored from saved state.
* - <b>initialize</b>: this event is the opposite of the create/load event pair. It fires when a plugin
* has been restored from a postback (and before then decode event).
* - <b>decode</b>: this event is fired on postback in order to let the plugin decode the HTTP POST values
* sent by the browser. It is left to the plugin to decide where to store the decode data.
* - <b>dataBound</b>: this event is fired when plugin is loaded and ready - both on postback and the
* - <b>render</b>: this event is fired when the plugin is required to render itself based on the data
* it got through the previous events. This function is only called on Smarty function plugins.
* The event handler is supposed to return the rendered output.
* - <b>renderBegin</b>: this event is for Smarty block plugins only. It is fired in order to allow
* the plugin to render something before the plugins contained within it.
* - <b>renderContent</b>: this event is for Smarty block plugins only. It is fired in order to allow
* the plugin to modify content renderes by the plugins contained within it.
* - <b>renderEnd</b>: this event is for Smarty block plugins only. It is fired in order to allow
* the plugin to render something after the plugins contained within it.
* - <b>postRender</b>: this event is fired after all rendering is done <i>for all plugins on the page</i>.
* In this event handler you can use {@link pnFormRender::pnFormGetPluginById()} to fetch other plugins
* and read/modify their data.
* Most events on one plugin happens before the next plugin is loaded (except the postRender event). So for two
* plugins A and B you would get the event sequence (assuming B is placed after A in the Smarty template):
* This contains the identifier for the plugin. You can use this ID in {@link pnFormRender::pnFormGetPluginById()}
* as well as in JavaScript where it should be set on the HTML elements (this does although depend on the plugin
* Do <i>not</i> change this variable!
* Specifies whether or not a plugin should be rendered
* Reference to parent plugin if used inside a block
* Associative array of attributes to add to the plugin. For instance:
* array('title' => 'A tooltip title', onclick => 'doSomething()')
* Name of function to call in form event handler when plugin is loaded
* If you need to notify the form event handler when the plugin has been loaded then
* specify the name of this handler here. The prototype of the function must be:
* function MyOnLoadHandler(&$render, &$plugin, $params) where $render is the form render,
* $plugin is this plugin, and $params are the Smarty parameters passed to the plugin.
* The data bound handler is called both on postback and first page render.
* class MyPlugin extends pnFormPlugin
* $this->onDataBound = 'MyLoadHandler';
* class MyFormHandler extends pnFormHandler
* function MyLoadHandler(&$render, &$plugin, $params)
* The name "dataBound" was chosen to avoid clashes with the "load" event.
* Reference to sub-plugins
* This variable contains an array of references to sub-plugins when this plugin is
* a block plugin containing other plugins.
* Temporary storage of the output from renderBegin in blocks
* Volatile indicator (disables state management in sub-plugins)
* Read Smarty plugin parameters
* This is the function that takes care of reading smarty parameters and storing them in the member variables
* or attributes (all unknown parameters go into the "attribues" array).
* You can override this for special situations.
// Iterate through all params: place known params in member variables and the rest in the attributes set
foreach ($params as $name => $value)
* Default action is to do nothing.
* @param pnFormRender &$render Reference to pnForm render object
* @param array &$params Parameters passed from the Smarty plugin function
function create(&$render, &$params)
* Default action is to do nothing.
* @param pnFormRender &$render Reference to pnForm render object
* @param array &$params Parameters passed from the Smarty plugin function
function load(&$render, &$params)
* Initialize event handler
* Default action is to do nothing. Typically used to add self as validator.
* @param pnFormRender &$render Reference to pnForm render object
* Default action is to do nothing
* @param pnFormRender &$render Reference to pnForm render object
* @param array &$params Parameters passed from the Smarty plugin function
* Decode event handler for actions that generate a postback event
* Default action is to do nothing. Usefull for buttons that should generate events
* after the plugins have decoded their normal values.
* @param pnFormRender &$render Reference to pnForm render object
* @param array &$params Parameters passed from the Smarty plugin function
* DataBound event handler
* Default action is to call onDataBound handler in form event handler.
* @param pnFormRender &$render Reference to pnForm render object
* @param array &$params Parameters passed from the Smarty plugin function
$render->pnFormEventHandler->$dataBoundHandlerName($render, $this, $params);
$attr .= " $name=\"$value\"";
* Default action is to return an empty string.
* @param pnFormRender &$render Reference to pnForm render object
* @param array &$params Parameters passed from the Smarty plugin function
* @return string The rendered output
* RenderBegin event handler
* Default action is to return an empty string.
* @param pnFormRender &$render Reference to pnForm render object
* @return string The rendered output
* RenderContent event handler
* Default action is to return the content unmodified.
* @param pnFormRender &$render Reference to pnForm render object
* @return string The (optionally) modified content
* RenderEnd event handler
* Default action is to return an empty string.
* @param pnFormRender &$render Reference to pnForm render object
* @return string The rendered output
* PostRender event handler
* Default action is to do nothing.
* @param pnFormRender &$render Reference to pnForm render object
* Utility function to generate HTML for ID attribute
* Generate id="..." for use in the plugin's render methods.
* This function ignores automatically created IDs (those named "plgNNN") and will
* return an empty string for these.
* Base plugin class for plugins that uses CSS styling
* This plugin adds attributes like "color", "back_groundcolor" and "font_weight" to plugins that extends it.
* The extending plugin must call {@link pnFormPlugin::renderAttributes()} to use the added CSS features.
* See also {@link pnFormTextInput} for an example implementation.
* The support CSS styles are listed in the $styleElements array. Please use this as a reference. Underscores
* are converted to hyphens in the resulting output to match the correct CSS styles. When you need to use unsupported
* CSS styles then just write them directly in the style parameter of the plugin:
* <!--[pnformtextinput id="title" maxLength="100" width="30em" style="border-left: 1px solid red;"]-->
* You can also add styling in the code by adding key/value pairs to $styleAttributes. Example:
* $this->styleAttributes['border-right'] = '1px solid green';
* Styles added programatically
static $styleElements = array
('width', 'height', 'color', 'background_color',
'border', 'padding', 'margin',
'float', 'display', 'position', 'visibility', 'overflow', 'clip',
'font', 'font_family', 'font_style', 'font_weight', 'font_size', );
foreach ($this->attributes as $name => $value)
else if (in_array($name, $styleElements))
$this->styleAttributes[$name] = $value;
$attr .= " $name=\" $value\" ";
if (count($this->styleAttributes) > 0 && strlen($style) > 0 && $style[strlen($style)- 1] != ';')
$style .= str_replace('_', '-', $name) . ": $value; ";
$attr .= " style=\"$style\"";
|