/**
  * Sets the attributes of the form according to the passed data, performing
  * validation on the way. If the form is submitted, this checks and processes
  * the form.
  *
  * @param array $data The form description hash
  */
 public function __construct($data)
 {
     /*{{{*/
     $GLOBALS['_PIEFORM_REGISTRY'][] = $this;
     if (!isset($data['name']) || !preg_match('/^[a-z_][a-z0-9_]*$/', $data['name'])) {
         throw new PieformException('Forms must have a name, and that name must be valid (validity test: could you give a PHP function the name?)');
     }
     $this->name = $data['name'];
     // If the form has global configuration, get it now
     if (function_exists('pieform_configure')) {
         $formconfig = pieform_configure();
         $defaultelements = isset($formconfig['elements']) ? $formconfig['elements'] : array();
         foreach ($defaultelements as $name => $element) {
             if (!isset($data['elements'][$name])) {
                 $data['elements'][$name] = $element;
             }
         }
     } else {
         $formconfig = array();
     }
     // Assign defaults for the form
     $this->defaults = self::get_pieform_defaults();
     $this->data = array_merge($this->defaults, $formconfig, $data);
     // Set the method - only get/post allowed
     $this->data['method'] = strtolower($this->data['method']);
     if ($this->data['method'] != 'post') {
         $this->data['method'] = 'get';
     }
     // Make sure that the javascript callbacks are valid
     if ($this->data['jsform']) {
         $this->validate_js_callbacks();
     }
     if (!$this->data['validatecallback']) {
         $this->data['validatecallback'] = $this->name . '_validate';
     }
     if (!$this->data['successcallback']) {
         $this->data['successcallback'] = $this->name . '_submit';
     }
     if (!$this->data['replycallback']) {
         $this->data['replycallback'] = $this->name . '_reply';
     }
     $this->data['configdirs'] = array_map(create_function('$a', 'return substr($a, -1) == "/" ? substr($a, 0, -1) : $a;'), (array) $this->data['configdirs']);
     if (empty($this->data['tabindex'])) {
         $this->data['tabindex'] = 0;
     }
     if (!is_array($this->data['elements']) || count($this->data['elements']) == 0) {
         throw new PieformException('Forms must have a list of elements');
     }
     if (isset($this->data['spam'])) {
         // Enable form tricks to make it harder for bots to fill in the form.
         // This was moved from lib/antispam.php, see:
         // http://wiki.mahara.org/Developer_Area/Specifications_in_Development/Anti-spam#section_7
         //
         // Use the spam_error() method in your _validate function to check whether a submitted form
         // has failed any of these checks.
         //
         // Available options:
         //  - hash:    An array of element names to be hashed.  Currently ids of input elements
         //             are also hashed, so you need to be careful if you include 'elementname' in
         //             the hash array, and make sure you rewrite any css or js so it doesn't rely on
         //             an id like 'formname_elementname'.
         //  - secret:  String used to hash the fields.
         //  - mintime: Minimum number of seconds that must pass between page load & form submission.
         //  - maxtime: Maximum number of seconds that must pass between page load & form submission.
         //  - reorder: Array of element names to be reordered at random.
         if (empty($this->data['spam']['secret']) || !isset($this->data['elements']['submit'])) {
             // @todo don't rely on submit element
             throw new PieformException('Forms with spam config must have a secret and submit element');
         }
         $this->time = isset($_POST['__timestamp']) ? $_POST['__timestamp'] : time();
         $spamelements1 = array('__invisiblefield' => array('type' => 'text', 'title' => get_string('spamtrap'), 'defaultvalue' => '', 'class' => 'dontshow'));
         $spamelements2 = array('__timestamp' => array('type' => 'hidden', 'value' => $this->time), '__invisiblesubmit' => array('type' => 'submit', 'value' => get_string('spamtrap'), 'class' => 'dontshow'));
         $insert = rand(0, count($this->data['elements']));
         $this->data['elements'] = array_merge(array_slice($this->data['elements'], 0, $insert, true), $spamelements1, array_slice($this->data['elements'], $insert, count($this->data['elements']) - $insert, true), $spamelements2);
         // Min & max number of seconds between page load & submission
         if (!isset($this->data['spam']['mintime'])) {
             $this->data['spam']['mintime'] = 0.01;
         }
         if (!isset($this->data['spam']['maxtime'])) {
             $this->data['spam']['maxtime'] = 86400;
         }
         if (empty($this->data['spam']['hash'])) {
             $this->data['spam']['hash'] = array();
         }
         $this->data['spam']['hash'][] = '__invisiblefield';
         $this->data['spam']['hash'][] = '__invisiblesubmit';
         $this->hash_fieldnames();
         if (isset($this->data['spam']['reorder'])) {
             // Reorder form fields randomly
             $order = $this->data['spam']['reorder'];
             shuffle($order);
             $order = array_combine($this->data['spam']['reorder'], $order);
             $temp = array();
             foreach (array_keys($this->data['elements']) as $k) {
                 if (isset($order[$k])) {
                     $temp[$order[$k]] = $this->data['elements'][$order[$k]];
                 } else {
                     $temp[$k] = $this->data['elements'][$k];
                 }
             }
             $this->data['elements'] = $temp;
         }
         $this->spamerror = false;
     }
     // Get references to all the elements in the form, excluding fieldsets/containers
     foreach ($this->data['elements'] as $name => &$element) {
         // The name can be in the element itself. This is compatibility for
         // the perl version
         if (isset($element['name'])) {
             $name = $element['name'];
         }
         if (isset($element['type']) && ($element['type'] == 'fieldset' || $element['type'] == 'container')) {
             // Load the fieldset/container plugin as we know this form has one now
             $this->include_plugin('element', $element['type']);
             if ($this->get_property('template')) {
                 self::info("Your form '{$this->name}' has a " . $element['type'] . ", but is using a template. Fieldsets/containers make no sense when using templates");
             }
             foreach ($element['elements'] as $subname => &$subelement) {
                 if (isset($subelement['name'])) {
                     $subname = $subelement['name'];
                 }
                 $this->elementrefs[$subname] =& $subelement;
                 $subelement['name'] = $subname;
             }
             unset($subelement);
         } else {
             $this->elementrefs[$name] =& $element;
         }
         $element['name'] = isset($this->hashedfields[$name]) ? $this->hashedfields[$name] : $name;
     }
     unset($element);
     // Check that all elements have names compliant to PHP's variable naming policy
     // (otherwise things get messy later)
     foreach (array_keys($this->elementrefs) as $name) {
         if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $name)) {
             throw new PieformException('Element "' . $name . '" is badly named (validity test: could you give a PHP variable the name?)');
         }
     }
     // Remove elements to ignore
     // This can't be done using $this->elementrefs, because you can't unset
     // an entry in there and have it unset the entry in $this->data['elements']
     foreach ($this->data['elements'] as $name => $element) {
         if (isset($element['type']) && ($element['type'] == 'fieldset' || $element['type'] == 'container')) {
             foreach ($element['elements'] as $subname => $subelement) {
                 if (!empty($subelement['ignore'])) {
                     unset($this->data['elements'][$name]['elements'][$subname]);
                     unset($this->elementrefs[$subname]);
                 }
             }
         } else {
             if (!empty($element['ignore'])) {
                 unset($this->data['elements'][$name]);
                 unset($this->elementrefs[$name]);
             }
         }
     }
     // Set some attributes for all elements
     $autofocusadded = false;
     foreach ($this->elementrefs as $name => &$element) {
         if (count($element) == 0) {
             throw new PieformException('An element in form "' . $this->name . '" has no data (' . $name . ')');
         }
         if (!isset($element['type']) || $element['type'] == 'markup') {
             $element['type'] = 'markup';
             if (!isset($element['value'])) {
                 throw new PieformException('The markup element "' . $name . '" has no value');
             }
         } else {
             // Now we know what type the element is, we can load the plugin for it
             $this->include_plugin('element', $element['type']);
             // All elements should have at least the title key set
             if (!isset($element['title'])) {
                 $element['title'] = '';
             }
             // This function can be defined by the application using Pieforms,
             // and applies to all elements of this type
             $function = 'pieform_element_' . $element['type'] . '_configure';
             if (function_exists($function)) {
                 $element = $function($element);
             }
             // vvv --------------------------------------------------- vvv
             // After this point Pieforms can set or override attributes
             // without fear that the developer will be able to change them.
             // This function is defined by the plugin itself, to set
             // fields on the element that need to be set but should not
             // be set by the application
             $function = 'pieform_element_' . $element['type'] . '_set_attributes';
             if (function_exists($function)) {
                 $element = $function($element);
                 // Allow an element to remove itself from the form
                 if (!$element) {
                     unset($this->data['elements'][$name]);
                     unset($this->elementrefs[$name]);
                     continue;
                 }
             }
             // Force the form method to post if there is a file to upload
             if (!empty($element['needsmultipart'])) {
                 $this->fileupload = true;
                 if ($this->data['method'] == 'get') {
                     $this->data['method'] = 'post';
                     self::info("Your form '{$this->name}' had the method 'get' and also a file element - it has been converted to 'post'");
                 }
             }
             // Add the autofocus flag to the element if required
             if (!$autofocusadded && $this->data['autofocus'] === true && empty($element['nofocus'])) {
                 $element['autofocus'] = true;
                 $autofocusadded = true;
             } elseif (!empty($this->data['autofocus']) && $this->data['autofocus'] !== true && $name == $this->data['autofocus']) {
                 $element['autofocus'] = true;
             }
             if (!empty($element['autofocus']) && $element['type'] == 'text' && !empty($this->data['autoselect']) && $name == $this->data['autoselect']) {
                 $element['autoselect'] = true;
             }
             // All elements inherit the form tabindex
             $element['tabindex'] = $this->data['tabindex'];
         }
     }
     unset($element);
     // Check if the form was submitted, and if so, validate and process it
     $global = $this->data['method'] == 'get' ? $_GET : $_POST;
     if ($this->data['validate'] && isset($global['pieform_' . $this->name])) {
         if ($this->data['submit']) {
             $this->submitted = true;
             // If the hidden value the JS code inserts into the form is
             // present, then the form was submitted by JS
             if (!empty($global['pieform_jssubmission'])) {
                 $this->submitted_by_js = true;
             }
             // If the form was submitted via the dropzone
             if (!empty($global['dropzone'])) {
                 $this->submitted_by_dropzone = true;
             }
             // Check if the form has been cancelled
             if ($this->data['iscancellable']) {
                 foreach ($global as $key => $value) {
                     if (substr($key, 0, 7) == 'cancel_') {
                         // Check for and call the cancel function handler, if defined
                         $function = $this->name . '_' . $key;
                         if (function_exists($function)) {
                             $function($this);
                         }
                         // Redirect the user to where they should go, if the cancel handler didn't already
                         $element = $this->get_element(substr($key, 7));
                         if (!isset($element['goto'])) {
                             throw new PieformException('Cancel element "' . $element['name'] . '" has no page to go to');
                         }
                         if ($this->submitted_by_js) {
                             $this->json_reply(PIEFORM_CANCEL, array('location' => $element['goto']), false);
                         }
                         header('HTTP/1.1 303 See Other');
                         header('Location:' . $element['goto']);
                         exit;
                     }
                 }
             }
         }
         // Get the values that were submitted
         $values = $this->get_submitted_values();
         // Perform general validation first
         $this->validate($values);
         // Submit the form if things went OK
         if ($this->data['submit'] && !$this->has_errors()) {
             $submitted = false;
             foreach ($this->elementrefs as $name => $element) {
                 if (!empty($element['submitelement']) && isset($global[$element['name']])) {
                     if (!is_array($this->data['successcallback'])) {
                         $function = "{$this->data['successcallback']}_{$name}";
                         if (function_exists($function)) {
                             $function($this, $values);
                             log_debug('button-submit form ' . $function . ' should provide a redirect.');
                             return;
                         }
                     }
                 }
             }
             $function = $this->data['successcallback'];
             if (!$submitted && is_callable($function)) {
                 // Call the user defined function for processing a submit
                 // This function should really redirect/exit after it has
                 // finished processing the form.
                 call_user_func_array($function, array($this, $values));
                 if ($this->data['dieaftersubmit']) {
                     if ($this->data['jsform']) {
                         $message = 'Your ' . $this->name . '_submit function should use $form->reply to send a response, which should redirect or exit when it is done. Perhaps you want to make your reply callback do this?';
                     } else {
                         $message = 'Your ' . $this->name . '_submit function should redirect or exit when it is done';
                     }
                     throw new PieformException($message);
                 } else {
                     // Successful submission, and the user doesn't care about replying, so...
                     return;
                 }
             } else {
                 if (!$submitted) {
                     throw new PieformException('No function registered to handle form submission for form "' . $this->name . '"');
                 }
             }
         }
         // If we get here, the form was submitted but failed validation
         // Auto focus the first element with an error if required
         if ($this->data['autofocus'] !== false) {
             $this->auto_focus_first_error();
         }
         // Call the user-defined PHP error function, if it exists
         $function = $this->data['errorcallback'];
         if (is_callable($function)) {
             call_user_func_array($function, array($this));
         }
         // If the form has been submitted by javascript, return json
         if ($this->submitted_by_js) {
             // TODO: get error messages in a 'third person' type form to
             // use here maybe? Would have to work for non js forms too. See
             // the TODO file
             //$errors = $this->get_errors();
             //$json = array();
             //foreach ($errors as $element) {
             //    $json[$element['name']] = $element['error'];
             //}
             $message = $this->get_property('jserrormessage');
             $this->json_reply(PIEFORM_ERR, array('message' => $message));
         } else {
             global $SESSION;
             $SESSION->add_error_msg($this->get_property('errormessage'));
         }
     }
 }
