Webylon 3.2 API Docs
  • Package
  • Class
  • Tree
  • Deprecated
  • Download
Version: current
  • 3.2
  • 3.1

Packages

  • 1c
    • exchange
      • catalog
  • auth
  • Booking
  • building
    • company
  • cart
    • shipping
    • steppedcheckout
  • Catalog
    • monument
  • cms
    • assets
    • batchaction
    • batchactions
    • bulkloading
    • comments
    • content
    • core
    • export
    • newsletter
    • publishers
    • reports
    • security
    • tasks
  • Dashboard
  • DataObjectManager
  • event
  • faq
  • forms
    • actions
    • core
    • fields-basic
    • fields-dataless
    • fields-datetime
    • fields-files
    • fields-formatted
    • fields-formattedinput
    • fields-relational
    • fields-structural
    • transformations
    • validators
  • googlesitemaps
  • guestbook
  • installer
  • newsletter
  • None
  • photo
    • gallery
  • PHP
  • polls
  • recaptcha
  • sapphire
    • api
    • bulkloading
    • control
    • core
    • cron
    • dev
    • email
    • fields-formattedinput
    • filesystem
    • formatters
    • forms
    • i18n
    • integration
    • misc
    • model
    • parsers
    • search
    • security
    • tasks
    • testing
    • tools
    • validation
    • view
    • widgets
  • seo
    • open
      • graph
  • sfDateTimePlugin
  • spamprotection
  • stealth
    • captha
  • subsites
  • userform
    • pagetypes
  • userforms
  • webylon
  • widgets

