Fieldmanager
  • Package
  • Function
  • Tree
  • Todo

Packages

  • Fieldmanager
    • Context
    • Datasource
    • Field
    • Util
  • None

Functions

  • Fieldmanager_Util_Validation
  • fm_page_form_did_save
  • fm_the_page_form
  • fm_url_to_post_id
  1 <?php
  2 /**
  3  * Framework for user-facing data validation.
  4  *
  5  * @package Fieldmanager_Util
  6  */
  7 
  8 class Fieldmanager_Util_Validation {
  9 
 10     /**
 11      * Instance of this class
 12      *
 13      * @var Fieldmanager_Util_Validation
 14      * @access private
 15      */
 16     private static $instance;
 17 
 18     /**
 19      * @var array
 20      * @access private
 21      * Array of Fieldmanager fields that require validation
 22      */
 23     private $fields = array();
 24 
 25     /**
 26      * @var array
 27      * @access private
 28      * Rules for each of the fields to be validated
 29      */
 30     private $rules = array();
 31 
 32     /**
 33      * @var array
 34      * @access private
 35      * Messages to override the default and display when a field is invalid
 36      */
 37     private $messages = array();
 38 
 39     /**
 40      * @var string
 41      * @access private
 42      * The form ID that requires validation
 43      */
 44     private $form_id;
 45 
 46     /**
 47      * @var string
 48      * @access private
 49      * The context this form appears in
 50      */
 51     private $context;
 52 
 53     /**
 54      * @var array
 55      * @access private
 56      * The allowed validation rules
 57      */
 58     private $valid_rules = array( 'required', 'remote', 'minlength', 'maxlength', 'rangelength', 'min', 'max', 'range', 'email', 'url', 'date', 'dateISO', 'number', 'digits', 'creditcard', 'equalTo' );
 59 
 60     /**
 61      * Singleton helper
 62      *
 63      * @param array $form_ids
 64      * @param string $context
 65      * @return object The singleton instance
 66      */
 67     public static function instance( $form_id, $context ) {
 68         // The current form ID and context are used to generate a global variable name to store the instance.
 69         // This is necessary so that it persists until all Fieldmanager fields with validation for the current form and context are added.
 70         $global_id = $form_id . '_' . $context;
 71 
 72         // Check if this global is set.
 73         // If yes, return it.
 74         // If not, initialize the singleton instance, set it to the global and return it.
 75         if ( isset( $GLOBALS[$global_id] ) ) {
 76             return $GLOBALS[$global_id];
 77         } else {
 78             self::$instance = new Fieldmanager_Util_Validation;
 79             self::$instance->setup( $form_id, $context );
 80             $GLOBALS[$global_id] = self::$instance;
 81             return self::$instance;
 82         }
 83     }
 84 
 85     /**
 86      * Add scripts, initialize variables and add action hooks
 87      *
 88      * @access private
 89      * @param string $form_id
 90      * @param string $context
 91      * @return Fieldmanager_Util_Validation
 92      */
 93     private function setup( $form_id, $context ) {
 94         // Set class variables
 95         $this->form_id = $form_id;
 96         $this->context = $context;
 97 
 98         // Add the appropriate action hook to finalize and output validation JS
 99         // Also determine where the jQuery validation script needs to be added
100         if ( $context == 'page' ) {
101             // Currently only the page context outputs to the frontend
102             $action = 'wp_footer';
103             $admin = false;
104         } else {
105             // All other contexts are used only on the admin side
106             $action = 'admin_footer';
107             $admin = true;
108         }
109 
110         // Hook the action
111         add_action( $action, array( &$this, 'add_validation' ) );
112     }
113 
114     /**
115      * Check if a field has validation enabled and if so add it
116      *
117      * @access public
118      * @param Fieldmanager_Field $fm
119      */
120     public function add_field( &$fm ) {
121         // If this field is a Fieldmanager_Group, iterate over the children
122         if ( get_class( $fm ) == "Fieldmanager_Group" ) {
123             foreach ( $fm->children as $child ) {
124                 $this->add_field( $child );
125             }
126         }
127 
128         // Check if this field has validation enabled. If not, return.
129         if ( empty( $fm->validation_rules ) )
130             return;
131 
132         // Determine if the rules are a string or an array and ensure they are valid.
133         // Also aggregate any messages that were set for the rules, ignoring any messages that don't match a rule.
134         $messages = "";
135         if ( ! is_array( $fm->validation_rules ) ) {
136             // If a string, the only acceptable value is "required".
137             if ( ! is_string( $fm->validation_rules ) || $fm->validation_rules != 'required' )
138                 $fm->_invalid_definition( sprintf( __( 'The validation rule "%s" does not exist.', 'wordpress-fieldmanager' ), $fm->validation_rules ) );
139 
140             // Convert the value to an array since we standardize the Javascript output on this format
141             $fm->validation_rules = array( 'required' => true );
142 
143             // In this instance, messages must either be a string or empty. If valid and defined, store this.
144             if ( ! empty( $fm->validation_messages ) && is_string( $fm->validation_messages ) )
145                 $messages['required'] = $fm->validation_messages;
146 
147         } else {
148             // Verify each rule defined in the array is valid and also check for any messages that were defined for each.
149             foreach ( $fm->validation_rules as $validation_key => $validation_rule ) {
150                 if ( ! in_array( $validation_key, $this->valid_rules ) ) {
151                     // This is not a rule available in jQuery validation
152                     $fm->_invalid_definition( sprintf( __( 'The validation rule "%s" does not exist.', 'wordpress-fieldmanager' ), $validation_key ) );
153                 } else {
154                     // This rule is valid so check for any messages
155                     if ( isset( $fm->validation_messages[$validation_key] ) )
156                         $messages[$validation_key] = $fm->validation_messages[$validation_key];
157                 }
158             }
159         }
160 
161         // If this is the term context and the field is required, modify the original element to have the required property.
162         // This is necessary because it is the only way validation is supported on the term add form.
163         // Other validation methods won't work and will just fail gracefully.
164         if ( $this->context == 'term' && isset( $fm->validation_rules['required'] ) && $fm->validation_rules['required'] )
165             $fm->required = true;
166 
167         // If we have reached this point, there were no errors so store the field and the corresponding rules and messages
168         $name = $fm->get_form_name();
169         $this->fields[] = $name;
170         $this->rules[$name] = $fm->validation_rules;
171         $this->messages[$name] = $messages;
172     }
173 
174     /**
175      * Output the Javascript required for validation, if any fields require it
176      *
177      * @access public
178      */
179     public function add_validation() {
180         // Iterate through the fields and output the required Javascript
181         $rules = array();
182         $messages = array();
183         foreach ( $this->fields as $field ) {
184             // Add the rule string to an array
185             $rule = $this->value_to_js( $field, $this->rules );
186             if ( ! empty( $rule ) ) {
187                 $rules[] = $rule;
188 
189                 // Add the message to an array, if it exists
190                 $message = $this->value_to_js( $field, $this->messages );
191                 if ( ! empty( $message ) )
192                     $messages[] = $message;
193             }
194         }
195 
196         // Create final rule string
197         if ( ! empty( $rules ) ) {
198             $rules_js = $this->array_to_js( $rules, "rules" );
199             $messages_js = $this->array_to_js( $messages, "messages" );
200 
201             // Add a comma and newline if messages is not empty
202             if ( ! empty( $messages_js ) ) {
203                 $rules_js .= ",\n";
204             }
205 
206             // Fields that should always be ignored
207             $ignore[] = ".fm-autocomplete";
208             $ignore[] = "input[type='button']";
209             $ignore[] = ":hidden";
210 
211             // Certain fields need to be ignored depending on the context
212             switch ( $this->context ) {
213                 case "post":
214                     $ignore[] = "#active_post_lock";
215                     break;
216             }
217 
218             // Add JS for fields to ignore
219             $ignore_js = implode( ", ", $ignore );
220 
221             // Add the Fieldmanager validation script and CSS
222             // This is not done via the normal enqueue process since there is no way to know at that point if any fields will require validation
223             // Doing this here avoids loading JS/CSS for validation if not in use
224             echo "<link rel='stylesheet' id='fm-validation-css' href='" . fieldmanager_get_baseurl() . "css/fieldmanager-validation.css' />\n";
225             echo "<script type='text/javascript' src='" . fieldmanager_get_baseurl() . "js/validation/fieldmanager-validation.js?ver=0.3'></script>\n";
226 
227             // Add the jQuery validation script
228             echo "<script type='text/javascript' src='" . fieldmanager_get_baseurl() . "js/validation/jquery.validate.min.js'></script>\n";
229 
230             // Add the ignore, rules and messages to final validate method with form ID, wrap in script tags and output
231             echo sprintf(
232                 "\t<script type='text/javascript'>\n\t\t( function( $ ) {\n\t\t$( document ).ready( function () {\n\t\t\tvar validator = $( '#%s' ).validate( {\n\t\t\t\tinvalidHandler: function( event, validator ) { fm_validation.invalidHandler( event, validator ); },\n\t\t\t\tsubmitHandler: function( form ) { fm_validation.submitHandler( form ); },\n\t\t\t\terrorClass: \"fm-js-error\",\n\t\t\t\tignore: \"%s\",\n%s%s\n\t\t\t} );\n\t\t} );\n\t\t} )( jQuery );\n\t</script>\n",
233                 esc_attr( $this->form_id ),
234                 $ignore_js,
235                 $rules_js,
236                 $messages_js
237             );
238         }
239     }
240 
241     /**
242      * Converts a single rule or message value into Javascript
243      *
244      * @access private
245      * @param string $field
246      * @param string $data
247      * @return string The Javascript output or an empty string if no data was provided
248      */
249     private function value_to_js( $field, $data ) {
250         // Check the array for the corresponding value. If it doesn't exist, return an empty string.
251         if ( empty( $data[$field] ) )
252             return "";
253 
254         // Format the field name
255         $name = $this->quote_field_name( $field );
256 
257         // Iterate over the values convert them into a single string
258         $values = array();
259         foreach ( $data[$field] as $k => $v ) {
260             $values[] = sprintf(
261                 "\t\t\t\t\t\t%s: %s",
262                 esc_js( $k ),
263                 $this->format_value( $v )
264             );
265         }
266 
267         // Convert the array to a string
268         $value = sprintf(
269             "{\n%s\n\t\t\t\t\t}",
270             implode( ",\n", $values )
271         );
272 
273         // Combine the name and value and return it
274         return sprintf(
275             "\t\t\t\t\t%s: %s",
276             $name,
277             $value
278         );
279     }
280 
281     /**
282      * Converts an array of values into Javascript
283      *
284      * @access private
285      * @param array $data
286      * @param string $label
287      * @return string The Javascript output or an empty string if no data was provided
288      */
289     private function array_to_js( $data, $label ) {
290         return sprintf(
291             "\t\t\t\t%s: {\n%s\n\t\t\t\t}",
292             esc_js( $label ),
293             implode( ",\n", $data )
294         );
295     }
296 
297     /**
298      * Converts a PHP value to the required format for Javascript
299      *
300      * @access private
301      * @param string $value
302      * @return string The formatted value
303      */
304     private function format_value( $value ) {
305         // Determine the data type and return the value formatted appropriately
306         if ( is_bool( $value ) ) {
307             // Convert the value to a string
308             return ( $value ) ? "true" : "false";
309         } else if ( is_numeric( $value ) ) {
310             // Return as-is
311             return $value;
312         } else {
313             // For any other type (should only be a string) escape for JS output
314             return '"' . esc_js( $value ) . '"';
315         }
316     }
317 
318     /**
319      * Determine if the field name needs to be quoted for Javascript output
320      *
321      * @access private
322      * @param string $field
323      * @return string The field name with quotes added if necessary
324      */
325     private function quote_field_name( $field ) {
326         // Check if the field name is alphanumeric (underscores and dashes are allowed)
327         if ( ctype_alnum( str_replace( array( '_', '-'), '', $field ) ) )
328             return $field;
329         else
330             return '"' . esc_js( $field ) . '"';
331     }
332 }
333 
334 /**
335  * Singleton helper for Fieldmanager_Util_Validation
336  *
337  * @param string $form_id
338  * @param string $context
339  * @return object
340  */
341 function Fieldmanager_Util_Validation( $form_id, $context ) {
342     return Fieldmanager_Util_Validation::instance( $form_id, $context );
343 }
344 
Fieldmanager API documentation generated by ApiGen 2.8.0