* @license PHP License * @package WB * @subpackage content */ /** * Content component * * @version 0.5.1 * @package WB * @subpackage content */ class WBFormProcessor extends WBStdClass { /** * whether to use template engine or not * @var bool */ protected $withFormTmpl = false; /** * automatically parse template and return HTML * @var bool */ protected $parseFormTmpl = true; /** * Template's name - allow sub-classes to override this value * @var string */ protected $formTmplName = 'snippet'; /** * location of form config * * Return sub directory where form element definitions are located * * @return string folder */ protected function getFormConfigDir() { return '.'; } /** * Fetch list of form elements * * The standard behaviour loads form defintions from XML config * file located in form config dir {@link getFormConfigDir() } * Overwrite this to implement your own behaviour. * * @param string name of the xml- and template-filename * @return array $elements */ protected function getFormElementList($name) { // standard behaviour $config = WBClass::create('WBConfig'); $config->load($this->getFormConfigDir(). '/' . $name); // load elements $elements = $config->get(array('form', 'elements')); if( is_array($elements)) { return $elements; } // for backward compatibility $elements = $config->get(array('form')); if( is_array($elements)) { return $elements; } return array(); } /** * Fetch list of form rules * * The standard behaviour loads rules from XML form defintions * file located in form config dir {@link getFormConfigDir() } * Overwrite this to implement your own behaviour. * * @see getFormElementList() * @param string name of the xml filename * @return array $rules */ protected function getFormRuleList($name) { // standard behaviour $config = WBClass::create('WBConfig'); $config->load($this->getFormConfigDir(). '/' . $name , 'xml'); // load rules $rules = $config->get(array('form', 'rules')); if( is_array($rules)) { return $rules; } return array(); } /** * merge form parameters * * This allows to overwrite default form parameters * * @param array $param list of form parameters * @return bool */ protected function mergeFormParameters(&$params) { return true; } /** * Create a brand new form object * * Loaded form defintion, create the form object and inserts rules. * * @param string $name of the xml- and template-filename * @param array preset form values * @return patForms $form * @see getFormElementList() * @see insertFormRules() */ final public function getForm($name, $values = array()) { $search = $this->mkSearchReplace($values); $elements = $this->getFormElementList($name); $this->populateWithValues($elements, $search); $rules = $this->getFormRuleList($name); $this->populateWithValues($rules, $search); $formParams = array( 'action' => '[[SELF]][[PATH]]', 'renderer' => 'Array', 'elements' => &$elements, 'rules' => &$rules ); $this->mergeFormParameters($formParams); $form = WBClass::Create('patForms', $formParams); $this->insertFormRules($form, $name, $elements); return $form; } /** * build search and replace pattern * * Use list of values to build common pattern for replacing placeholers. * * Return value is an array: * $search = array( * 0 => array( * {PATTERN}... * ) * 1 => array( * replacement... * ) *); * * @param array $values * @return array */ private function mkSearchReplace($values) { if (empty($values)) { return array(); } $search = array(array(), array()); foreach ($values as $k => $v) { if (!is_scalar($v)) { continue; } $search[0][] = '{' . strtoupper($k) . '}'; $search[1][] = $v; } return $search; } /** * populate data with replacements * * Walk through data and apply replacment. Recursive method! * * @see mkSearchReplace() * @param array $data * @param array $search */ private function populateWithValues(&$data, $search) { if (empty($search) || empty($data)) { return; } foreach ($data as &$d) { if (is_array($d)) { $this->populateWithValues($d, $search); } $d = str_replace($search[0],$search[1], $d); } } /** * Add form rules to just created form * * This is just an emtpy function meant to bo overwriten by sub classes * * @param patForms $form object * @param string name of the xml- and template-filename * @param array $elements list of elements current form * @return bool true on success */ protected function insertFormRules(&$form, $name, $elements) { return true; } /** * Gerneric form processing helper function * * This makes form processing a lot easier and more unified. Usually, * you just call @link processForm() to receive and display the returned HTML. * * Still, there are two hooks where you can add you code: "onXXXValid" and * "onInvalid". Both "events" try to find a method named accodingly and fire * the function call. The function name rendered out of the event's name * (either "onXXXValidated" or "onXXXInvalid") where XXX is replaced with the * form's name * * * function doSomething * { * return $this->processForm( 'login' ); * } * * function onLoginValidated( $form, $values ) * { * // do login stuff here * return true; * } * * * In there is no "onXXXValid" or "onXXXInvalid" function, standard * behaviour applies * * In case there is "onXXXValid" or "onXXXInvalid" (or both) the return value * controlls whether normal processing is continued * * @param string $name named form to prrocess * @param array $values form values to pre-set * @param string $saveParam name of POST / GET parameter that triggers "save" * @param bool $always - always process form, ignore save parameter, "false" * @return mixed string HTML in case template engine will be used, or bool * @see getForm(); */ public function processForm($name, $values = array(), $saveParam = 'save', $always = false) { // process form $form = $this->getForm($name, $values); $req = WBClass::create('WBRequest'); if ($always) { $save = true; } else { $save = $req->get($saveParam); } if ($save || empty($values)) { $values = $req->export(); } if (!$save) { // pre-fill form even if (!empty($values)) { $form->setValues($values); } if (!$this->withFormTmpl) { return true; } $this->loadTemplates($name); if (isset($values['id'])) { $this->tmpl->addGlobalVar('id', $values['id']); } $this->renderForm($form); if ($this->parseFormTmpl) { return $this->tmpl->getParsedTemplate($this->formTmplName); } else { return true; } } // fake post parameter $_POST = $values; $form->setSubmitted(true); $function = implode('', array_map('ucfirst', explode('/', $name))); if (!$form->validateForm()) { $method = sprintf('on%sInvalid', $function); if (method_exists($this, $method)) { if (!call_user_func(array($this, $method), $form)) { return false; } } if (!$this->withFormTmpl) { return false; } $this->loadTemplates($name); if (isset($values['id'])) { $this->tmpl->addGlobalVar('id', $values['id']); } $this->renderFormError($form); $this->renderForm($form); if ($this->parseFormTmpl) { return $this->tmpl->getParsedTemplate($this->formTmplName); } else { return false; } } $method = sprintf('on%sValid', $function); if (method_exists( $this, $method)) { $values = $form->getValues(); if (!call_user_func(array($this, $method), $form, $values)) { return true; } } if (!$this->withFormTmpl) { return true; } $this->loadTemplates($name . 'Valid'); if (isset($values['id'])) { $this->tmpl->addGlobalVar('id', $values['id']); } $tmp = array(); foreach ($values as $k => $v) { if (is_scalar($v)) { $tmp[$k] = $v; } } $this->tmpl->addGlobalVars($tmp, 'form_values_'); if ($this->parseFormTmpl) { return $this->tmpl->getParsedTemplate($this->formTmplName); } return true; } /** * Add form to template engine * * @param patForms $form * @return bool true */ protected function renderForm($form, $values = array()) { $elements = $form->renderForm(); $values = $form->getValues(); // add basic form tags $this->tmpl->addGlobalVar('form_start', $form->serializeStart()); $this->tmpl->addGlobalVar('form_end', $form->serializeEnd()); $this->tmpl->addGlobalVar('form_id', $form->getAttribute('id')); $this->tmpl->addGlobalVar('form_class', $form->getAttribute('class')); // add all form elements foreach ($elements as $e) { $name = $e['name']; // add value if (isset( $values[$name]) && is_scalar($values[$name])) { $e['value'] = $values[$name]; } // add attributes $data = array(); foreach ($e as $k => $v) { if (is_scalar($v)) { $data[$k] = $v; } } $this->tmpl->addGlobalVars($data, $name . '_'); } return true; } /** * put form errors to template * * @param object $form * @return bool true on success */ protected function renderFormError($form) { $errors = $form->getValidationErrors(); // load error template if (!$this->tmpl->exists('form_error')) { $this->tmpl->readTemplatesFromInput('formError.tmpl'); } // convert errors to simple list $list = array(); // render each form validation error foreach ($errors as $fieldname => $errs) { if (empty($errs)) { continue; } $l = array(); $field = $form->getElement($fieldname); $atts = $field->getAttributes(); if (!isset($atts['class'])) { $atts['class'] = ''; } foreach ($atts as $att => $value) { if (is_scalar($value)) { $l['field_' . $att] = $value; } } if ($fieldname == '__form') { $l['error_type'] = 'form'; } else { $l['error_type'] = 'field'; $field->setAttribute('class', $atts['class'] . ' error'); $this->tmpl->addGlobalVar($fieldname . '_status', 'error'); } foreach ($errs as $err) { $l['error_code'] = $err['code']; $l['error_element'] = $err['element']; $l['error_message'] = $err['message']; $list[] = $l; } } if ($this->tmpl->exists('form_error_entry')) { $this->tmpl->addRows('form_error_entry', $list); } $tmp = $this->tmpl->getParsedTemplate('form_error'); $this->tmpl->addGlobalVar('form_error', $tmp); return true; } }