disableDefaultAction(); */ protected $hasDefaultAction = true; /** * Target attribute of form-tag. * Useful to open a new window upon * form submission. * * @var string */ protected $target; protected $buttonClickedFunc; protected $message; protected $messageType; protected $security = true; /** * HACK This is a temporary hack to allow multiple calls to includeJavascriptValidation on * the validator (if one is present). * * @var boolean */ public $jsValidationIncluded = false; /** * Create a new form, with the given fields an action buttons. * * @param controller The parent controller, necessary to create the appropriate form action tag. * @param name The method on the controller that will return this form object. * @param fields All of the fields in the form - a {@link FieldSet} of {@link FormField} objects. * @param actions All of the action buttons in the form - a {@link FieldSet} of {@link FormAction} objects */ function __construct($controller, $name, FieldSet $fields, FieldSet $actions, $validator = null) { parent::__construct(); foreach($fields as $field) $field->setForm($this); foreach($actions as $action) $actions->setForm($this); $this->fields = $fields; $this->actions = $actions; $this->controller = $controller; $this->name = $name; if(!$this->controller) user_error("$this->class form created without a controller", E_USER_ERROR); // Form validation if($validator) { $this->validator = $validator; $this->validator->setForm($this); } // Form error controls $errorInfo = Session::get("FormInfo.{$this->FormName()}"); if(isset($errorInfo['errors']) && is_array($errorInfo['errors'])){ foreach($errorInfo['errors'] as $error){ $field = $this->fields->dataFieldByName($error['fieldName']); if(!$field){ $errorInfo['message'] = $error['message']; $errorInfo['type'] = $error['messageType']; } else { $field->setError($error['message'],$error['messageType']); } } // load data in from previous submission upon error if(isset($errorInfo['data'])) $this->loadDataFrom($errorInfo['data']); } if(isset($errorInfo['message']) && isset($errorInfo['type'])) { $this->setMessage($errorInfo['message'],$errorInfo['type']); } $this->security = self::$default_security; } static $url_handlers = array( 'field/$FieldName!' => 'handleField', '$Action!' => 'handleAction', 'POST ' => 'httpSubmission', 'GET ' => 'httpSubmission', ); /** * Handle a form submission. GET and POST requests behave identically. * Populates the form with {@link loadDataFrom()}, calls {@link validate()}, * and only triggers the requested form action/method * if the form is valid. */ function httpSubmission($request) { $vars = $request->requestVars(); if(isset($funcName)) { Form::set_current_action($funcName); } // Populate the form $this->loadDataFrom($vars, true); // Validate the form if(!$this->validate()) { if(Director::is_ajax()) { return FormResponse::respond(); } else { Director::redirectBack(); return; } } // Protection against CSRF attacks if($this->securityTokenEnabled()) { $securityID = Session::get('SecurityID'); if(!$securityID || !isset($vars['SecurityID']) || $securityID != $vars['SecurityID']) { $this->httpError(400, "SecurityID doesn't match, possible CRSF attack."); } } // Determine the action button clicked $funcName = null; foreach($vars as $paramName => $paramVal) { if(substr($paramName,0,7) == 'action_') { // Break off querystring arguments included in the action if(strpos($paramName,'?') !== false) { list($paramName, $paramVars) = explode('?', $paramName, 2); $newRequestParams = array(); parse_str($paramVars, $newRequestParams); $vars = array_merge((array)$vars, (array)$newRequestParams); } // Cleanup action_, _x and _y from image fields $funcName = preg_replace(array('/^action_/','/_x$|_y$/'),'',$paramName); break; } } // If the action wasnt' set, choose the default on the form. if(!isset($funcName) && $defaultAction = $this->defaultAction()){ $funcName = $defaultAction->actionName(); } if(isset($funcName)) { $this->setButtonClicked($funcName); } // First, try a handler method on the controller if($this->controller->hasMethod($funcName)) { return $this->controller->$funcName($vars, $this, $request); // Otherwise, try a handler method on the form object } else { return $this->$funcName($vars, $this, $request); } } /** * Handle a field request */ function handleField($request) { return $this->dataFieldByName($request->param('FieldName')); } /** * Convert this form into a readonly form */ function makeReadonly() { $this->transform(new ReadonlyTransformation()); } /** * Add an error message to a field on this form. It will be saved into the session * and used the next time this form is displayed. */ function addErrorMessage($fieldName, $message, $messageType) { Session::addToArray("FormInfo.{$this->FormName()}.errors", array( 'fieldName' => $fieldName, 'message' => $message, 'messageType' => $messageType, )); } function transform(FormTransformation $trans) { $newFields = new FieldSet(); foreach($this->fields as $field) { $newFields->push($field->transform($trans)); } $this->fields = $newFields; $newActions = new FieldSet(); foreach($this->actions as $action) { $newActions->push($action->transform($trans)); } $this->actions = $newActions; // We have to remove validation, if the fields are not editable ;-) if($this->validator) $this->validator->removeValidation(); } /** * Get the {@link Validator} attached to this form. * @return Validator */ function getValidator() { return $this->validator; } /** * Set the {@link Validator} on this form. */ function setValidator( Validator $validator ) { if($validator) { $this->validator = $validator; $this->validator->setForm($this); } } /** * Remove the {@link Validator} from this from. */ function unsetValidator(){ $this->validator = null; } /** * Convert this form to another format. */ function transformTo(FormTransformation $format) { $newFields = new FieldSet(); foreach($this->fields as $field) { $newFields->push($field->transformTo($format)); } $this->fields = $newFields; // We have to remove validation, if the fields are not editable ;-) if($this->validator) $this->validator->removeValidation(); } /** * Generate extra special fields - namely the SecurityID field * * @return FieldSet */ public function getExtraFields() { $extraFields = new FieldSet(); if(!$this->fields->fieldByName('SecurityID') && $this->securityTokenEnabled()) { if(Session::get('SecurityID')) { $securityID = Session::get('SecurityID'); } else { $securityID = rand(); Session::set('SecurityID', $securityID); } $securityField = new HiddenField('SecurityID', '', $securityID); $securityField->setForm($this); $extraFields->push($securityField); $this->securityTokenAdded = true; } // add the "real" HTTP method if necessary (for PUT, DELETE and HEAD) if($this->FormMethod() != $this->FormHttpMethod()) { $methodField = new HiddenField('_method', '', $this->FormHttpMethod()); $methodField->setForm($this); $extraFields->push($methodField); } return $extraFields; } /** * Return the form's fields - used by the templates * * @return FieldSet The form fields */ function Fields() { foreach($this->getExtraFields() as $field) { if(!$this->fields->fieldByName($field->Name())) $this->fields->push($field); } return $this->fields; } /** * Return all fields * in a form - including fields nested in {@link CompositeFields}. * Useful when doing custom field layouts. * * @return FieldSet */ function HiddenFields() { return $this->fields->HiddenFields(); } /** * Setter for the form fields. * * @param FieldSet $fields */ function setFields($fields) { $this->fields = $fields; } /** * Get a named field from this form's fields. * It will traverse into composite fields for you, to find the field you want. * It will only return a data field. * * @return FormField */ function dataFieldByName($name) { foreach($this->getExtraFields() as $field) { if(!$this->fields->dataFieldByName($field->Name())) $this->fields->push($field); } return $this->fields->dataFieldByName($name); } /** * Return the form's action buttons - used by the templates * * @return FieldSet The action list */ function Actions() { return $this->actions; } /** * Setter for the form actions. * * @param FieldSet $actions */ function setActions($actions) { $this->actions = $actions; } /** * Unset all form actions */ function unsetAllActions(){ $this->actions = new FieldSet(); } /** * Unset the form's action button by its name. * * @param string $name */ function unsetActionByName($name) { $this->actions->removeByName($name); } /** * Unset the form's dataField by its name */ function unsetDataFieldByName($fieldName){ foreach($this->Fields()->dataFields() as $child) { if(is_object($child) && ($child->Name() == $fieldName || $child->Title() == $fieldName)) { $child = null; } } } /** * Remove a field from the given tab. */ public function unsetFieldFromTab($tabName, $fieldName) { // Find the tab $tab = $this->Fields()->findOrMakeTab($tabName); $tab->removeByName($fieldName); } /** * Return the attributes of the form tag - used by the templates * @return string The attribute string */ function FormAttributes() { // Forms shouldn't be cached, cos their error messages won't be shown HTTP::set_cache_age(0); if($this->validator && !$this->jsValidationIncluded) $this->validator->includeJavascriptValidation(); if($this->target) $target = " target=\"".$this->target."\""; else $target = ""; return "id=\"" . $this->FormName() . "\" action=\"" . $this->FormAction() . "\" method=\"" . $this->FormMethod() . "\" enctype=\"" . $this->FormEncType() . "\"$target"; } /** * Set the target of this form to any value - useful for opening the form contents in a new window or refreshing another frame * * @param target The value of the target */ function setTarget($target) { $this->target = $target; } /** * Returns the encoding type of the form. * This will be either "multipart/form-data"" if there are any {@link FileField} instances, * otherwise "application/x-www-form-urlencoded" * * @return string The encoding mime type */ function FormEncType() { if(is_array($this->fields->dataFields())){ foreach($this->fields->dataFields() as $field) { if(is_a($field, "FileField")) return "multipart/form-data"; } } return "application/x-www-form-urlencoded"; } /** * Returns the real HTTP method for the form: * GET, POST, PUT, DELETE or HEAD. * As most browsers only support GET and POST in * form submissions, all other HTTP methods are * added as a hidden field "_method" that * gets evaluated in {@link Director::direct()}. * See {@link FormMethod()} to get a HTTP method * for safe insertion into a