<?php

	// Exit if accessed directly
	if ( ! defined( 'ABSPATH' ) ) {
		exit;
	}

	// Helper functions for developers

	/**
	 * Gets an array of all registered field types
	 *
	 * @return Array $field_types
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_config_get_field_types() {

		return WS_Form_Config::get_field_types_flat();
	}

	/**
	 * Gets a form object by ID
	 *
	 * @param int                     $form_id                The form ID
	 * @param bool                    $get_meta               If set to true then form, group, section and field meta data will be included
	 * @param bool                    $get_groups             If set to true, then group, section and field data will be included
	 *
	 * @return Object WS_Form_Form
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_object($form_id, $get_meta = true, $get_groups = true) {

		// Get form ID
		$form_id = absint($form_id);

		// Check form ID
		if($form_id === 0) { throw new Exception('Invalid form ID (wsf_form_get_object)'); }

		// Create new form instance
		$ws_form_form = new WS_Form_Form();

		// Set form ID
		$ws_form_form->id = $form_id;

		// Return for object
		return $ws_form_form->db_read($get_meta, $get_groups, false, false, true);
	}

	/**
	 * Get the label of a form by ID
	 *
	 * @param int                     $form_id                The form ID
	 *
	 * @return string|bool         Form label or false if not found
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_label_by_id($form_id) {

		// Get form ID
		$form_id = absint($form_id);

		// Check form ID
		if($form_id === 0) { throw new Exception('Invalid form ID (wsf_form_get_label_by_id)'); }

		// Create new form instance
		$ws_form_form = new WS_Form_Form();

		// Set form ID
		$ws_form_form->id = $form_id;

		// Return form label
		try {

			return $ws_form_form->db_get_label();

		} catch ( Exception $e ) {

			return false;
		}
	}

	/**
	 * Get the submission count of a form by ID
	 *
	 * @param int                     $form_id                The form ID
	 *
	 * @return int     Total submissions
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_count_submit_by_id($form_id) {

		// Get form ID
		$form_id = absint($form_id);

		// Check form ID
		if($form_id === 0) { throw new Exception('Invalid form ID (wsf_form_get_count_submit_by_id)'); }

		// Create new form stat instance
		$ws_form_form_stat = new WS_Form_Form_Stat();

		// Set form ID
		$ws_form_form_stat->form_id = $form_id;

		// Get counts
		$counts = $ws_form_form_stat->db_get_counts();

		// Return submit count 
		return absint( $counts['count_submit'] );
	}

	/**
	 * Get an array containing all forms
	 *
	 * @param bool                    $published              If true, only return published forms
	 * @param string                  $order_by               id, label, date_added or date_modified (Defaults to label)
	 * @param string                  $order                  ASC or DESC (Defaults to ASC)
	 *
	 * @return Array WS_Form_Form
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_all($published = false, $order_by = 'label', $order = 'ASC') {

		// Initiate instance of Form class
		$ws_form_form = new WS_Form_Form();

		// Read all forms
		return $ws_form_form->get_all($published, $order_by, $order);
	}

	/**
	 * Get an array containing all forms in a simple key value format
	 *
	 * @param bool                    $published              If true, only return published forms
	 * @param string                  $order_by               id, label, date_added or date_modified (Defaults to label)
	 * @param string                  $order                  ASC or DESC (Defaults to ASC)
	 * @param string                  $include_ids            If true ' (ID: xxx)' appended to values
	 *
	 * @return Array WS_Form_Form  id => label
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_all_key_value($published = false, $order_by = 'label', $order = 'ASC', $include_ids = true) {

		// Initiate instance of Form class
		$ws_form_form = new WS_Form_Form();

		// Read all forms
		return $ws_form_form->get_all_key_value($published, $order_by, $order, $include_ids);
	}

	/**
	 * Get a group (tab) object by ID
	 * Tabs in WS Form are known as groups in core
	 *
	 * @param WS_Form_Form            $form_object            The form object
	 * @param int                     $group_id               The group ID
	 *
	 * @return Object WS_Form_Group
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_group_get_object($form_object, $group_id = false) {

		// Check form object
		wsf_form_check($form_object);

		// Get group ID
		$group_id = absint($group_id);

		// Check group ID
		if($group_id === 0) { throw new Exception('Invalid group ID'); }

		// Return group object
		return wsf_group_get_objects($form_object, $group_id);
	}

	/**
	 * Get groups (tabs) or a group (tab) by ID from a form object
	 *
	 * @param WS_Form_Form            $form_object            The form object
	 * @param int                     $group_id               The group ID
	 *
	 * @return Array WS_Form_Group          If no tab specified
	 * @return WS_Form_Group                If group_id specified
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_group_get_objects($form_object, $group_id = false) {

		// Check form object
		wsf_form_check($form_object);

		// Get group ID
		$group_id = absint($group_id);

		// Check group ID
		if($group_id === 0) { throw new Exception('Invalid group ID'); }

		// Set up return groups array
		$return_groups = [];

		$groups = $form_object->groups;

		foreach($groups as $group) {

			if($group_id !== false) {

				if($group->id == $group_id) { return $group; }

			} else {

				$return_groups[$group->id] = $group;
			}
		}

		if(count($return_groups) === 0) {

			throw new Exception('Group not found');
		}

		return $return_groups;
	}

	/**
	 * Get a section object by ID
	 *
	 * @param WS_Form_Form            $form_object            The form object
	 * @param int                     $section_id             The section ID
	 *
	 * @return Object WS_Form_Section
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_section_get_object($form_object, $section_id = false) {

		// Check form object
		wsf_form_check($form_object);

		// Get section ID
		$section_id = absint($section_id);

		// Check section ID
		if($section_id === 0) { throw new Exception('Invalid section ID'); }

		// Return section object
		return wsf_section_get_objects($form_object, $section_id);
	}

	/**
	 * Get sections or a section by ID from a form object
	 *
	 * @param WS_Form_Form            $form_object            The form object
	 * @param int                     $section_id             The section ID
	 *
	 * @return Array WS_Form_Section        If no section specified
	 * @return WS_Form_Section              If section_id specified
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_section_get_objects($form_object, $section_id = false) {

		// Check form object
		wsf_form_check($form_object);

		$return_sections = [];

		$groups = $form_object->groups;

		foreach($groups as $group) {

			$sections = $group->sections;

			foreach($sections as $section) {

				if($section_id !== false) {

					if($section->id == $section_id) { return $section; }

				} else {

					$return_sections[$section->id] = $section;
				}
			}
		}

		if(count($return_sections) === 0) {

			throw new Exception('Section not found');
		}

		return $return_sections;
	}

	/**
	 * Get a field object by ID
	 *
	 * @param WS_Form_Form            $form_object            The form object
	 * @param int                     $field_id               The field ID
	 *
	 * @return Object WS_Form_Field
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_field_get_object($form_object, $field_id = false) {

		// Check form object
		wsf_form_check($form_object);

		// Get field ID
		$field_id = absint($field_id);

		// Check field ID
		if($field_id === 0) { throw new Exception('Invalid field ID'); }

		// Return field object
		return wsf_field_get_objects($form_object, $field_id);
	}

	/**
	 * Get fields by label
	 *
	 * @param WS_Form_Form            $form_object            The form object
	 * @param string                  $field_label            The field label
	 *
	 * @return Array WS_Form_Field
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_field_get_objects_by_label($form_object, $field_label = false) {

		// Check form object
		wsf_form_check($form_object);

		// Check field label
		if(empty($field_label)) { throw new Exception('Invalid field label'); }

		// Return fields matching label
		return wsf_field_get_objects($form_object, false, $field_label);
	}

	/**
	 * Get field objects by meta
	 *
	 * @param WS_Form_Form            $form_object            The form object
	 * @param string                  $field_meta_key         The field meta key
	 * @param string                  $field_meta_value       The field meta value
	 *
	 * @return Array WS_Form_Field
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_field_get_objects_by_meta($form_object, $field_meta_key = false, $field_meta_value = false) {

		// Check form object
		wsf_form_check($form_object);

		// Check field meta key
		if(empty($field_meta_key)) { throw new Exception('Invalid field meta key'); }

		// Check field meta value
		if(empty($field_meta_value)) { throw new Exception('Invalid field meta value'); }

		// Return fields matching meta
		return wsf_field_get_objects($form_object, false, false, $field_meta_key, $field_meta_value);
	}

	/**
	 * Get field objects by class (Field wrapper or field class name)
	 *
	 * @param WS_Form_Form            $form_object            The form object
	 * @param string                  $class_name             The field class name or wrapper class name
	 *
	 * @return Array WS_Form_Field
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_field_get_objects_by_class($form_object, $class_name = false) {

		// Check form object
		wsf_form_check($form_object);

		// Check class name
		if(empty($class_name)) { throw new Exception('Invalid class name'); }

		try {

			// Return fields matching class name by field class
			$fields = wsf_field_get_objects($form_object, false, false, 'class_field', $class_name);

		} catch (Exception $e) {

			// Return fields matching class name by field wrapper class
			$fields = wsf_field_get_objects($form_object, false, false, 'class_field_wrapper', $class_name);
		}

		return $fields;
	}

	/**
	 * Get fields or a field by ID from a form object
	 *
	 * @param WS_Form_Form            $form_object            The form object
	 * @param int                     $field_id               Filter by field ID
	 * @param string                  $field_label            Filter by field label
	 * @param string                  $field_meta_key         Filter by field meta key
	 * @param string                  $field_meta_value       Filter by field meta value
	 *
	 * @return Array WS_Form_Field          If no field ID specified
	 * @return WS_Form_Field                If field ID specified
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_field_get_objects($form_object, $field_id = false, $field_label = false, $field_meta_key = false, $field_meta_value = false) {

		// Check form object
		wsf_form_check($form_object);

		$return_fields = [];

		$groups = $form_object->groups;

		// Process groups
		foreach($groups as $group) {

			$sections = $group->sections;

			// Process sections
			foreach($sections as $section) {

				$fields = $section->fields;

				// Process fields
				foreach($fields as $field) {

					// Check by field ID
					if($field_id !== false) {

						if($field->id == $field_id) { return $field; }

					// Check by field label
					} else if($field_label !== false) {

						if($field->label == $field_label) {

							$return_fields[] = $field;
						}

					// Check by field meta key
					} else if($field_meta_key !== false) {

						// Get meta value
						$meta_value = wsf_helper_get_object_meta_value($field, $field_meta_key);

						switch($field_meta_key) {

							// Space separated values support
							case 'class_field' :
							case 'class_field_wrapper' :

								if(in_array($field_meta_value, explode(' ', $meta_value))) {

									$return_fields[] = $field;
								}

								break;

							// Direct check
							default :

								if($field_meta_value == $meta_value) {

									$return_fields[] = $field;
								}
						}

					} else {

						$return_fields[$field->id] = $field;
					}
				}
			}
		}

		if(count($return_fields) === 0) {

			throw new Exception(

				empty($field_id) ?
				'Field not found' :
				sprintf('Field ID %u not found', absint($field_id))
			);
		}

		return $return_fields;
	}

	/**
	 * Clear all rows in a field data grid
	 *
	 * @param WS_Form_Field           $field_object           The field object
	 * @param int                     $group_id               The group ID
	 *
	 * @return WS_Form_Field          The modified field
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_field_rows_clear($field_object, $group_id = false) {

		// Initiate instance of Data Grid class
		$ws_form_data_grid = new WS_Form_Data_Grid($field_object);

		// Clear rows
		return $ws_form_data_grid->rows_clear($group_id);
	}

	/**
	 * Add a row to a field data grid
	 *
	 * @param WS_Form_Field           $field_object           The field object
	 * @param WS_Form_Data_Grid_Row   $data_grid_row_object   The row object
	 *
	 * @return WS_Form_Field              The modified field
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_field_row_add($field_object, $data_grid_row_object = false) {

		// Initiate instance of Data Grid class
		$ws_form_data_grid = new WS_Form_Data_Grid($field_object);

		// Add row
		return $ws_form_data_grid->row_add($data_grid_row_object);
	}

	/**
	 * Get a field data grid
	 *
	 * @param WS_Form_Field           $field_object           The field object
	 *
	 * @return WS_Form_Data_Grid     The data grid
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_field_get_data_grid($field_object) {

		// Initiate instance of Data Grid class
		$ws_form_data_grid = new WS_Form_Data_Grid($field_object);

		// Return data grid
		return $ws_form_data_grid->data_grid;
	}

	/**
	 * Get a field data grid group
	 *
	 * @param WS_Form_Data_Grid       $data_grid_object       The data grid object
	 * @param int                     $group_id               Group ID to filter by
	 *
	 * @return WS_Form_Data_Grid_Group  The data_grid group object
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_data_grid_get_groups($data_grid_object, $group_id = false) {

		// Initiate instance of Data Grid class
		$ws_form_data_grid = new WS_Form_Data_Grid();

		// Return groups
		return $ws_form_data_grid->get_groups($data_grid_object, $group_id);
	}

	/**
	 * Get next data grid group row ID
	 *
	 * @param WS_Form_Data_Grid_Group $group_object           The data grid group object
	 *
	 * @return WS_Form_Data_Grid_Group The data grid group object
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_group_row_id_next($group_object) {

		// Initiate instance of Data Grid class
		$ws_form_data_grid = new WS_Form_Data_Grid();

		// Return next row ID
		return $ws_form_data_grid->get_group_row_id_next($group_object);
	}

	/**
	 * Check form object is valid
	 * Throws an exception if form object is invalid
	 *
	 * @param WS_Form_Form            $form_object            Form object
	 *
	 * @return None
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_check($form_object) {

		// Initiate instance of Form class
		$ws_form_form = new WS_Form_Form();

		if(!$ws_form_form->is_valid($form_object)) {

			throw new Exception('Invalid form object');
		}
	}

	/**
	 * Check group object is valid
	 * Throws an exception if group object is invalid
	 *
	 * @param WS_Form_Group           $group_object           Group object
	 *
	 * @return None
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_group_check($group_object) {

		// Initiate instance of Group class
		$ws_form_group = new WS_Form_Group();

		if(!$ws_form_group->is_valid($group_object)) {

			throw new Exception('Invalid group object');
		}
	}

	/**
	 * Check section object is valid
	 * Throws an exception if section object is invalid
	 *
	 * @param WS_Form_Section         $section_object         Section object
	 *
	 * @return None
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_section_check($section_object) {

		// Initiate instance of Section class
		$ws_form_section = new WS_Form_Section();

		if(!$ws_form_section->is_valid($section_object)) {

			throw new Exception('Invalid section object');
		}
	}

	/**
	 * Check a field object
	 * Throws an exception if field object is invalid
	 *
	 * @param WS_Form_Field           $field_object           The field object
	 *
	 * @return None
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_field_check($field_object) {

		// Initiate instance of Field class
		$ws_form_field = new WS_Form_Field();

		if(!$ws_form_field->is_valid($field_object)) {

			throw new Exception('Invalid field');
		}
	}

	/**
	 * Check a submit object
	 * Throws an exception if submit object is invalid
	 *
	 * @param WS_Form_Submit          $submit_object          The submit object
	 *
	 * @return None
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_submit_check($submit_object) {

		// Initiate instance of Submit class
		$ws_form_submit = new WS_Form_Submit();

		if(!$ws_form_submit->is_valid($submit_object)) {

			throw new Exception('Invalid submit');
		}
	}

	/**
	 * Check a data grid object
	 *
	 * @param WS_Form_Data_Grid       $data_grid              The data grid object
	 *
	 * @return bool    true
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_data_grid_check($data_grid) {

		// Initiate instance of Data Grid class
		$ws_form_data_grid = new WS_Form_Data_Grid();

		if(!$ws_form_data_grid->is_valid($data_grid)) {

			throw new Exception('Invalid data grid');
		}

		return true;
	}

	/**
	 * Check a data grid group object
	 *
	 * @param WS_Form_Data_Grid_Group $data_grid_group_object The data grid group object
	 *
	 * @return bool    true
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_data_grid_group_check($data_grid_group_object) {

		// Initiate instance of Data Grid class
		$ws_form_data_grid = new WS_Form_Data_Grid();

		if(!$ws_form_data_grid->is_valid_group($data_grid_group_object)) {

			throw new Exception('Invalid data grid group');
		}

		return true;
	}

	/**
	 * Check a data grid row object
	 *
	 * @param WS_Form_Data_Grid_Row   $data_grid_row_object   The data grid row object
	 *
	 * @return bool    true
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_data_grid_row_check($data_grid_row_object) {

		// Initiate instance of Data Grid class
		$ws_form_data_grid = new WS_Form_Data_Grid();

		if(!$ws_form_data_grid->is_valid_row($data_grid_row_object)) {

			throw new Exception('Invalid data grid row');
		}

		return true;
	}

	/**
	 * Get a submit object by ID
	 *
	 * @param int                     $submit_id              The submit ID
	 *
	 * @return WS_Form_Submit
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_submit_get_object($submit_id) {

		// Initiate instance of Submit class
		$ws_form_submit = new WS_Form_Submit();

		$ws_form_submit->id = $submit_id;

		$ws_form_submit->db_read(true, true, true);

		return $ws_form_submit;
	}

	/**
	 * Get a submit object by hash
	 *
	 * @param string                  $submit_hash            The submit hash
	 *
	 * @return WS_Form_Submit
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_submit_get_by_hash($submit_hash) {

		// Initiate instance of Submit class
		$ws_form_submit = new WS_Form_Submit();

		$ws_form_submit->hash = $submit_hash;

		$ws_form_submit->db_read_by_hash(true, true, false, true);

		return $ws_form_submit;
	}

	/**
	 * Get a submit value by meta key
	 *
	 * @param WS_Form_Submit          $submit_object          The submit object
	 * @param string                  $meta_key               The meta key (e.g. 'field_123')
	 * @param string                  $default_value          Default value if not found
	 * @param bool                    $protected              Set to true for accessing protected data (e.g. password)
	 *
	 * @return mixed Field value (string or object depending on field type)
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_submit_get_value($submit_object, $meta_key, $default_value = '', $protected = false) {

		wsf_submit_check($submit_object);

		return WS_Form_Action::get_submit_value(

			$submit_object,
			$meta_key,
			$default_value,
			$protected
		);
	}

	/**
	 * Get a repeatable submit value by meta key
	 *
	 * @param WS_Form_Submit          $submit_object          The submit object
	 * @param string                  $meta_key               The meta key (e.g. 'field_123')
	 * @param string                  $default_value          Default value if not found
	 * @param bool                    $protected              Set to true for accessing protected data (e.g. password)
	 *
	 * @return mixed List of values or a single value (string or object depending on field type)
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_submit_get_value_repeatable($submit_object, $meta_key, $default_value = '', $protected = false) {

		wsf_submit_check($submit_object);

		return WS_Form_Action::get_submit_value_repeatable(

			$submit_object,
			$meta_key,
			$default_value,
			$protected
		);
	}

	/**
	 * Get a submit field value by class (Field wrapper or field class name)
	 *
	 * @param WS_Form_Form            $form_object            The form object
	 * @param WS_Form_Submit          $submit_object          The submit object
	 * @param string                  $class_name             The field class name or wrapper class name
	 * @param string                  $default_value          Default value if not found
	 * @param bool                    $protected              Set to true for accessing protected data (e.g. password)
	 *
	 * @return mixed $field_value or Array of field values if more than one field found
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_submit_get_value_by_field_class($form_object, $submit_object, $class_name = false, $default_value = '', $protected = false) {

		// Check form object
		wsf_form_check($form_object);

		// Check submit object
		wsf_submit_check($submit_object);

		// Check class name
		if(empty($class_name)) { throw new Exception('Invalid class name'); }

		// Get fields
		$fields = wsf_field_get_objects_by_class($form_object, $class_name);

		// Check fields
		if(
			!is_array($fields) ||
			!isset($fields[0]) ||
			!property_exists($fields[0], 'id')
		) {
			throw new Exception('Invalid return from wsf_field_get_objects_by_class');
		}

		// Build return array
		$return_array = array();

		foreach($fields as $field) {

			// Check field
			if(
				!is_object($field) ||
				!property_exists($field, 'id')
			) {

				throw new Exception('Invalid field');
			}

			// Get submit value
			$return_array[] = wsf_submit_get_value($submit_object, sprintf('field_%u', $field->id), $default_value, $protected);
		}

		// Return value
		return (count($return_array) == 1) ? $return_array[0] : $return_array;
	}

	/**
	 * Get a repeatable submit field value by class (Field wrapper or field class name)
	 *
	 * @param WS_Form_Form            $form_object            The form object
	 * @param WS_Form_Submit          $submit_object          The submit object
	 * @param string                  $class_name             The field class name or wrapper class name
	 * @param string                  $default_value          Default value if not found
	 * @param bool                    $protected              Set to true for accessing protected data (e.g. password)
	 *
	 * @return mixed $field_value
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_submit_get_value_repeatable_by_field_class($form_object, $submit_object, $class_name = false, $default_value = '', $protected = false) {

		// Check form object
		wsf_form_check($form_object);

		// Check submit object
		wsf_submit_check($submit_object);

		// Check class name
		if(empty($class_name)) { throw new Exception('Invalid class name'); }

		// Get fields
		$fields = wsf_field_get_objects_by_class($form_object, $class_name);

		// Check fields
		if(
			!is_array($fields) ||
			!isset($fields[0]) ||
			!property_exists($fields[0], 'id')
		) {
			throw new Exception('Invalid return from wsf_field_get_objects_by_class');
		}

		// Build return array
		$return_array = array();

		foreach($fields as $field) {

			// Check field
			if(
				!is_object($field) ||
				!property_exists($field, 'id')
			) {

				throw new Exception('Invalid field');
			}

			// Get submit value
			$return_array[] = wsf_submit_get_value_repeatable($submit_object, sprintf('field_%u', $field->id), $default_value, $protected);
		}

		// Return value
		return (count($return_array) == 1) ? $return_array[0] : $return_array;
	}

	/**
	 * Set a submit meta value by meta key
	 *
	 * @param WS_Form_Submit          $submit_object          The submit object
	 * @param string                  $meta_key               The meta key
	 * @param mixed                   $meta_value             Meta value to set
	 *
	 * @return bool    true
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_submit_set_value($submit_object, $meta_key = '', $meta_value = '') {

		// Check submit object
		wsf_submit_check($submit_object);

		// Get submit ID
		$submit_id = absint(isset($submit_object->id) ? $submit_object->id : false);

		// Check submit ID
		if($submit_id === 0) { throw new Exception('Invalid submit ID'); }

		// Check meta key
		if(empty($meta_key)) { throw new Exception('Invalid meta key'); }

		// Keys exactly `field_{numeric_id}` must use the submit-meta field branch (field_id column, wsf_submit_meta_update)
		if(strpos($meta_key, WS_FORM_FIELD_PREFIX) === 0) {

			$field_id_suffix = substr($meta_key, strlen(WS_FORM_FIELD_PREFIX));
			if(
				($field_id_suffix !== '') &&
				ctype_digit((string) $field_id_suffix) &&
				(!is_array($meta_value) || !array_key_exists('id', $meta_value))
			) {

				$meta_value = array(

					'id' => absint($field_id_suffix),
					'value' => $meta_value
				);
			}
		}

		// Build meta data
		$meta = array(

			$meta_key => $meta_value
		);

		// Update submit meta data
		$ws_form_submit_meta = new WS_Form_Submit_Meta();
		$ws_form_submit_meta->parent_id = $submit_id;
		return $ws_form_submit_meta->db_update_from_array($meta);
	}

	/**
	 * Set a submit meta value by field ID
	 *
	 * @param WS_Form_Submit          $submit_object          The submit object
	 * @param int                     $field_id               The field ID
	 * @param mixed                   $meta_value             Meta value to set
	 *
	 * @return bool    true
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_submit_set_value_by_field_id($submit_object, $field_id, $meta_value = '') {

		// Check field ID
		$field_id = absint($field_id);
		if($field_id === 0) { throw new Exception('Invalid field ID'); }

		return wsf_submit_set_value($submit_object, WS_FORM_FIELD_PREFIX . $field_id, $meta_value);
	}

	/**
	 * Get a property value from an object
	 *
	 * @param object                  $object                 Object to read from
	 * @param string                  $property               Property name
	 * @param mixed                   $default_value          Value if the property is not found
	 *
	 * @return mixed
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_helper_get_object_property($object, $property, $default_value = '') {

		return WS_Form_Common::get_object_property($object, $property, $default_value);
	}

	/**
	 * Get a meta value from an object
	 *
	 * @param object                  $object                 Object to read from
	 * @param string|false            $meta_key               Meta key, or false
	 * @param mixed                   $default_value          Value if the meta key is not found
	 *
	 * @return mixed
	 */
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_helper_get_object_meta_value($object, $meta_key = false, $default_value = '') {

		return WS_Form_Common::get_object_meta_value($object, $meta_key, $default_value);
	}


	// Legacy wrappers
	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_form_object($form_id, $get_meta = true, $get_groups = true) {

		return wsf_form_get_object($form_id, $get_meta, $get_groups);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_form_label($form_id) {

		return wsf_form_get_label_by_id($form_id);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_tabs($form_object, $group_id = false) {

		return wsf_group_get_objects($form_object, $group_id);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_tab($form_object, $group_id = false) {

		return wsf_group_get_object($form_object, $group_id);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_group($form_object, $group_id = false) {

		return wsf_group_get_object($form_object, $group_id);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_section($form_object, $section_id = false) {

		return wsf_section_get_object($form_object, $section_id);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_field($form_object, $field_id = false) {

		return wsf_field_get_object($form_object, $field_id);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_fields_by_label($form_object, $field_label = false) {

		return wsf_field_get_objects_by_label($form_object, $field_label);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_fields_by_meta($form_object, $field_meta_key = false, $field_meta_value = false) {

		return wsf_field_get_objects_by_meta($form_object, $field_meta_key, $field_meta_value);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_groups($form_object, $group_id = false) {

		return wsf_group_get_objects($form_object, $group_id);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_sections($form_object, $section_id = false) {

		return wsf_section_get_objects($form_object, $section_id);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_fields($form_object, $field_id = false) {

		return wsf_field_get_objects($form_object, $field_id);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_submit_set_field_value($submit_object, $field_id, $meta_value = '') {

		return wsf_submit_set_value_by_field_id($submit_object, $field_id, $meta_value);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_form_get_count_submit($form_id) {

		return wsf_form_get_count_submit_by_id($form_id);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_datagrid_check($data_grid) {

		return wsf_data_grid_check($data_grid);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_datagrid_group_check($data_grid_group_object) {

		return wsf_data_grid_group_check($data_grid_group_object);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_datagrid_get_groups($data_grid_object, $group_id = false) {

		return wsf_data_grid_get_groups($data_grid_object, $group_id);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_datagrid_row_check($data_grid_row_object) {

		return wsf_data_grid_row_check($data_grid_row_object);
	}

	// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- All functions prefixed with wsf_
	function wsf_field_get_datagrid($field_object) {

		return wsf_field_get_data_grid($field_object);
	}