示例#2
0
文件: pieform.php 项目: Br3nda/mahara
 /**
  * Sets the attributes of the form according to the passed data, performing
  * validation on the way. If the form is submitted, this checks and processes
  * the form.
  *
  * @param array $data The form description hash
  */
 public function __construct($data)
 {
     /*{{{*/
     $GLOBALS['_PIEFORM_REGISTRY'][] = $this;
     if (!isset($data['name']) || !preg_match('/^[a-z_][a-z0-9_]*$/', $data['name'])) {
         throw new PieformException('Forms must have a name, and that name must be valid (validity test: could you give a PHP function the name?)');
     }
     $this->name = $data['name'];
     // If the form has global configuration, get it now
     if (function_exists('pieform_configure')) {
         $formconfig = pieform_configure();
         $defaultelements = isset($formconfig['elements']) ? $formconfig['elements'] : array();
         foreach ($defaultelements as $name => $element) {
             if (!isset($data['elements'][$name])) {
                 $data['elements'][$name] = $element;
             }
         }
     } else {
         $formconfig = array();
     }
     // Assign defaults for the form
     $this->data = array_merge(self::get_pieform_defaults(), $formconfig, $data);
     // Set the method - only get/post allowed
     $this->data['method'] = strtolower($this->data['method']);
     if ($this->data['method'] != 'post') {
         $this->data['method'] = 'get';
     }
     // Make sure that the javascript callbacks are valid
     if ($this->data['jsform']) {
         $this->validate_js_callbacks();
     }
     if (!$this->data['validatecallback']) {
         $this->data['validatecallback'] = $this->name . '_validate';
     }
     if (!$this->data['successcallback']) {
         $this->data['successcallback'] = $this->name . '_submit';
     }
     if (!$this->data['replycallback']) {
         $this->data['replycallback'] = $this->name . '_reply';
     }
     $this->data['configdirs'] = array_map(create_function('$a', 'return substr($a, -1) == "/" ? substr($a, 0, -1) : $a;'), (array) $this->data['configdirs']);
     if (empty($this->data['tabindex'])) {
         $this->data['tabindex'] = self::$formtabindex++;
     }
     if (!is_array($this->data['elements']) || count($this->data['elements']) == 0) {
         throw new PieformException('Forms must have a list of elements');
     }
     // Get references to all the elements in the form, excluding fieldsets
     foreach ($this->data['elements'] as $name => &$element) {
         // The name can be in the element itself. This is compatibility for
         // the perl version
         if (isset($element['name'])) {
             $name = $element['name'];
         }
         if (isset($element['type']) && $element['type'] == 'fieldset') {
             // Load the fieldset plugin as we know this form has one now
             $this->include_plugin('element', 'fieldset');
             if ($this->get_property('template')) {
                 self::info("Your form '{$this->name}' has a fieldset, but is using a template. Fieldsets make no sense when using templates");
             }
             foreach ($element['elements'] as $subname => &$subelement) {
                 if (isset($subelement['name'])) {
                     $subname = $subelement['name'];
                 }
                 $this->elementrefs[$subname] =& $subelement;
                 $subelement['name'] = $subname;
             }
             unset($subelement);
         } else {
             $this->elementrefs[$name] =& $element;
         }
         $element['name'] = $name;
     }
     unset($element);
     // Check that all elements have names compliant to PHP's variable naming policy
     // (otherwise things get messy later)
     foreach (array_keys($this->elementrefs) as $name) {
         if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $name)) {
             throw new PieformException('Element "' . $name . '" is badly named (validity test: could you give a PHP variable the name?)');
         }
     }
     // Remove elements to ignore
     // This can't be done using $this->elementrefs, because you can't unset
     // an entry in there and have it unset the entry in $this->data['elements']
     foreach ($this->data['elements'] as $name => $element) {
         if (isset($element['type']) && $element['type'] == 'fieldset') {
             foreach ($element['elements'] as $subname => $subelement) {
                 if (!empty($subelement['ignore'])) {
                     unset($this->data['elements'][$name]['elements'][$subname]);
                     unset($this->elementrefs[$subname]);
                 }
             }
         } else {
             if (!empty($element['ignore'])) {
                 unset($this->data['elements'][$name]);
                 unset($this->elementrefs[$name]);
             }
         }
     }
     // Set some attributes for all elements
     $autofocusadded = false;
     foreach ($this->elementrefs as $name => &$element) {
         if (count($element) == 0) {
             throw new PieformException('An element in form "' . $this->name . '" has no data (' . $name . ')');
         }
         if (!isset($element['type']) || $element['type'] == 'markup') {
             $element['type'] = 'markup';
             if (!isset($element['value'])) {
                 throw new PieformException('The markup element "' . $name . '" has no value');
             }
         } else {
             // Now we know what type the element is, we can load the plugin for it
             $this->include_plugin('element', $element['type']);
             // All elements should have at least the title key set
             if (!isset($element['title'])) {
                 $element['title'] = '';
             }
             // This function can be defined by the application using Pieforms,
             // and applies to all elements of this type
             $function = 'pieform_element_' . $element['type'] . '_configure';
             if (function_exists($function)) {
                 $element = $function($element);
             }
             // vvv --------------------------------------------------- vvv
             // After this point Pieforms can set or override attributes
             // without fear that the developer will be able to change them.
             // This function is defined by the plugin itself, to set
             // fields on the element that need to be set but should not
             // be set by the application
             $function = 'pieform_element_' . $element['type'] . '_set_attributes';
             if (function_exists($function)) {
                 $element = $function($element);
             }
             // Force the form method to post if there is a file to upload
             if (!empty($element['needsmultipart'])) {
                 $this->fileupload = true;
                 if ($this->data['method'] == 'get') {
                     $this->data['method'] = 'post';
                     self::info("Your form '{$this->name}' had the method 'get' and also a file element - it has been converted to 'post'");
                 }
             }
             // Add the autofocus flag to the element if required
             if (!$autofocusadded && $this->data['autofocus'] === true && empty($element['nofocus'])) {
                 $element['autofocus'] = true;
                 $autofocusadded = true;
             } elseif (!empty($this->data['autofocus']) && $this->data['autofocus'] !== true && $name == $this->data['autofocus']) {
                 $element['autofocus'] = true;
             }
             // All elements inherit the form tabindex
             $element['tabindex'] = $this->data['tabindex'];
         }
     }
     unset($element);
     // Check if the form was submitted, and if so, validate and process it
     $global = $this->data['method'] == 'get' ? $_GET : $_POST;
     if ($this->data['validate'] && isset($global['pieform_' . $this->name])) {
         if ($this->data['submit']) {
             $this->submitted = true;
             // If the hidden value the JS code inserts into the form is
             // present, then the form was submitted by JS
             if (!empty($global['pieform_jssubmission'])) {
                 $this->submitted_by_js = true;
             }
             // Check if the form has been cancelled
             if ($this->data['iscancellable']) {
                 foreach ($global as $key => $value) {
                     if (substr($key, 0, 7) == 'cancel_') {
                         // Check for and call the cancel function handler, if defined
                         $function = $this->name . '_' . $key;
                         if (function_exists($function)) {
                             $function($this);
                         }
                         // Redirect the user to where they should go, if the cancel handler didn't already
                         $element = $this->get_element(substr($key, 7));
                         if (!isset($element['goto'])) {
                             throw new PieformException('Cancel element "' . $element['name'] . '" has no page to go to');
                         }
                         if ($this->submitted_by_js) {
                             $this->json_reply(PIEFORM_CANCEL, array('location' => $element['goto']), false);
                         }
                         header('HTTP/1.1 303 See Other');
                         header('Location:' . $element['goto']);
                         exit;
                     }
                 }
             }
         }
         // Get the values that were submitted
         $values = $this->get_submitted_values();
         // Perform general validation first
         $this->validate($values);
         // Submit the form if things went OK
         if ($this->data['submit'] && !$this->has_errors()) {
             $submitted = false;
             foreach ($this->elementrefs as $element) {
                 if (!empty($element['submitelement']) && isset($global[$element['name']])) {
                     $function = "{$this->data['successcallback']}_{$element['name']}";
                     if (function_exists($function)) {
                         $function($this, $values);
                         $submitted = true;
                         break;
                     }
                 }
             }
             $function = $this->data['successcallback'];
             if (!$submitted && is_callable($function)) {
                 // Call the user defined function for processing a submit
                 // This function should really redirect/exit after it has
                 // finished processing the form.
                 call_user_func_array($function, array($this, $values));
                 if ($this->data['dieaftersubmit']) {
                     if ($this->data['jsform']) {
                         $message = 'Your ' . $this->name . '_submit function should use $form->reply to send a response, which should redirect or exit when it is done. Perhaps you want to make your reply callback do this?';
                     } else {
                         $message = 'Your ' . $this->name . '_submit function should redirect or exit when it is done';
                     }
                     throw new PieformException($message);
                 } else {
                     // Successful submission, and the user doesn't care about replying, so...
                     return;
                 }
             } else {
                 if (!$submitted) {
                     throw new PieformException('No function registered to handle form submission for form "' . $this->name . '"');
                 }
             }
         }
         // If we get here, the form was submitted but failed validation
         // Auto focus the first element with an error if required
         if ($this->data['autofocus'] !== false) {
             $this->auto_focus_first_error();
         }
         // Call the user-defined PHP error function, if it exists
         $function = $this->data['errorcallback'];
         if (is_callable($function)) {
             call_user_func_array($function, array($this));
         }
         // If the form has been submitted by javascript, return json
         if ($this->submitted_by_js) {
             // TODO: get error messages in a 'third person' type form to
             // use here maybe? Would have to work for non js forms too. See
             // the TODO file
             //$errors = $this->get_errors();
             //$json = array();
             //foreach ($errors as $element) {
             //    $json[$element['name']] = $element['error'];
             //}
             $message = $this->get_property('jserrormessage');
             $this->json_reply(PIEFORM_ERR, array('message' => $message));
         }
     }
 }