Classes

  • DataObjectManager_Popup
  • FileDataObjectManager_Popup
  • Form
  • Form_FieldMap
  • FormField
  • FormResponse
  • ImageDataObjectManager_Popup
  • MediawebPage_Popup
  • Order_CancelForm
  • PhotoAlbumManager_Popup
   1 <?php
   2 /**
   3  * Base class for all forms.
   4  * The form class is an extensible base for all forms on a sapphire application.  It can be used
   5  * either by extending it, and creating processor methods on the subclass, or by creating instances
   6  * of form whose actions are handled by the parent controller.
   7  *
   8  * In either case, if you want to get a form to do anything, it must be inextricably tied to a
   9  * controller.  The constructor is passed a controller and a method on that controller.  This method
  10  * should return the form object, and it shouldn't require any arguments.  Parameters, if necessary,
  11  * can be passed using the URL or get variables.  These restrictions are in place so that we can
  12  * recreate the form object upon form submission, without the use of a session, which would be too
  13  * resource-intensive.
  14  * 
  15  * You will need to create at least one method for processing the submission (through {@link FormAction}).
  16  * This method will be passed two parameters: the raw request data, and the form object.
  17  * Usually you want to save data into a {@link DataObject} by using {@link saveInto()}.
  18  * If you want to process the submitted data in any way, please use {@link getData()} rather than
  19  * the raw request data.
  20  * 
  21  * <h2>Validation</h2>
  22  * Each form needs some form of {@link Validator} to trigger the {@link FormField->validate()} methods for each field.
  23  * You can't disable validator for security reasons, because crucial behaviour like extension checks for file uploads depend on it.
  24  * The default validator is an instance of {@link RequiredFields}.
  25  * If you want to enforce serverside-validation to be ignored for a specific {@link FormField},
  26  * you need to subclass it.
  27  *
  28  * <h2>URL Handling</h2>
  29  * The form class extends {@link RequestHandler}, which means it can
  30  * be accessed directly through a URL. This can be handy for refreshing
  31  * a form by ajax, or even just displaying a single form field.
  32  * You can find out the base URL for your form by looking at the
  33  * <form action="..."> value. For example, the edit form in the CMS would be located at
  34  * "admin/EditForm". This URL will render the form without its surrounding
  35  * template when called through GET instead of POST. 
  36  * 
  37  * By appending to this URL, you can render invidual form elements
  38  * through the {@link FormField->FieldHolder()} method.
  39  * For example, the "URLSegment" field in a standard CMS form would be
  40  * accessible through "admin/EditForm/field/URLSegment/FieldHolder".
  41  *
  42  * @package forms
  43  * @subpackage core
  44  */
  45 class Form extends RequestHandler {
  46     
  47     /**
  48      * @var boolean $includeFormTag Accessed by Form.ss; modified by {@link formHtmlContent()}.
  49      * A performance enhancement over the generate-the-form-tag-and-then-remove-it code that was there previously
  50      */
  51     public $IncludeFormTag = true;
  52     
  53     protected $fields;
  54     
  55     protected $actions;
  56     
  57     protected $controller;
  58     
  59     protected $name;
  60 
  61     protected $validator;
  62     
  63     protected $formMethod = "post";
  64     
  65     protected static $current_action;
  66     
  67     /**
  68      * @var Dataobject $record Populated by {@link loadDataFrom()}.
  69      */
  70     protected $record;
  71 
  72     /**
  73      * Keeps track of whether this form has a default action or not.
  74      * Set to false by $this->disableDefaultAction();
  75      */
  76     protected $hasDefaultAction = true;
  77 
  78     /**
  79      * Target attribute of form-tag.
  80      * Useful to open a new window upon
  81      * form submission.
  82      *
  83      * @var string
  84      */
  85     protected $target;
  86     
  87     /**
  88      * Legend value, to be inserted into the 
  89      * <legend> element before the <fieldset>
  90      * in Form.ss template.
  91      *
  92      * @var string
  93      */
  94     protected $legend;
  95     
  96     /**
  97      * The SS template to render this form HTML into.
  98      * Default is "Form", but this can be changed to
  99      * another template for customisation.
 100      * 
 101      * @see Form->setTemplate()
 102      * @var string
 103      */
 104     protected $template;
 105     
 106     protected $buttonClickedFunc;
 107     
 108     protected $message;
 109     
 110     protected $messageType;
 111     
 112     /**
 113      * Should we redirect the user back down to the 
 114      * the form on validation errors rather then just the page
 115      * 
 116      * @var bool
 117      */
 118     protected $redirectToFormOnValidationError = false;
 119     
 120     protected $security = true;
 121     
 122     /**
 123      * HACK This is a temporary hack to allow multiple calls to includeJavascriptValidation on
 124      * the validator (if one is present).
 125      *
 126      * @var boolean
 127      */
 128     public $jsValidationIncluded = false;
 129     
 130     /**
 131      * @var $extraClasses array Extra CSS-classes for the formfield-container
 132      */
 133     protected $extraClasses = array();
 134 
 135     /**
 136      * Create a new form, with the given fields an action buttons.
 137      * 
 138      * @param Controller $controller The parent controller, necessary to create the appropriate form action tag.
 139      * @param String $name The method on the controller that will return this form object.
 140      * @param FieldSet $fields All of the fields in the form - a {@link FieldSet} of {@link FormField} objects.
 141      * @param FieldSet $actions All of the action buttons in the form - a {@link FieldSet} of {@link FormAction} objects
 142      * @param Validator $validator Override the default validator instance (Default: {@link RequiredFields})
 143      */
 144     function __construct($controller, $name, FieldSet $fields, FieldSet $actions, $validator = null) {
 145         parent::__construct();
 146 
 147         $fields->setForm($this);
 148         $actions->setForm($this);
 149 
 150         $this->fields = $fields;
 151         $this->actions = $actions;
 152         $this->controller = $controller;
 153         $this->name = $name;
 154         
 155         if(!$this->controller) user_error("$this->class form created without a controller", E_USER_ERROR);
 156 
 157         // Form validation
 158         $this->validator = ($validator) ? $validator : new RequiredFields();
 159         $this->validator->setForm($this);
 160 
 161         // Form error controls
 162         $this->setupFormErrors();
 163         
 164         // Check if CSRF protection is enabled, either on the parent controller or from the default setting. Note that
 165         // method_exists() is used as some controllers (e.g. GroupTest) do not always extend from Object.
 166         if(method_exists($controller, 'securityTokenEnabled')) {
 167             $this->security = $controller->securityTokenEnabled();
 168         } else {
 169             $this->security = self::$default_security;
 170         }
 171     }
 172     
 173     static $url_handlers = array(
 174         'field/$FieldName!' => 'handleField',
 175         '$Action!' => 'handleAction',
 176         'POST ' => 'httpSubmission',
 177         'GET ' => 'httpSubmission',
 178     );
 179     
 180     /**
 181      * Set up current form errors in session to
 182      * the current form if appropriate.
 183      */
 184     function setupFormErrors() {
 185         $errorInfo = Session::get("FormInfo.{$this->FormName()}");
 186 
 187         if(isset($errorInfo['errors']) && is_array($errorInfo['errors'])) {
 188             foreach($errorInfo['errors'] as $error) {
 189                 $field = $this->fields->dataFieldByName($error['fieldName']);
 190 
 191                 if(!$field) {
 192                     $errorInfo['message'] = $error['message'];
 193                     $errorInfo['type'] = $error['messageType'];
 194                 } else {
 195                     $field->setError($error['message'], $error['messageType']);
 196                 }
 197             }
 198 
 199             // load data in from previous submission upon error
 200             if(isset($errorInfo['data'])) $this->loadDataFrom($errorInfo['data']);
 201         }
 202 
 203         if(isset($errorInfo['message']) && isset($errorInfo['type'])) {
 204             $this->setMessage($errorInfo['message'], $errorInfo['type']);
 205         }
 206     }
 207     
 208     /**
 209      * Handle a form submission.  GET and POST requests behave identically.
 210      * Populates the form with {@link loadDataFrom()}, calls {@link validate()},
 211      * and only triggers the requested form action/method
 212      * if the form is valid.
 213      */
 214     function httpSubmission($request) {
 215         $vars = $request->requestVars();
 216         if(isset($funcName)) {
 217             Form::set_current_action($funcName);
 218         }
 219         
 220         // Populate the form
 221         $this->loadDataFrom($vars, true);
 222         
 223         // Protection against CSRF attacks
 224         if($this->securityTokenEnabled()) {
 225             $securityID = Session::get('SecurityID');
 226 
 227             if(!$securityID || !isset($vars['SecurityID']) || $securityID != $vars['SecurityID']) {
 228                 $this->httpError(400, "SecurityID doesn't match, possible CSRF attack.");
 229             }
 230         }
 231         
 232         // Determine the action button clicked
 233         $funcName = null;
 234         foreach($vars as $paramName => $paramVal) {
 235             if(substr($paramName,0,7) == 'action_') {
 236                 // Break off querystring arguments included in the action
 237                 if(strpos($paramName,'?') !== false) {
 238                     list($paramName, $paramVars) = explode('?', $paramName, 2);
 239                     $newRequestParams = array();
 240                     parse_str($paramVars, $newRequestParams);
 241                     $vars = array_merge((array)$vars, (array)$newRequestParams);
 242                 }
 243                 
 244                 // Cleanup action_, _x and _y from image fields
 245                 $funcName = preg_replace(array('/^action_/','/_x$|_y$/'),'',$paramName);
 246                 break;
 247             }
 248         }
 249         
 250         // If the action wasnt' set, choose the default on the form.
 251         if(!isset($funcName) && $defaultAction = $this->defaultAction()){
 252             $funcName = $defaultAction->actionName();
 253         }
 254             
 255         if(isset($funcName)) {
 256             $this->setButtonClicked($funcName);
 257         }
 258         
 259         // Validate the form
 260         if(!$this->validate()) {
 261             if(Director::is_ajax()) {
 262                 // Special case for legacy Validator.js implementation (assumes eval'ed javascript collected through FormResponse)
 263                 if($this->validator->getJavascriptValidationHandler() == 'prototype') {
 264                     return FormResponse::respond();
 265                 } else {
 266                     $acceptType = $request->getHeader('Accept');
 267                     if(strpos($acceptType, 'application/json') !== FALSE) {
 268                         // Send validation errors back as JSON with a flag at the start
 269                         $response = new SS_HTTPResponse(Convert::array2json($this->validator->getErrors()));
 270                         $response->addHeader('Content-Type', 'application/json');
 271                     } else {
 272                         $this->setupFormErrors();
 273                         // Send the newly rendered form tag as HTML
 274                         $response = new SS_HTTPResponse($this->forAjaxTemplate());
 275                         $response->addHeader('Content-Type', 'text/html');
 276                     }
 277                     
 278                     return $response;
 279                 }
 280             } else {
 281                 if($this->getRedirectToFormOnValidationError()) {
 282                     if($pageURL = $request->getHeader('Referer')) {
 283                         if(Director::is_site_url($pageURL)) {
 284                             // Remove existing pragmas
 285                             $pageURL = preg_replace('/(#.*)/', '', $pageURL);
 286                             return Director::redirect($pageURL . '#' . $this->FormName());
 287                         }
 288                     }
 289                 }
 290                 return Director::redirectBack();
 291             }
 292         }
 293         
 294         // First, try a handler method on the controller
 295         if($this->controller->hasMethod($funcName)) {
 296             return $this->controller->$funcName($vars, $this, $request);
 297 
 298         // Otherwise, try a handler method on the form object
 299         } else {
 300             if($this->hasMethod($funcName)) {
 301                 return $this->$funcName($vars, $this, $request);
 302             }
 303         }
 304     }
 305     
 306     /**
 307      * Handle a field request.
 308      * Uses {@link Form->dataFieldByName()} to find a matching field,
 309      * and falls back to {@link FieldSet->fieldByName()} to look
 310      * for tabs instead. This means that if you have a tab and a
 311      * formfield with the same name, this method gives priority
 312      * to the formfield.
 313      * 
 314      * @param SS_HTTPRequest $request
 315      * @return FormField
 316      */
 317     function handleField($request) {
 318         $field = $this->dataFieldByName($request->param('FieldName'));
 319         
 320         if($field) {
 321             return $field;
 322         } else {
 323             // falling back to fieldByName, e.g. for getting tabs
 324             return $this->Fields()->fieldByName($request->param('FieldName'));
 325         }
 326     }
 327 
 328     /**
 329      * Convert this form into a readonly form
 330      */
 331     function makeReadonly() {
 332         $this->transform(new ReadonlyTransformation());
 333     }
 334     
 335     /**
 336      * Set whether the user should be redirected back down to the 
 337      * form on the page upon validation errors in the form or if 
 338      * they just need to redirect back to the page
 339      *
 340      * @param bool Redirect to the form
 341      */
 342     public function setRedirectToFormOnValidationError($bool) {
 343         $this->redirectToFormOnValidationError = $bool;
 344     }
 345     
 346     /**
 347      * Get whether the user should be redirected back down to the
 348      * form on the page upon validation errors
 349      *
 350      * @return bool
 351      */
 352     public function getRedirectToFormOnValidationError() {
 353         return $this->redirectToFormOnValidationError;
 354     }
 355 
 356     /**
 357      * Add an error message to a field on this form.  It will be saved into the session
 358      * and used the next time this form is displayed.
 359      */
 360     function addErrorMessage($fieldName, $message, $messageType) {
 361         Session::add_to_array("FormInfo.{$this->FormName()}.errors",  array(
 362             'fieldName' => $fieldName,
 363             'message' => $message,
 364             'messageType' => $messageType,
 365         ));
 366     }
 367 
 368     function transform(FormTransformation $trans) {
 369         $newFields = new FieldSet();
 370         foreach($this->fields as $field) {
 371             $newFields->push($field->transform($trans));
 372         }
 373         $this->fields = $newFields;
 374 
 375         $newActions = new FieldSet();
 376         foreach($this->actions as $action) {
 377             $newActions->push($action->transform($trans));
 378         }
 379         $this->actions = $newActions;
 380 
 381 
 382         // We have to remove validation, if the fields are not editable ;-)
 383         if($this->validator)
 384             $this->validator->removeValidation();
 385     }
 386     
 387     /**
 388      * Get the {@link Validator} attached to this form.
 389      * @return Validator
 390      */
 391     function getValidator() {
 392         return $this->validator;
 393     }
 394 
 395     /**
 396      * Set the {@link Validator} on this form.
 397      */
 398     function setValidator( Validator $validator ) {
 399         if($validator) {
 400             $this->validator = $validator;
 401             $this->validator->setForm($this);
 402         }
 403     }
 404 
 405     /**
 406      * Remove the {@link Validator} from this from.
 407      */
 408     function unsetValidator(){
 409         $this->validator = null;
 410     }
 411 
 412     /**
 413      * Convert this form to another format.
 414      */
 415     function transformTo(FormTransformation $format) {
 416         $newFields = new FieldSet();
 417         foreach($this->fields as $field) {
 418             $newFields->push($field->transformTo($format));
 419         }
 420         $this->fields = $newFields;
 421 
 422         // We have to remove validation, if the fields are not editable ;-)
 423         if($this->validator)
 424             $this->validator->removeValidation();
 425     }
 426 
 427         
 428     /**
 429      * Generate extra special fields - namely the SecurityID field
 430      * 
 431      * @return FieldSet
 432      */
 433     public function getExtraFields() {
 434         $extraFields = new FieldSet();
 435         
 436         if(!$this->fields->fieldByName('SecurityID') && $this->securityTokenEnabled()) {
 437             if(Session::get('SecurityID')) {
 438                 $securityID = Session::get('SecurityID');
 439             } else {
 440                 $securityID = rand();
 441                 Session::set('SecurityID', $securityID);
 442             }
 443             
 444             $securityField = new HiddenField('SecurityID', '', $securityID);
 445             $securityField->setForm($this);
 446             $extraFields->push($securityField);
 447             $this->securityTokenAdded = true;
 448         }
 449         
 450         // add the "real" HTTP method if necessary (for PUT, DELETE and HEAD)
 451         if($this->FormMethod() != $this->FormHttpMethod()) {
 452             $methodField = new HiddenField('_method', '', $this->FormHttpMethod());
 453             $methodField->setForm($this);
 454             $extraFields->push($methodField);
 455         }
 456         
 457         return $extraFields;
 458     }
 459     
 460     /**
 461      * Return the form's fields - used by the templates
 462      * 
 463      * @return FieldSet The form fields
 464      */
 465     function Fields() {
 466         foreach($this->getExtraFields() as $field) {
 467             if(!$this->fields->fieldByName($field->Name())) $this->fields->push($field);
 468         }
 469         
 470         return $this->fields;
 471     }
 472     
 473     /**
 474      * Return all <input type="hidden"> fields
 475      * in a form - including fields nested in {@link CompositeFields}.
 476      * Useful when doing custom field layouts.
 477      * 
 478      * @return FieldSet
 479      */
 480     function HiddenFields() {
 481         return $this->fields->HiddenFields();
 482     }
 483     
 484     /**
 485      * Setter for the form fields.
 486      *
 487      * @param FieldSet $fields
 488      */
 489     function setFields($fields) {
 490         $this->fields = $fields;
 491     }
 492     
 493     /**
 494      * Get a named field from this form's fields.
 495      * It will traverse into composite fields for you, to find the field you want.
 496      * It will only return a data field.
 497      * 
 498      * @return FormField
 499      */
 500     function dataFieldByName($name) {
 501         foreach($this->getExtraFields() as $field) {
 502             if(!$this->fields->dataFieldByName($field->Name())) $this->fields->push($field);
 503         }
 504         
 505         return $this->fields->dataFieldByName($name);
 506     }
 507 
 508     /**
 509      * Return the form's action buttons - used by the templates
 510      * 
 511      * @return FieldSet The action list
 512      */
 513     function Actions() {
 514         return $this->actions;
 515     }
 516 
 517     /**
 518      * Setter for the form actions.
 519      *
 520      * @param FieldSet $actions
 521      */
 522     function setActions($actions) {
 523         $this->actions = $actions;
 524     }
 525     
 526     /**
 527      * Unset all form actions
 528      */
 529     function unsetAllActions(){
 530         $this->actions = new FieldSet();
 531     }
 532 
 533     /**
 534      * Unset the form's action button by its name.
 535      * 
 536      * @param string $name
 537      */
 538     function unsetActionByName($name) {
 539         $this->actions->removeByName($name);
 540     }
 541 
 542     /**
 543      * Unset the form's dataField by its name
 544      */
 545     function unsetDataFieldByName($fieldName){
 546         foreach($this->Fields()->dataFields() as $child) {
 547             if(is_object($child) && ($child->Name() == $fieldName || $child->Title() == $fieldName)) {
 548                 $child = null;
 549             }
 550         }
 551     }
 552     
 553     /**
 554      * Remove a field from the given tab.
 555      */
 556     public function unsetFieldFromTab($tabName, $fieldName) {
 557         // Find the tab
 558         $tab = $this->Fields()->findOrMakeTab($tabName);
 559         $tab->removeByName($fieldName);
 560     }
 561 
 562     /**
 563      * Return the attributes of the form tag - used by the templates.
 564      * 
 565      * @return string The attribute string
 566      */
 567     function FormAttributes() {
 568         $attributes = array();
 569         
 570         // Forms shouldn't be cached, cos their error messages won't be shown
 571         HTTP::set_cache_age(0);
 572 
 573         // workaround to include javascript validation
 574         if($this->validator && !$this->jsValidationIncluded) {
 575             $this->validator->includeJavascriptValidation();
 576             if ($this->validator->getJavascriptValidationHandler() == 'prototype')
 577                 $attributes['novalidate'] = 'novalidate';
 578         }
 579         
 580         // compile attributes       
 581         $attributes['id'] = $this->FormName();
 582         $attributes['action'] = $this->FormAction();
 583         $attributes['method'] = $this->FormMethod();
 584         $attributes['enctype'] = $this->FormEncType();
 585         if($this->target) $attributes['target'] = $this->target;
 586         if($this->extraClass()) $attributes['class'] = $this->extraClass();
 587         if($this->validator && $this->validator->getErrors()) {
 588             if(!isset($attributes['class'])) $attributes['class'] = '';
 589             $attributes['class'] .= ' validationerror';
 590         }
 591         
 592         // implode attributes into string
 593         $preparedAttributes = '';
 594         foreach($attributes as $k => $v) {
 595             // Note: as indicated by the $k == value item here; the decisions over what to include in the attributes can sometimes get finicky
 596             if(!empty($v) || $v === '0' || $k == 'value') $preparedAttributes .= " $k=\"" . Convert::raw2att($v) . "\"";
 597         }
 598         
 599         return $preparedAttributes;
 600     }
 601 
 602     /**
 603     * Set the target of this form to any value - useful for opening the form contents in a new window or refreshing another frame
 604     * 
 605     * @param target The value of the target
 606     */
 607     function setTarget($target) {
 608         $this->target = $target;
 609     }
 610     
 611     /**
 612      * Set the legend value to be inserted into
 613      * the <legend> element in the Form.ss template.
 614      */
 615     function setLegend($legend) {
 616         $this->legend = $legend;
 617     }
 618     
 619     /**
 620      * Set the SS template that this form should use
 621      * to render with. The default is "Form".
 622      * 
 623      * @param string $template The name of the template (without the .ss extension)
 624      */
 625     function setTemplate($template) {
 626         $this->template = $template;
 627     }
 628     
 629     /**
 630      * Return the template to render this form with.
 631      * If the template isn't set, then default to the
 632      * form class name e.g "Form".
 633      * 
 634      * @return string
 635      */
 636     function getTemplate() {
 637         if($this->template) return $this->template;
 638         else return $this->class;
 639     }
 640     
 641     /**
 642      * Returns the encoding type of the form.
 643      * This will be either "multipart/form-data"" if there are any {@link FileField} instances,
 644      * otherwise "application/x-www-form-urlencoded"
 645      * 
 646      * @return string The encoding mime type
 647      */
 648     function FormEncType() {
 649         if(is_array($this->fields->dataFields())){
 650             foreach($this->fields->dataFields() as $field) {
 651                 if(is_a($field, "FileField")) return "multipart/form-data";
 652             }
 653         }
 654         return "application/x-www-form-urlencoded";
 655     }
 656     
 657     /**
 658      * Returns the real HTTP method for the form:
 659      * GET, POST, PUT, DELETE or HEAD.
 660      * As most browsers only support GET and POST in
 661      * form submissions, all other HTTP methods are
 662      * added as a hidden field "_method" that
 663      * gets evaluated in {@link Director::direct()}.
 664      * See {@link FormMethod()} to get a HTTP method
 665      * for safe insertion into a <form> tag.
 666      * 
 667      * @return string HTTP method
 668      */
 669     function FormHttpMethod() {
 670         return $this->formMethod;
 671     }
 672     
 673     /**
 674      * Returns the form method to be used in the <form> tag.
 675      * See {@link FormHttpMethod()} to get the "real" method.
 676      * 
 677      * @return string Form tag compatbile HTTP method: 'get' or 'post'
 678      */
 679     function FormMethod() {
 680         if(in_array($this->formMethod,array('get','post'))) {
 681             return $this->formMethod;
 682         } else {
 683             return 'post';
 684         }
 685     }
 686     
 687     /**
 688      * Set the form method: GET, POST, PUT, DELETE.
 689      * 
 690      * @param $method string
 691      */
 692     function setFormMethod($method) {
 693         $this->formMethod = strtolower($method);
 694     }
 695     
 696     /**
 697      * Return the form's action attribute.
 698      * This is build by adding an executeForm get variable to the parent controller's Link() value
 699      * 
 700      * @return string 
 701      */
 702     function FormAction() {
 703         if ($this->formActionPath) {
 704             return $this->formActionPath;
 705         } elseif($this->controller->hasMethod("FormObjectLink")) {
 706             return $this->controller->FormObjectLink($this->name);
 707         } else {
 708             return Controller::join_links($this->controller->Link(), $this->name);
 709         }
 710     }
 711     
 712     /** @ignore */
 713     private $formActionPath = false;
 714     
 715     /**
 716      * Set the form action attribute to a custom URL.
 717      * 
 718      * Note: For "normal" forms, you shouldn't need to use this method.  It is recommended only for situations where you have
 719      * two relatively distinct parts of the system trying to communicate via a form post.
 720      */
 721     function setFormAction($path) {
 722         $this->formActionPath = $path;
 723     }
 724 
 725     /**
 726      * @ignore
 727      */
 728     private $htmlID = null;
 729     
 730     /**
 731      * Returns the name of the form
 732      */
 733     function FormName() {
 734         if($this->htmlID) return $this->htmlID;
 735         else return $this->class . '_' . str_replace(array('.','/'),'',$this->name);
 736     }
 737 
 738     /**
 739      * Set the HTML ID attribute of the form
 740      */
 741     function setHTMLID($id) {
 742         $this->htmlID = $id;
 743     }
 744     
 745     /**
 746      * Returns this form's controller
 747      */
 748     function Controller() {
 749         return $this->controller;
 750     }
 751     
 752     /**
 753      * @return string
 754      */
 755     function Name() {
 756         return $this->name;
 757     }
 758     
 759     /**
 760      * Returns an object where there is a method with the same name as each data field on the form.
 761      * That method will return the field itself.
 762      * It means that you can execute $firstNameField = $form->FieldMap()->FirstName(), which can be handy
 763      */
 764     function FieldMap() {
 765         return new Form_FieldMap($this);
 766     }
 767 
 768     /**
 769      * The next functions store and modify the forms
 770      * message attributes. messages are stored in session under
 771      * $_SESSION[formname][message];
 772      * 
 773      * @return string
 774      */
 775     function Message() {
 776         $this->getMessageFromSession();
 777         $message = $this->message;
 778         $this->clearMessage();
 779         return $message;
 780     }
 781     
 782     /**
 783      * @return string
 784      */
 785     function MessageType() {
 786         $this->getMessageFromSession();
 787         return $this->messageType;
 788     }
 789 
 790     protected function getMessageFromSession() {
 791         if($this->message || $this->messageType) {
 792             return $this->message;
 793         }else{
 794             $this->message = Session::get("FormInfo.{$this->FormName()}.formError.message");
 795             $this->messageType = Session::get("FormInfo.{$this->FormName()}.formError.type");
 796 
 797             Session::clear("FormInfo.{$this->FormName()}");
 798         }
 799     }
 800 
 801     /**
 802      * Set a status message for the form.
 803      * 
 804      * @param message the text of the message
 805      * @param type Should be set to good, bad, or warning.
 806      */
 807     function setMessage($message, $type) {
 808         $this->message = $message;
 809         $this->messageType = $type;
 810     }
 811 
 812     /**
 813      * Set a message to the session, for display next time this form is shown.
 814      * 
 815      * @param message the text of the message
 816      * @param type Should be set to good, bad, or warning.
 817      */
 818     function sessionMessage($message, $type) {
 819         Session::set("FormInfo.{$this->FormName()}.formError.message", $message);
 820         Session::set("FormInfo.{$this->FormName()}.formError.type", $type);
 821     }
 822 
 823     static function messageForForm( $formName, $message, $type ) {
 824         Session::set("FormInfo.{$formName}.formError.message", $message);
 825         Session::set("FormInfo.{$formName}.formError.type", $type);
 826     }
 827 
 828     function clearMessage() {
 829         $this->message  = null;
 830         Session::clear("FormInfo.{$this->FormName()}.errors");
 831         Session::clear("FormInfo.{$this->FormName()}.formError");
 832     }
 833     function resetValidation() {
 834         Session::clear("FormInfo.{$this->FormName()}.errors");
 835     }
 836 
 837     /**
 838      * Returns the DataObject that has given this form its data
 839      * through {@link loadDataFrom()}.
 840      * 
 841      * @return DataObject
 842      */
 843     function getRecord() {
 844         return $this->record;
 845     }
 846     
 847     /**
 848      * Get the legend value to be inserted into the
 849      * <legend> element in Form.ss
 850      *
 851      * @return string
 852      */
 853     function getLegend() {
 854         return $this->legend;
 855     }
 856 
 857     /**
 858      * Processing that occurs before a form is executed.
 859      * This includes form validation, if it fails, we redirect back
 860      * to the form with appropriate error messages.
 861      * Triggered through {@link httpSubmission()} which is triggered
 862      */
 863      function validate(){
 864         if($this->validator){
 865             $errors = $this->validator->validate();
 866 
 867             if($errors){
 868                 if(Director::is_ajax() && $this->validator->getJavascriptValidationHandler() == 'prototype') {
 869                     FormResponse::status_message(_t('Form.VALIDATIONFAILED', 'Validation failed'), 'bad');
 870                     foreach($errors as $error) {
 871                         FormResponse::add(sprintf(
 872                             "validationError('%s', '%s', '%s');\n",
 873                             Convert::raw2js($error['fieldName']),
 874                             Convert::raw2js($error['message']),
 875                             Convert::raw2js($error['messageType'])
 876                         ));
 877                     }
 878                 } else {
 879                     $data = $this->getData();
 880 
 881                     // Load errors into session and post back
 882                     Session::set("FormInfo.{$this->FormName()}", array(
 883                         'errors' => $errors,
 884                         'data' => $data,
 885                     ));
 886 
 887                 }
 888                 return false;
 889             }
 890         }
 891         return true;
 892     }
 893 
 894     /**
 895      * Load data from the given DataObject or array.
 896      * It will call $object->MyField to get the value of MyField.
 897      * If you passed an array, it will call $object[MyField].
 898      * Doesn't save into dataless FormFields ({@link DatalessField}),
 899      * as determined by {@link FieldSet->dataFields()}.
 900      * 
 901      * By default, if a field isn't set (as determined by isset()),
 902      * its value will not be saved to the field, retaining
 903      * potential existing values.
 904      * 
 905      * Passed data should not be escaped, and is saved to the FormField instances unescaped.
 906      * Escaping happens automatically on saving the data through {@link saveInto()}.
 907      * 
 908      * @uses FieldSet->dataFields()
 909      * @uses FormField->setValue()
 910      * 
 911      * @param array|DataObject $data
 912      * @param boolean $clearMissingFields By default, fields which don't match
 913      *  a property or array-key of the passed {@link $data} argument are "left alone",
 914      *  meaning they retain any previous values (if present). If this flag is set to true,
 915      *  those fields are overwritten with null regardless if they have a match in {@link $data}.
 916      * @param $fieldList An optional list of fields to process.  This can be useful when you have a 
 917      * form that has some fields that save to one object, and some that save to another.
 918      */
 919     function loadDataFrom($data, $clearMissingFields = false, $fieldList = null) {
 920         if(!is_object($data) && !is_array($data)) {
 921             user_error("Form::loadDataFrom() not passed an array or an object", E_USER_WARNING);
 922             return false;
 923         }
 924 
 925         // if an object is passed, save it for historical reference through {@link getRecord()}
 926         if(is_object($data)) $this->record = $data;
 927 
 928         // dont include fields without data
 929         $dataFields = $this->fields->dataFields();
 930         if($dataFields) foreach($dataFields as $field) {
 931             $name = $field->Name();
 932             
 933             // Skip fields that have been exlcuded
 934             if($fieldList && !in_array($name, $fieldList)) continue;
 935             
 936             // First check looks for (fieldname)_unchanged, an indicator that we shouldn't overwrite the field value
 937             if(is_array($data) && isset($data[$name . '_unchanged'])) continue;
 938             
 939             // get value in different formats
 940             $hasObjectValue = false;
 941             if(
 942                 is_object($data) 
 943                 && (
 944                     isset($data->$name)
 945                     || $data->hasMethod($name)
 946                     || ($data->hasMethod('hasField') && $data->hasField($name))
 947                 )
 948             ) {
 949                 // We don't actually call the method because it might be slow.  
 950                 // In a later release, relation methods will just return references to the query that should be executed, 
 951                 // and so we will be able to safely pass the return value of the 
 952                 // relation method to the first argument of setValue
 953                 $val = $data->__get($name);
 954                 $hasObjectValue = true;
 955             } else if(strpos($name,'[') && is_array($data) && !isset($data[$name])) {
 956                 // if field is in array-notation, we need to resolve the array-structure PHP creates from query-strings
 957                 preg_match('/' . addcslashes($name,'[]') . '=([^&]*)/', urldecode(http_build_query($data)), $matches);
 958                 $val = isset($matches[1]) ? $matches[1] : null;
 959             } elseif(is_array($data) && array_key_exists($name, $data)) {
 960                 // else we assume its a simple keyed array
 961                 $val = $data[$name];
 962             } else {
 963                 $val = null;
 964             }
 965 
 966             // save to the field if either a value is given, or loading of blank/undefined values is forced
 967             if(isset($val) || $hasObjectValue || $clearMissingFields) {
 968                 // pass original data as well so composite fields can act on the additional information
 969                 $field->setValue($val, $data);
 970             }
 971         }
 972     }
 973     
 974     /**
 975      * Save the contents of this form into the given data object.
 976      * It will make use of setCastedField() to do this.
 977      * 
 978      * @param $dataObject The object to save data into
 979      * @param $fieldList An optional list of fields to process.  This can be useful when you have a 
 980      * form that has some fields that save to one object, and some that save to another.
 981      */
 982     function saveInto(DataObjectInterface $dataObject, $fieldList = null) {
 983         $dataFields = $this->fields->saveableFields();
 984         $lastField = null;
 985 
 986         if($dataFields) foreach($dataFields as $field) {
 987             // Skip fields that have been exlcuded
 988             if($fieldList && is_array($fieldList) && !in_array($field->Name(), $fieldList)) continue;
 989 
 990 
 991             $saveMethod = "save{$field->Name()}";
 992 
 993             if($field->Name() == "ClassName"){
 994                 $lastField = $field;
 995             }else if( $dataObject->hasMethod( $saveMethod ) ){
 996                 $dataObject->$saveMethod( $field->dataValue());
 997             } else if($field->Name() != "ID"){
 998                 $field->saveInto($dataObject);
 999             }
1000         }
1001         if($lastField) $lastField->saveInto($dataObject);
1002     }
1003     
1004     /**
1005      * Get the submitted data from this form through
1006      * {@link FieldSet->dataFields()}, which filters out
1007      * any form-specific data like form-actions.
1008      * Calls {@link FormField->dataValue()} on each field,
1009      * which returns a value suitable for insertion into a DataObject
1010      * property.
1011      * 
1012      * @return array
1013      */
1014     function getData() {
1015         $dataFields = $this->fields->dataFields();
1016         $data = array();
1017         
1018         if($dataFields){
1019             foreach($dataFields as $field) {
1020                 if($field->Name()) {
1021                     $data[$field->Name()] = $field->dataValue();
1022                 }
1023             }
1024         }
1025         return $data;
1026     }
1027 
1028     /**
1029      * Resets a specific field to its passed default value.
1030      * Does NOT clear out all submitted data in the form.
1031      * 
1032      * @param string $fieldName
1033      * @param mixed $fieldValue
1034      */
1035     function resetField($fieldName, $fieldValue = null) {
1036         $dataFields = $this->fields->dataFields();
1037         if($dataFields) foreach($dataFields as $field) {
1038             if($field->Name()==$fieldName) {
1039                 $field = $field->setValue($fieldValue);
1040             }
1041         }
1042     }
1043     
1044     /**
1045      * Call the given method on the given field.
1046      * This is used by Ajax-savvy form fields.  By putting '&action=callfieldmethod' to the end
1047      * of the form action, they can access server-side data.
1048      * @param fieldName The name of the field.  Can be overridden by $_REQUEST[fieldName]
1049      * @param methodName The name of the field.  Can be overridden by $_REQUEST[methodName]
1050      */
1051     function callfieldmethod($data) {
1052         $fieldName = $data['fieldName'];
1053         $methodName = $data['methodName'];
1054         $fields = $this->fields->dataFields();
1055 
1056         // special treatment needed for TableField-class and TreeDropdownField
1057         if(strpos($fieldName, '[')) {
1058             preg_match_all('/([^\[]*)/',$fieldName, $fieldNameMatches);
1059             preg_match_all('/\[([^\]]*)\]/',$fieldName, $subFieldMatches);
1060             $tableFieldName = $fieldNameMatches[1][0];
1061             $subFieldName = $subFieldMatches[1][1];
1062         }
1063 
1064         if(isset($tableFieldName) && isset($subFieldName) && is_a($fields[$tableFieldName], 'TableField')) {
1065             $field = $fields[$tableFieldName]->getField($subFieldName, $fieldName);
1066             return $field->$methodName();
1067         } else if(isset($fields[$fieldName])) {
1068             return $fields[$fieldName]->$methodName();
1069         } else {
1070             user_error("Form::callfieldmethod() Field '$fieldName' not found", E_USER_ERROR);
1071         }
1072 
1073     }
1074 
1075     /**
1076      * Return a rendered version of this form.
1077      * 
1078      * This is returned when you access a form as $FormObject rather
1079      * than <% control FormObject %>
1080      */
1081     function forTemplate() {
1082         return $this->renderWith(array(
1083             $this->getTemplate(),
1084             $this->class,
1085             'Form'
1086         ));
1087     }
1088 
1089     /**
1090      * Return a rendered version of this form, suitable for ajax post-back.
1091      * It triggers slightly different behaviour, such as disabling the rewriting of # links
1092      */
1093     function forAjaxTemplate() {
1094         $view = new SSViewer(array(
1095             $this->getTemplate(),
1096             $this->class,
1097             'Form'
1098         ));
1099         
1100         return $view->dontRewriteHashlinks()->process($this);
1101     }
1102 
1103     /**
1104      * Returns an HTML rendition of this form, without the <form> tag itself.
1105      * Attaches 3 extra hidden files, _form_action, _form_name, _form_method, and _form_enctype.  These are
1106      * the attributes of the form.  These fields can be used to send the form to Ajax.
1107      */
1108     function formHtmlContent() {
1109         // Call FormAttributes to force inclusion of custom client-side validation of fields
1110         // because it won't be included by the template
1111         if($this->validator && !$this->jsValidationIncluded) $this->validator->includeJavascriptValidation();
1112         
1113         $this->IncludeFormTag = false;
1114         $content = $this->forTemplate();
1115         $this->IncludeFormTag = true;
1116 
1117         $content .= "<input type=\"hidden\" name=\"_form_action\" id=\"" . $this->FormName . "_form_action\" value=\"" . $this->FormAction() . "\" />\n";
1118         $content .= "<input type=\"hidden\" name=\"_form_name\" value=\"" . $this->FormName() . "\" />\n";
1119         $content .= "<input type=\"hidden\" name=\"_form_method\" value=\"" . $this->FormMethod() . "\" />\n";
1120         $content .= "<input type=\"hidden\" name=\"_form_enctype\" value=\"" . $this->FormEncType() . "\" />\n";
1121 
1122         return $content;
1123     }
1124 
1125     /**
1126      * Render this form using the given template, and return the result as a string
1127      * You can pass either an SSViewer or a template name
1128      */
1129     function renderWithoutActionButton($template) {
1130         $custom = $this->customise(array(
1131             "Actions" => "",
1132         ));
1133 
1134         if(is_string($template)) $template = new SSViewer($template);
1135         return $template->process($custom);
1136     }
1137 
1138 
1139     /**
1140      * Sets the button that was clicked.  This should only be called by the Controller.
1141      * @param funcName The name of the action method that will be called.
1142      */
1143     function setButtonClicked($funcName) {
1144         $this->buttonClickedFunc = $funcName;
1145     }
1146 
1147     function buttonClicked() {
1148         foreach($this->actions as $action) {
1149             if($this->buttonClickedFunc == $action->actionName()) return $action;
1150         }
1151     }
1152 
1153     /**
1154      * Return the default button that should be clicked when another one isn't available
1155      */
1156     function defaultAction() {
1157         if($this->hasDefaultAction && $this->actions)
1158             return $this->actions->First();
1159     }
1160 
1161     /**
1162      * Disable the default button.
1163      * Ordinarily, when a form is processed and no action_XXX button is available, then the first button in the actions list
1164      * will be pressed.  However, if this is "delete", for example, this isn't such a good idea.
1165      */
1166     function disableDefaultAction() {
1167         $this->hasDefaultAction = false;
1168     }
1169     
1170     /**
1171      * Disable the requirement of a SecurityID in the Form. This security protects
1172      * against CSRF attacks, but you should disable this if you don't want to tie 
1173      * a form to a session - eg a search form.
1174      */
1175     function disableSecurityToken() {
1176         $this->security = false;
1177     }
1178     
1179     
1180     private static $default_security = true;
1181     
1182     /**
1183      * Disable security tokens for every form on this site.
1184      */
1185     static function disable_all_security_tokens() {
1186         self::$default_security = false;
1187     }
1188     
1189     /**
1190      * Returns true if security is enabled - that is if the SecurityID
1191      * should be included and checked on this form.
1192      *
1193      * @return bool
1194      */
1195     function securityTokenEnabled() {
1196         return $this->security;
1197     }
1198 
1199     /**
1200      * Returns the name of a field, if that's the only field that the current controller is interested in.
1201      * It checks for a call to the callfieldmethod action.
1202      * This is useful for optimising your forms
1203      * 
1204      * @return string
1205      */
1206     static function single_field_required() {
1207         if(self::current_action() == 'callfieldmethod') return $_REQUEST['fieldName'];
1208     }
1209 
1210     /**
1211      * Return the current form action being called, if available.
1212      * This is useful for optimising your forms
1213      */
1214     static function current_action() {
1215         return self::$current_action;
1216     }
1217 
1218     /**
1219      * Set the current form action.  Should only be called by Controller.
1220      */
1221     static function set_current_action($action) {
1222         self::$current_action = $action;
1223     }
1224     
1225     /**
1226      * Compiles all CSS-classes. 
1227      * 
1228      * @return String CSS-classnames, separated by a space
1229      */
1230     function extraClass() {
1231         return implode($this->extraClasses, " ");
1232     }
1233     
1234     /**
1235      * Add a CSS-class to the form-container.
1236      * 
1237      * @param $class String
1238      */
1239     function addExtraClass($class) {
1240         $this->extraClasses[$class] = $class;
1241     }
1242 
1243     /**
1244      * Remove a CSS-class from the form-container.
1245      * 
1246      * @param $class String
1247      */
1248     function removeExtraClass($class) {
1249         if(array_key_exists($class, $this->extraClasses)) unset($this->extraClasses[$class]);
1250     }
1251     
1252     /**
1253      * Return Form extraClasses as DataObjectSet
1254      * 
1255      * @return DataObjectSet
1256      */
1257     function getExtraClasses() {
1258         $classes = new DataObjectSet();
1259         if (count($this->extraClasses)) {
1260             foreach($this->extraClasses as $class) {
1261                 $classes->push(new ArrayData(array(
1262                     'Class' => $class
1263                 )));
1264             }
1265         }
1266         return $classes;
1267     }
1268     
1269     function debug() {
1270         $result = "<h3>$this->class</h3><ul>";
1271         foreach($this->fields as $field) {
1272             $result .= "<li>$field" . $field->debug() . "</li>";
1273         }
1274         $result .= "</ul>";
1275 
1276         if( $this->validator )
1277                 $result .= '<h3>'._t('Form.VALIDATOR', 'Validator').'</h3>' . $this->validator->debug();
1278 
1279         return $result;
1280     }
1281     
1282     
1283     //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1284     // TESTING HELPERS
1285     //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1286     
1287     /**
1288      * Test a submission of this form.
1289      * @return SS_HTTPResponse the response object that the handling controller produces.  You can interrogate this in your unit test.
1290      */
1291     function testSubmission($action, $data) {
1292         $data['action_' . $action] = true;
1293         
1294         return Director::test($this->FormAction(), $data, Controller::curr()->getSession());
1295         
1296         //$response = $this->controller->run($data);
1297         //return $response;
1298     }
1299     
1300     /**
1301      * Test an ajax submission of this form.
1302      * @return SS_HTTPResponse the response object that the handling controller produces.  You can interrogate this in your unit test.
1303      */
1304     function testAjaxSubmission($action, $data) {
1305         $data['ajax'] = 1;
1306         return $this->testSubmission($action, $data);
1307     }
1308 }
1309 
1310 /**
1311  * @package forms
1312  * @subpackage core
1313  */
1314 class Form_FieldMap extends ViewableData {
1315     protected $form;
1316     
1317     function __construct($form) {
1318         $this->form = $form;
1319         parent::__construct();
1320     }
1321     
1322     /**
1323      * Ensure that all potential method calls get passed to __call(), therefore to dataFieldByName
1324      */
1325     function hasMethod($method) {
1326         return true;
1327     }
1328 
1329     function __call($method, $args = null) {
1330         return $this->form->Fields()->fieldByName($method);
1331     }
1332 }
1333 
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -
Webylon 3.2 API Docs API documentation generated by ApiGen 2.8.0