Advertisement
Guest User

Webform Class

a guest
Jul 6th, 2011
181
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 22.30 KB | None | 0 0
  1. <?php
  2.  
  3. define('F_AUTO', 1);
  4.  
  5. class webform
  6. {
  7.  
  8.     /*
  9.     File:       webform.class.php
  10.     Developer:  Shane Harter
  11.     Created:    Apr 25, 2008
  12.     Version:    1.0
  13.    
  14.     Description:
  15.     ---------------------------------------------------------------------------------------
  16.     This class represents an HTML input form. Ties together a data_model object and an event model LCP file
  17.  
  18.     Instructions:
  19.     ---------------------------------------------------------------------------------------
  20.     Create a webform object, extending this class, for each data_model object in your app.
  21.     Sometimes multiple forms per data object.
  22.  
  23.     For each field on your form, create a function modeled after input_generic() below. ALL
  24.     of these methods must begin with "input_" -- that naming convention is used when assembling the form.
  25.  
  26.     Important to use the same field naming as is used on your data_model (which should be the same as the DB)
  27.  
  28.     Dependencies:
  29.     ----------------------------------------------------------------------------------------
  30.     input_field     v1.0
  31.     error           v1.0
  32.     fatal_error     v1.0
  33.     warning         v1.0
  34.     libValidate     v1.0
  35.     libArray        v1.0
  36.     data_model      v1.0
  37.     */
  38.    
  39.     var $form_name;                         // String - The form Name (Also used as the id)
  40.     var $form_title;                        // String - The form Title
  41.     var $form_attrib;                       // String - Optional Advanced Form Attributes - Should be valid HTML, will be dropped into FORM tag. (OnClick="" for example)  
  42.     var $form_method    = 'post';           // Enum[get|post] - The method attribute of the form tag
  43.     var $form_action    = F_AUTO;           // String - The action of the form tag (What URL to get/post to). Auto Detect will attempt to determine the URL based on where this obj is instantiated
  44.    
  45.     // Configuration Options
  46.     var $show_errors    = true;             // Boolean - Enable built-in error reporting (for validation errors only). Turn off if you want to display these errors elsewhere on the page
  47.     var $persist        = false;            // Boolean - Never remove the webform_key from the $_SESSION collection. This will mean the user could use back button, but it also means they could accidently double-submit
  48.    
  49.     /**
  50.      * @var data_model */  
  51.     var $m_model;                           // Object - data_model - should extend data_model abstract class
  52.     var $m_posted       = false;            // Boolean - has this form been posted yet? set in load_from_post()
  53.     var $m_fields       = array();          // Array of input_field Objects - Associative array using field_name as key, input_field object as value       
  54.     var $m_webform_key;                     // String - The webform key for this instance. Only exists if render() has been called.
  55.    
  56.     function webform($form_name, $data_object)
  57.     {
  58.         // The form_name is used as the DOM id among other things..
  59.         $this->form_name = htmlentities($form_name);
  60.  
  61.         // The $data_object object is essential. If the form you're creating is not backed by a DB table
  62.         // then you must still create a basic data_model with properties added for each field, with a save() method.
  63.         if (false == is_subclass_of(&$data_object, 'data_object'))
  64.         {
  65.             $e = new fatal_error('An error has occured while processing this form');
  66.             $e->comment = 'A valid data_object is required.';
  67.             $e->class = 'webform';
  68.             $e->commit();
  69.            
  70.             return false;
  71.         }
  72.        
  73.         $this->m_model =& $data_object;
  74.        
  75.         // Now call fields() to add the actual fields..
  76.         $this->fields();
  77.     }
  78.  
  79.     // **********************************************************************************************************************************************
  80.     // * Public Methods
  81.     // ----------------------------------------------------------------------------------------------------------------------------------------------
  82.    
  83.     function fields()
  84.     {
  85.         // Abstract.
  86.         // Use to create field definitions and corresponding add_field() calls
  87.        
  88.         /*
  89.          * Template:
  90.         $o = new input_field('Name', $this);
  91.         $o->set_label('Label');
  92.         $o->set_field_as_input();
  93.         $this->add_field($o);
  94.         */
  95.     }
  96.    
  97.     function layout()
  98.     {
  99.         // This method will auto-layout the input_field objects in an HTML Table. Think of it as everything INSIDE the <form>..</form> tags, which are auto-added.
  100.         // Returns HTML
  101.        
  102.         /*
  103.        
  104.         Webform Tags Available in Layout:
  105.         -------------------------------------
  106.         %form_name%         - The name of the form
  107.         %form_title%        - The title of the form
  108.         %error_block%       - An error_message block (only applicable if $show_errors=true)
  109.        
  110.         You will also need to add placeholders for EACH field you've added to the form.
  111.         The actual tag name should match the fieldname that was passed into the 'new input_field' statement. If desired, you also need to add tags for their corresponding
  112.         labels. The tags will look like this:
  113.  
  114.         %fieldname%
  115.         %label_fieldname%
  116.  
  117.         If you have an input_field named, for example, userName, the tags will be:
  118.  
  119.         %username%
  120.         %label_username%
  121.  
  122.        
  123.         CSS Styling Options    
  124.         -------------------------------------
  125.         Nearly every webform element, the table, tr, td, form tags, etc, have a CSS class specified. This will allow us to have a default style in the global css file,  
  126.         and it allows you to overload those styles at the application-level.
  127.  
  128.         A select few elements also are assigned a unique id tag that lets you define CSS stylization for those specific DOM id's.
  129.  
  130.         Tag                 Class                   ID
  131.         ----------------------------------------------------------------
  132.         <form>              webform_form            webform_%formname%
  133.         <div> (form_title)  webform_title           webform_title_%formname%
  134.         <table>             webform_table           webform_table_%formname%
  135.         <tr>                webform_tr
  136.         <td>                webform_td
  137.         <label>             webform_label
  138.         <input>             webform_input
  139.         <select>            webform_select
  140.        
  141.         It's important, though, to remember that input_field's and the webform itself are very customizable. If you define your own input_field by passing the
  142.         actual HTML (opposed to using one of the set_field_as_xxx() methods), the class='' and/or id='' does not get parsed-in. You must include it if you want it there.
  143.         */
  144.        
  145.         $this->m_sort_fields();
  146.        
  147.         $fields = '';
  148.         foreach ($this->m_fields as $field)
  149.         {
  150.             $fields .= "
  151.                         <tr class='webform_tr'>
  152.                             <td width='25%' class='webform_td'>%label_{$field->m_field_name}%</td>
  153.                             <td width='75%' class='webform_td'>%{$field->m_field_name}%</td>
  154.                         </tr>";
  155.         }
  156.        
  157.         // Now as long as we have some $layout, wrap w/ table tags
  158.         if (empty($fields))
  159.         {
  160.             return '';
  161.         }
  162.        
  163.         return "<br/>
  164.                 <div class='webform_title' id='webform_title_%form_name%'>%form_title%</div>
  165.                 <br/>
  166.                 %error_block%
  167.                 <table class='webform_table' id='webform_table_%form_name%'>
  168.                 {$fields}
  169.                 </table>"; 
  170.     }  
  171.            
  172.     function add_field(&$field)
  173.     {
  174.         // This method will add the supplied $field (of type input_field or subclass_of input_field) to the $fields collection
  175.         // This object must be bound to a data_model object before fields can be added.
  176.         // Returns Boolean, Raises fatal error if field could not be added.
  177.        
  178.         if (false == is_a($field, 'input_field'))
  179.         {
  180.             $e = new fatal_error('An error has occured while processing this form');
  181.             $e->comment = "add_field() faild. Supplied field could not be validated as an instance or subclass-of the input_field type";
  182.             $e->class = 'webform';
  183.             $e->commit();
  184.            
  185.             return false;
  186.         }
  187.        
  188.         if (false == is_a($this->m_model, 'data_object'))
  189.         {
  190.             $e = new fatal_error('An error has occured while processing this form');
  191.             $e->comment = "add_field() failed. The webform must be bound to a valid data_model object before fields can be added.";
  192.             $e->class = 'webform';
  193.             $e->commit();
  194.            
  195.             return false;
  196.         }
  197.        
  198.         // This should always be an array, but just in case, do the check here
  199.         if (false == is_array($this->m_fields))
  200.         {
  201.             $this->m_fields = array();
  202.         }
  203.        
  204.         // If the user hasn't ordered the field, order it based on the order in which it was added.
  205.         if (false == isset($field->order))
  206.         {
  207.             $field->order = count($this->m_fields)+1;
  208.         }
  209.        
  210.         $this->m_fields[$field->m_field_name] = $field;
  211.     }
  212.        
  213.     function render()
  214.     {
  215.         // This method will request the layout(), add <form> tags, m_parse_all() the placeholders, and return the result
  216.         // Returns HTML or throws a fatal error
  217.        
  218.         if ($this->m_validate())
  219.         {
  220.             // Build the Form Action
  221.             $this->m_autodetect_action();
  222.            
  223.             // Build the layout w/ placeholders for the input fields.
  224.             $form = $this->layout();
  225.            
  226.             // Create the <form> and <input type='hidden'> tags needed to give the form something to do with itself
  227.             $form = $this->m_build_tags($form);
  228.            
  229.             // Parse all the webform tags.
  230.             return $this->m_parse_webform($form);
  231.         }
  232.     }
  233.    
  234.     function save_model()
  235.     {
  236.         // This method will check the $posted flag and, if set, will attempt to save the model (to the DB, not from disaster. Disaster is already assured)
  237.         // Only Applicable AFTER post
  238.         // returns boolean
  239.        
  240.         if ($this->m_posted)
  241.         {
  242.             if (false == is_a($this->m_model, 'data_object'))
  243.             {
  244.                 $e = new fatal_error('An Error has Occurred While Processing this Form');
  245.                 $e->comment = "save_model() failed. The bound data model in m_model does not exist or is invalid. ";
  246.                 $e->class = 'webform';
  247.                 $e->commit();
  248.  
  249.                 return false;
  250.             }
  251.            
  252.             if ($this->m_model->save())
  253.             {
  254.                 if (false == $this->persist)
  255.                 {
  256.                     $this->destroy();
  257.                 }
  258.                
  259.                 return true;   
  260.             }
  261.            
  262.             return false;
  263.         }
  264.        
  265.         $w = new warning('save_model() called on a webform object that does not appear to have been posted.');
  266.         $w->comment = '[m_posted: ' . libFormat::literal($this->m_posted) . ']';
  267.         $w->commit();
  268.        
  269.         return false;
  270.     }
  271.    
  272.     function destroy()
  273.     {
  274.         // This method removes the webform AND bound model from the $_SESSION collection.
  275.         // Session key is created during render() so there's no need to call this if render() hasn't been called.
  276.         // This is called implicitly during the save_model() process UNLESS $pesist=true. If $persist is set, you must destroy() explicitely.
  277.         // Returns Bool
  278.        
  279.         if ($this->m_webform_key)
  280.         {
  281.             if (isset($_SESSION['webform_' . $this->m_webform_key]))
  282.             {
  283.                 unset($_SESSION['webform_' . $this->m_webform_key]);
  284.             }
  285.            
  286.             if (is_a($this->m_model, 'data_object'))
  287.             {
  288.                 $this->m_model->destroy();
  289.             }
  290.         }
  291.        
  292.         return isset($_SESSION['webform_' . $this->m_webform_key]);
  293.     }
  294.        
  295.    
  296.     // **********************************************************************************************************************************************
  297.     // * STATIC (AKA CLASS) METHODS
  298.     // ----------------------------------------------------------------------------------------------------------------------------------------------  
  299.    
  300.     function load_from_post()
  301.     {
  302.         // This method should be called STATICALLY on the webform:: class.
  303.         // It will look for a webform_key in the $_POST collection and, if exists, load that object from the $_SESSION collection.
  304.         // It will also re-load the data_model in the same fashion (calling its own load_from_post constructor)
  305.        
  306.         if (isset($_POST['webform_key']))
  307.         {
  308.             $o = webform::m_unserialize($_POST['webform_key']); /* @var o webform */
  309.  
  310.             if (is_a($o, 'webform'))
  311.             {
  312.                
  313.                 // Before we do anything, make sure that the model was successfully reinstantiated
  314.                 if (false == is_a($o->m_model, 'data_object'))
  315.                 {
  316.                     $e = new fatal_error('An Error has Occurred While Processing this Form');
  317.                     $e->comment = "The webform object has been loaded, but the data_model is invalid. [Model: {$o->m_model}]";
  318.                     $e->class = 'webform';
  319.                     $e->commit();
  320.                    
  321.                     return false;
  322.                 }
  323.            
  324.                 // First load the m_model...
  325.                 // This will load values from the $_POST into the bound variable in the m_model object
  326.                 $o->m_model->import_posted_data(array_keys($o->m_fields));
  327.                
  328.                 // Second load the input_field array...
  329.                 // We need to re-build the bindings between the input_field and the data_model. They were broken during the serialize/unserialize process
  330.                 // If you look under a debugger, the $m_bound_field method of each input_field object is now NULL.
  331.                 // Also, we're using the $key to implicitely avoid a copy of m_fields being made in the background, which is the usual MO of foreach() in PHP4
  332.                 foreach(array_keys($o->m_fields) as $key)
  333.                 {
  334.                     $o->m_fields[$key]->m_bind_field();
  335.                 }
  336.                
  337.                 // Finally, set the m_posted flag
  338.                 $o->m_posted = true;
  339.                
  340.                 return $o;
  341.             }
  342.            
  343.             $e = new fatal_error('An Error has Occurred While Processing this Form');
  344.             $e->comment = "Invalid webform_key provided. Webform could not be loaded from session collection.";
  345.             $e->class = 'webform';
  346.             $e->commit();
  347.         }
  348.        
  349.         return false;
  350.     }
  351.    
  352.     // **********************************************************************************************************************************************
  353.     // * Private Methods
  354.     // ----------------------------------------------------------------------------------------------------------------------------------------------      
  355.    
  356.     function m_validate()
  357.     {
  358.         // This method will validate that everything is in-place before render()ing the form. Fatal errors are issued liberally in the webform package: this is an important
  359.         // part of the MPG framework and while a half-baked form is a problem, a much bigger problem is a form that LOOKS like it worked, but didn't.
  360.         // Returns Boolean.
  361.  
  362.         $errs = array();
  363.         if (false == is_a($this->m_model, 'data_object')) $errs[] = 'A valid data_object object is required';
  364.         if (false == is_array($this->m_fields) || 0 == count($this->m_fields)) $errs[] = 'At least one field must be added before a form can be rendered';
  365.         if (empty($this->form_name)) $errs[] = 'A valid, html_safe form_name is required';
  366.         if (empty($this->form_action)) $errs[] = 'A valid, html_safe form_action is required';
  367.         if (false == libValidate::is_in(strtolower($this->form_method), 'get', 'post')) $errs[] = "The form_method is invalid. Get or Post expected. [method:{$this->form_method}]";
  368.        
  369.         foreach ($errs as $err)
  370.         {
  371.             $e = new fatal_error('An Error has Occurred While Processing this Form');
  372.             $e->comment = $err;
  373.             $e->class = 'webform';
  374.             $e->commit();
  375.         }
  376.        
  377.         // If an error object was raised return false.
  378.         return (false == is_object($e));
  379.     }
  380.        
  381.     function m_build_tags($layout)
  382.     {
  383.         // This methods accepts the indiscriminate HTML and wraps the needed <form> and <input tyep='hidden'> tags around it.
  384.         // Returns HTML.
  385.        
  386.         return "
  387.                 <form id='webform_%form_name%' name='webform_%form_name%' class='webform_form' method='%form_method%' action='%form_action%' %form_attrib%>
  388.                     <input type='hidden' name='webform_key' value='%webform_key%'>
  389.                     {$layout}
  390.                 </form>
  391.                 "; 
  392.     }
  393.    
  394.     function m_build_error_block()
  395.     {
  396.         // If $show_errors is set, this will instantiate an error_message object to display validation errors related to this form.
  397.         // Right now, it's simply putting the errors in block-form at the top of the form (or wherever the %error_block placeholder is)
  398.         // Returns HTML error block or empty string if $show_errors is false (or if there are no errors to show)
  399.        
  400.         if ($this->show_errors)
  401.         {
  402.             $err_msg = new error_message();
  403.             $err_msg->class = validation_error::get_classname();
  404.            
  405.             return $err_msg->return_message();
  406.         }
  407.        
  408.         return '';
  409.     }
  410.    
  411.     function m_parse_webform($html)
  412.     {
  413.         // This method will parse any field-placeholders in the layout with their corresponding fields from their respective input_field objects.
  414.         // Returns HTML
  415.        
  416.         // Parse the simple form details
  417.         $html = str_replace('%form_name%',      $this->form_name,               $html);
  418.         $html = str_replace('%form_title%',     $this->form_title,              $html);
  419.         $html = str_replace('%form_method%',    $this->form_method,             $html);
  420.         $html = str_replace('%form_action%',    $this->form_action,             $html);
  421.         $html = str_replace('%form_attrib%',    $this->form_attrib,             $html);
  422.         $html = str_replace('%error_block%',    $this->m_build_error_block(),   $html);
  423.        
  424.         // Parse the input fields themselves, then parse them into the form
  425.         // Uses array_keys to prevent php from making a copy of the m_fields array which is the MO of the foreach loop.
  426.  
  427.         foreach (array_keys($this->m_fields) as $field_name)
  428.         {          
  429.             // Now integrate into the $html
  430.             $html = str_replace("%{$field_name}%",       $this->m_fields[$field_name]->render_field(), $html);
  431.             $html = str_replace("%label_{$field_name}%", $this->m_fields[$field_name]->render_label(), $html);
  432.         }
  433.        
  434.         // Finally, serialize the object and add the webform key in a hidden field.
  435.         // Needs to be the last step b/c any changes made to the object after m_serialize() will not be reflected when the webform is unserialized after its posted.
  436.         if ($this->m_serialize())
  437.         {
  438.             $html = str_replace('%webform_key%', $this->m_webform_key, $html);     
  439.             return $html;
  440.         }
  441.        
  442.         // If we're still here, the serialize failed
  443.         $e = new fatal_error('An Error has Occurred While Processing this Form');
  444.         $e->comment = "m_serialize() failed. Unkown reason. [webform_key: {$this->m_webform_key}]";
  445.         $e->class = 'syserr';
  446.         $e->commit();
  447.        
  448.         return false;
  449.     }
  450.    
  451.     function m_sort_fields()
  452.     {
  453.         // This function will sort the $this->fields array based on the $order of each field object.
  454.         // Not applicable if you use the layout() method is overwritten in the subform (since it uses place-holders for each field by name)
  455.         // returns Boolean, sorts the $this->m_fields array
  456.        
  457.         if (count($this->m_fields) > 0)
  458.         {
  459.             // This will extract the 'order' field from each object in m_fields into the $order array.
  460.             // The keys will match up between $order and $m_fields
  461.             $order = libArray::extract_nested_key($this->m_fields, 'order');
  462.            
  463.             if (is_array($order) && count($order) == count($this->m_fields))
  464.             {
  465.                 // Placeholder for the sorted $m_fields array
  466.                 $new_m_fields = array();
  467.                
  468.                 // Sort the $order array
  469.                 // Then loop thru the keys of that array (which match the keys in $m_fields still) and populate $new_m_fields;
  470.                 @asort($order);
  471.                 foreach (array_keys($order) as $key)
  472.                 {
  473.                     $new_m_fields[$key] = $this->m_fields[$key];
  474.                 }
  475.                
  476.                 // Double Check...
  477.                 if (count($this->m_fields) == count($new_m_fields))
  478.                 {
  479.                     $this->m_fields = $new_m_fields;
  480.                     return true;
  481.                 }
  482.                
  483.                 $e = new warning('An error occured while processing this form');
  484.                 $e->comment = "m_sort_fields() failed. Count of m_fields does not match count of new_m_fields after sort() operation. Fields have not been sorted.";
  485.                 $e->commit();
  486.                
  487.                 return false;
  488.             }
  489.            
  490.             $e = new warning('An error occured while processing this form');
  491.             $e->comment = "m_sort_fields() failed. Count of order array does not match count of m_fields array after extract_nested_key() operation. Fields have not been sorted.";
  492.             $e->commit();          
  493.  
  494.             return false;
  495.         }
  496.        
  497.         $e = new warning('An error occured while processing this form');
  498.         $e->comment = "sort_fields() failed. No fields have been added.";
  499.         $e->commit();
  500.        
  501.         return false;
  502.     }
  503.        
  504.     function m_autodetect_action()
  505.     {
  506.         // This method attempts to autodetect the form action and, if successful, populates the $this->form_action property.
  507.         // It will determine what page the object is instantiated on (should be an LCP file) and it will look for a method on that page: {$form_name}PostRun()
  508.         // If the method does not exist, a fatal error will be thrown.
  509.        
  510.         // If the action is not set to F_AUTO, then just quit and go home
  511.         if ($this->form_action != F_AUTO)
  512.         {
  513.             return;
  514.         }
  515.        
  516.         // Create glob reference to the LC Service Object
  517.         global $service;
  518.        
  519.         // Get the backtrace info for the current calling page
  520.         $backtrace = debug_backtrace();
  521.         $backtrace = $backtrace[1];     // This level will have the URL of the lcp page
  522.        
  523.         if (false == method_exists($service, $this->form_name . 'PostRun'))
  524.         {
  525.             $e = new fatal_error('An error has occured while processing this form');
  526.             $e->comment = "The m_autodetect_action() method failed. The corresponding Post method does not exist. You must create the method, {$this->form_name}PostRun, or manually set the form_action property.";
  527.             $e->class = 'webform';
  528.             $e->commit();
  529.            
  530.             return false;
  531.         }
  532.        
  533.         // If we're here, the method exists, so build the action url.
  534.         $path = $backtrace['file'];
  535.        
  536.         // We now have this: /opt/lampp/logicreate/services/service_name/main.lcp
  537.         $path = explode('/services/', $path);
  538.  
  539.         // We now have an array, in the second position we should have this: service_name/main.lcp
  540.         $path = str_replace('.lcp', '', $path[1]);
  541.        
  542.         // We now have this: service_name/main
  543.         $this->form_action = APP_URL . $path . '/event=' . $this->form_name . 'Post';
  544.        
  545.         // And we now have this: /index.php/service_name/main/event=formIdPostRun
  546.        
  547.         return true;
  548.     }
  549.    
  550.     function m_serialize()
  551.     {
  552.         // This function will serialize $this and store it in the $_SESSION collection. A hash is made of the serialized object and that hash is used as the session key.
  553.         // Returns hashed session key.
  554.    
  555.         // Must be called on an instantiated object, not statically.
  556.         if (is_subclass_of($this, 'webform') == false) return false;
  557.        
  558.         // Serialize the data_model first, then the whole object
  559.         $this->m_model = $this->m_model->serialize();
  560.         $serial = serialize($this);
  561.        
  562.         // Hash
  563.         $hash = md5($serial);
  564.        
  565.         // Save in session memory
  566.         $_SESSION['webform_' . $hash] = $serial;
  567.        
  568.         // Add this key to the $m_webform_key property.
  569.         // Since this object has already been serialized, this won't exist when its unserialized.
  570.         // Rather than re-serialize it, we just pull this key in during the load_from_post process.
  571.         $this->m_webform_key = $hash;
  572.        
  573.         // Double check that the session key saved properly
  574.         return (isset($_SESSION['webform_' . $hash]));
  575.     }
  576.    
  577.     function m_unserialize($hash)
  578.     {
  579.         // This function will return an unserialized version of $text
  580.         // Should be called statically.
  581.         // Used to unserialize a measurable object
  582.  
  583.         // Get has from session memory and compare. Key is the md5'd, serialized text
  584.         if (isset($_SESSION['webform_' . $hash]))
  585.         {
  586.             // Unserialize the webform
  587.             $s = unserialize($_SESSION['webform_' . $hash]); /* @var s webform */
  588.  
  589.             // Unserialize the model -- the hash is stored in place of the object in m_model
  590.             $s->m_model = data_model::unserialize($s->m_model);
  591.  
  592.             // Re-add the webform_key. See notes in m_serialize() if you want to know more about this..
  593.             $s->m_webform_key = $hash;
  594.            
  595.             return $s;
  596.         }
  597.        
  598.         return false;
  599.     }  
  600. }
  601.  
  602. ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement