%PDF- %PDF-
Direktori : /var/www/tif-dev/wp-content/plugins/gravityforms/includes/settings/ |
Current File : //var/www/tif-dev/wp-content/plugins/gravityforms/includes/settings/class-settings.php |
<?php namespace Gravity_Forms\Gravity_Forms\Settings; use GFAPI; use GFCommon; use GF_Fields; use GFForms; use GFFormsModel; use WP_Error; class_exists( '\GFForms' ) or die(); class Settings { /** * Prefix for the name of rendered input fields. * * @since 2.5 * * @var string $field_prefix Input field name prefix. */ protected $input_name_prefix = '_gform_setting'; /** * Capability required for saving settings. * * @since 2.5 * * @var string */ protected $capability; /** * Fields to be rendered. * * @since 2.5 * * @var array $fields Settings fields. */ protected $fields = array(); /** * Flags whether settings display in tabs. * * @since 2.5 * * @var bool */ protected $is_tabbed = false; /** * HTML to be rendered before displaying fields. * * @since 2.5 * * @var callable */ protected $before_fields; /** * HTML to be rendered after displaying fields. * * @since 2.5 * * @var callable */ protected $after_fields; /** * The current form being modified by the settings. * * @since 2.5 * * @var integer */ protected $current_form; /** * Primary Save button to be displayed in header. * * @since 2.5 * * @var array */ protected $save_button; /** * If save postback has been processed. * * @since 2.5 * * @var bool */ private $processed_postback = false; /** * The previous field values, used when processing postback. * * @since 2.5 * * @var array */ private $_previous_values = array(); /** * The current field values. * * @since 2.5 * * @var array */ private $_saved_values = array(); /** * Called when postback values are valid to save submitted settings. * * @since 2.5 * * @var string|callable */ private $_save_callback = ''; /** * Message to be displayed after save callback has been processed. * * @since 2.5 * * @var string */ private $postback_message = ''; /** * Determines validation message should be displayed. * * @since 2.5 * * @var callable */ protected $postback_message_callback; /** * Enqueued scripts to register as no-conflict. * * @since 2.5 * * @var array */ private $no_conflict_scripts = array(); /** * Enqueued styles to register as no-conflict. * * @since 2.5 * * @var array */ private $no_conflict_styles = array(); /** * Initialize Settings instance. * * @since 2.5 * * @param array $args */ public function __construct( $args = array() ) { require_once 'class-fields.php'; // Set fields. if ( rgar( $args, 'fields' ) ) { $this->set_fields( $args['fields'] ); } if ( rgar( $args, 'capability' ) ) { $this->validate_capability( $args['capability'] ); $this->capability = $args['capability']; } if ( rgar( $args, 'before_fields' ) && is_callable( $args['before_fields'] ) ) { $this->before_fields = $args['before_fields']; } if ( rgar( $args, 'after_fields' ) && is_callable( $args['after_fields'] ) ) { $this->after_fields = $args['after_fields']; } if ( rgar( $args, 'save_button' ) && is_array( $args['save_button'] ) ) { $this->add_save_button( $args['save_button'] ); } if ( rgar( $args, 'current_form' ) ) { $this->current_form = (int) $args['current_form']; } if ( rgar( $args, 'input_name_prefix' ) ) { $this->input_name_prefix = (string) sanitize_html_class( $args['input_name_prefix'] ); } if ( rgar( $args, 'initial_values' ) ) { $this->set_values( $args['initial_values'] ); } if ( rgar( $args, 'save_callback' ) ) { $this->set_save_callback( $args['save_callback'] ); } if ( ! rgar( $args, 'save_callback' ) && rgar( $args, 'initial_values' ) && is_string( $args['initial_values'] ) && ! is_serialized( $args['initial_values'] ) ) { $this->set_save_callback( $args['initial_values'] ); } if ( rgar( $args, 'postback_message_callback' ) ) { $this->set_postback_message_callback( $args['postback_message_callback'] ); } // Enqueue registered scripts/styles. add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_scripts' ) ); // Register no-conflict scripts/styles. add_filter( 'gform_noconflict_scripts', array( $this, 'filter_gform_noconflict_scripts' ) ); add_filter( 'gform_noconflict_styles', array( $this, 'filter_gform_noconflict_styles' ) ); } /** * Capabilities must be either strings or arrays - any other object will should throw an exception. * * @since 2.5 * * @param mixed $capability The capability value to validate. */ private function validate_capability( $capability ) { if ( is_array( $capability ) || is_string( $capability ) ) { return; } throw new \InvalidArgumentException( 'Settings page capabilities must be an array or string.' ); } // # SCRIPT ENQUEUEING --------------------------------------------------------------------------------------------- /** * Enqueue registered scripts and styles. * * @since 2.5 */ public function action_admin_enqueue_scripts() { // Enqueue scripts. foreach ( $this->scripts() as $script ) { // If conditions are not fulfilled, do not enqueue. if ( ! $this->can_enqueue_script( rgar( $script, 'enqueue', array() ) ) ) { continue; } // Add to no-conflict scripts array. if ( ! in_array( $script['handle'], $this->no_conflict_scripts ) ) { $this->no_conflict_scripts[] = $script['handle']; } // Enqueue script. wp_enqueue_script( $script['handle'], rgar( $script, 'src', false ), rgar( $script, 'deps', array() ), rgar( $script, 'version', false ), rgar( $script, 'in_footer', false ) ); // Localize script strings. if ( rgar( $script, 'strings' ) ) { wp_localize_script( $script['handle'], $script['handle'] . '_strings', $script['strings'] ); } if ( isset( $script['callback'] ) && is_callable( $script['callback'] ) ) { call_user_func( $script['callback'], $this ); } } // Enqueue styles. foreach ( $this->styles() as $style ) { // If conditions are not fulfilled, do not enqueue. if ( ! $this->can_enqueue_script( rgar( $style, 'enqueue', array() ) ) ) { continue; } // Add to no-conflict styles array. if ( ! in_array( $style['handle'], $this->no_conflict_styles ) ) { $this->no_conflict_styles[] = $style['handle']; } // Enqueue style. wp_enqueue_style( $style['handle'], rgar( $style, 'src', false ), rgar( $style, 'deps', array() ), rgar( $style, 'version', false ), rgar( $style, 'media', 'all' ) ); } } /** * Registered styles to enqueue when displaying settings. * * @since 2.5 * * @return array */ public function styles() { // Define base styles. $styles = array(); // Register field styles. foreach ( $this->get_fields() as $group ) { $styles = array_merge( $styles, $this->get_scripts_for_group( $styles, $group, true ) ); $styles = array_unique( $styles, SORT_REGULAR ); } return $styles; } /** * Registered scripts to enqueue when displaying settings. * * @since 2.5 * * @return array */ public function scripts() { $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min'; // Define base scripts. $scripts = array( array( 'handle' => 'gform_settings_dependencies', 'src' => GFCommon::get_base_url() . "/includes/settings/js/dependencies{$min}.js", 'version' => GFForms::$version, 'enqueue' => array( function() { $dependencies = $this->get_live_dependencies(); return ! empty( $dependencies ); }, ), ), array( 'handle' => 'gform_settings_select_custom', 'src' => GFCommon::get_base_url() . "/includes/settings/js/select-custom{$min}.js", 'version' => GFForms::$version, 'enqueue' => array( function() { return $this->has_field_type( 'select_custom' ); } ), ), array( 'handle' => 'gform_settings_tabs', 'src' => GFCommon::get_base_url() . "/includes/settings/js/tabs{$min}.js", 'version' => GFForms::$version, 'enqueue' => array( function() { return $this->is_tabbed; }, ), ), ); // Register field scripts. foreach ( $this->get_fields() as $group ) { $scripts = array_merge( $scripts, $this->get_scripts_for_group( $scripts, $group ) ); $scripts = array_unique( $scripts, SORT_REGULAR ); } return $scripts; } /** * Get scripts/styles for a group of sections/fields. * * @since 2.5 * * @param array $scripts Array of enqueued scripts. * @param array $group Group of sections/fields. * @param bool $styles Get styles instead of scripts. * * @return array */ private function get_scripts_for_group( $scripts, $group, $styles = false ) { // Get nested key. $nested_key = GFCommon::get_nested_key( $group ); foreach ( rgar( $group, $nested_key, array() ) as $item ) { if ( is_object( $item ) ) { $scripts = array_merge( $scripts, $styles ? $item->styles() : $item->scripts() ); $scripts = array_unique( $scripts, SORT_REGULAR ); } if ( rgar( $item, 'fields', array() ) ) { $scripts = array_merge( $scripts, $this->get_scripts_for_group( $scripts, $item, $styles ) ); $scripts = array_unique( $scripts, SORT_REGULAR ); } } return $scripts; } /** * Determine if a script/style can be enqueued. * * @since 2.5 * * @param array $conditions Required enqueuing conditions. * * @return bool */ private function can_enqueue_script( $conditions ) { // If no conditions are defined, return. if ( empty( $conditions ) ) { return true; } // Get form. $form = $this->get_current_form(); // Loop through conditions and test. foreach ( $conditions as $condition ) { // If condition is a callable, try it. if ( is_callable( $condition ) ) { $callback_response = call_user_func( $condition, $form, false ); if ( $callback_response ) { return true; } } else { $query_matches = isset( $condition['query'] ) ? $this->script_request_condition_matches( $_GET, $condition['query'] ) : true; $post_matches = isset( $condition['post'] ) ? $this->script_request_condition_matches( $_POST, $condition['post'] ) : true; $field_type_matches = isset( $condition['field_types'] ) ? $this->script_field_condition_matches( $condition['field_types'], $form ) : true; if ( $query_matches && $post_matches && $field_type_matches ) { return true; } } } return false; } /** * Test a request condition. * * @since 2.5 * * @param array $request Current request. * @param array $query Condition query. * * @return bool */ private function script_request_condition_matches( $request, $query ) { // Parse condition query. parse_str( $query, $query_array ); foreach ( $query_array as $key => $value ) { switch ( $value ) { case '_notempty_': if ( rgempty( $key, $request ) ) { return false; } break; case '_empty_': if ( ! rgempty( $key, $request ) ) { return false; } break; default: if ( rgar( $request, $key ) != $value ) { return false; } break; } } return true; } /** * Test a field type condition. * * @since 2.5 * * @param string|array $field_types Field types to check for. * @param array $form Form object. * * @return bool */ private function script_field_condition_matches( $field_types, $form ) { // Force field types to array. if ( ! is_array( $field_types ) ) { $field_types = array( $field_types ); } // Get fields for form matching type. $fields = GFAPI::get_fields_by_type( $form, $field_types ); if ( count( $fields ) > 0 ) { foreach ( $fields as $field ) { if ( $field->is_administrative() && ! $field->allowsPrepopulate && ! GFForms::get_page() ) { continue; } return true; } } return false; } /** * Registers enqueued scripts to the no-conflict scripts whitelist. * * @since 2.5 * * @param array $scripts Array of scripts to be whitelisted. * * @return array */ public function filter_gform_noconflict_scripts( $scripts ) { return array_merge( $scripts, $this->no_conflict_scripts ); } /** * Registers enqueued styles to the no-conflict styles whitelist. * * @since 2.5 * * @param array $styles Array of styles to be whitelisted. * * @return array */ public function filter_gform_noconflict_styles( $styles ) { return array_merge( $styles, $this->no_conflict_styles ); } // # RENDER METHODS ------------------------------------------------------------------------------------------------ /** * Render fields. * Handles enqueueing styles, processing postback. * * @since 2.5 * * @param \GFAddOn $addon The Add-On responsible for rendering the settings page. */ public function render() { // Save field values. if ( self::is_save_postback() ) { $this->process_postback(); } if ( $this->postback_message_callback ) { $this->postback_message = call_user_func( $this->postback_message_callback, $this->postback_message ); } // Display validation message. if ( ! empty( $this->postback_message ) ) { // Get field errors. $field_errors = $this->get_field_errors(); printf( '<div class="alert %s" role="alert">%s</div>', empty( $field_errors ) ? 'gforms_note_success' : 'gforms_note_error', $this->postback_message ); } // Get sections. $fields = $this->get_fields(); ?> <form id="gform-settings" class="gform_settings_form" action="" method="post" enctype="multipart/form-data" novalidate> <?php if ( ! empty( $this->before_fields ) && is_callable( $this->before_fields ) ) { echo call_user_func( $this->before_fields ); } // If settings are tabbed, render tab navigation and tab content. if ( $this->is_tabbed ) { $this->render_tab_navigation(); foreach ( $fields as $tab ) { $this->render_tab( $tab ); } } else { // Loop through and render each section. foreach ( $fields as $section ) { $this->render_section( $section ); } } // Get save button. $save = $this->render_save_button(); if ( ! empty( $save ) ) { printf( '<div class="gform-settings-save-container">%s</div>', $save ); } if ( ! empty( $this->after_fields ) && is_callable( $this->after_fields ) ) { echo call_user_func( $this->after_fields ); } wp_nonce_field( 'gform_settings_save', 'gform_settings_save_nonce' ); ?> </form> <?php // Get live dependencies. $live_dependencies = $this->get_live_dependencies(); // Enqueue live dependencies. if ( ! empty( $live_dependencies ) ) { echo '<script>'; foreach ( $live_dependencies as $dependency ) { echo 'new GF_Settings_Dependencies( ' . wp_json_encode( $dependency ) . ' );'; } echo '</script>'; } } /** * Render tab navigation above settings sections. * * @since 2.5 */ public function render_tab_navigation() { // If settings are not tabbed, exit. if ( ! $this->is_tabbed ) { return; } // Get fields, initialize tabs array. $fields = $this->get_fields(); $tabs = array(); // Get tab names, labels. foreach ( $fields as $i => $tab ) { $tabs[] = array( 'name' => $tab['id'], 'label' => rgar( $tab, 'label' ) ? $tab['label'] : rgar( $tab, 'title' ), 'dependency' => rgar( $tab, 'dependency', array() ), ); } // Get active tab. $active_tab = $this->get_active_tab(); echo '<nav class="gform-settings-tabs__navigation" role="tablist">'; foreach ( $tabs as $i => $tab ) { printf( '<a href="#" role="tab" aria-selected="%3$s" id="gform-settings-tab-%2$s" data-tab="%2$s" class="%4$s"%5$s>%1$s</a>', esc_html( $tab['label'] ), esc_attr( $tab['name'] ), $tab['name'] === $active_tab ? 'true' : 'false', $tab['name'] === $active_tab ? 'active' : '', ! $this->is_dependency_met( $tab['dependency'] ) ? ' style="display:none;"' : '' ); } echo '</nav>'; printf( '<input type="hidden" name="gform_settings_tab" value="%s" />', esc_attr( $active_tab ) ); } /** * Render a single tab of fields. * * @since 2.5 * * @param array $tab Tab properties. */ public function render_tab( $tab ) { // Get active tab. $active_tab = $this->get_active_tab(); // Open tab container. printf( '<div class="gform-settings-tabs__container%2$s" role="tabpanel" aria-hidden="%3$s" data-tab="%1$s" aria-labelledby="gform-settings-tab-%1$s">', esc_attr( $tab['id'] ), $tab['id'] === $active_tab ? ' active' : '', $tab['id'] === $active_tab ? 'false' : 'true' ); // Loop through and render each section. foreach ( $tab['sections'] as $section ) { $this->render_section( $section ); } // Close tab container. echo '</div>'; } /** * Render a single section of fields. * * @since 2.5 * * @param array $section Section properties. */ public function render_section( $section ) { // Prepare section classes. $class = array( 'gform-settings-panel' ); // Add defined classes. if ( rgar( $section, 'class' ) ) { $section['class'] = explode( ' ', $section['class'] ); $class = array_merge( $class, $section['class'] ); } if ( rgar( $section, 'title' ) || ( rgar( $section, 'collapsible' ) && rgar( $section, 'id' ) ) ) { $class[] = 'gform-settings-panel--with-title'; } // Add collapsible classes. if ( rgar( $section, 'collapsible' ) && rgar( $section, 'id' ) ) { $class[] = 'gform-settings-panel--collapsible'; // Add collapsed class. if ( self::is_section_collapsed( $section ) ) { $class[] = 'gform-settings-panel--collapsed'; } } // Add card layout class. if ( self::has_card_layout( $section ) ) { $class[] = 'gform-settings-panel--card'; } // If dependency is not met for section, do not render if no live dependency. // Otherwise, set section to hidden. if ( ! $this->is_dependency_met( rgar( $section, 'dependency' ) ) && ! rgars( $section, 'dependency/live' ) ) { return; } else if ( ! $this->is_dependency_met( rgar( $section, 'dependency' ) ) ) { $section['style'] = rgar( $section, 'style' ) . 'display:none;'; } // Open section container. printf( '<fieldset id="%s" class="%s"%s>', rgar( $section, 'id' ) ? esc_attr( $section['id'] ) : '', implode(' ', $class ), rgar( $section, 'style' ) ? sprintf( ' style="%s"', esc_attr( $section['style'] ) ) : '' ); // Add section header. if ( rgar( $section, 'title' ) || ( rgar( $section, 'collapsible' ) && rgar( $section, 'id' ) ) ) { // Display title. if ( rgar( $section, 'title' ) ) { printf( '<legend class="gform-settings-panel__title gform-settings-panel__title--header">%s %s</legend>', esc_html( $section['title'] ), self::maybe_get_tooltip( $section ) ); } // Display collapsible toggle. if ( rgar( $section, 'collapsible' ) && rgar( $section, 'id' ) ) { ?> <span class="gform-settings-panel__collapsible-control"> <input type="checkbox" id="gform_settings_section_collapsed_<?php echo esc_attr( $section['id'] ); ?>" name="gform_settings_section_collapsed_<?php echo esc_attr( $section['id'] ); ?>" value="1" onclick="this.checked ? this.closest( '.gform-settings-panel' ).classList.add( 'gform-settings-panel--collapsed' ) : this.closest( '.gform-settings-panel' ).classList.remove( 'gform-settings-panel--collapsed' )" <?php checked( true, self::is_section_collapsed( $section ), true ); ?> /> <label class="gform-settings-panel__collapsible-toggle" for="gform_settings_section_collapsed_uninstall"><span class="screen-reader-text"><?php printf( esc_html__( 'Toggle %s Section', 'gravityforms' ), esc_html( $section['title' ]) ); ?></span></label> </span> <?php } } // Open settings table. echo '<div class="gform-settings-panel__content">'; // Display section description. if ( rgar( $section, 'description' ) ) { printf( '<div class="gform-settings-description gform-kitchen-sink">%s</div>', $section['description'] ); } /** * Loop through fields and render. * * @var Fields\Base $field */ foreach ( rgar( $section, 'fields', array() ) as $field ) { if ( is_wp_error( $field ) || is_array( $field ) ) { continue; } $this->render_field( $field ); } // Close table and section container. echo '</div></fieldset>'; } /** * Render a single field. * * @since 2.5 * * @param Fields\Base $field */ public function render_field( $field ) { // If dependency is not met for field, do not render if no live dependency. // Otherwise, set field to hidden. if ( ! $this->is_dependency_met( rgobj( $field, 'dependency' ) ) && ! rgars( $field, 'dependency/live' ) ) { return; } else if ( ! $this->is_dependency_met( rgobj( $field, 'dependency' ) ) ) { $field->hidden = true; } // Prepare hidden styling. $hidden = rgar( $field, 'hidden' ) === true || rgar( $field, 'type' ) === 'hidden' ? ' style="display:none;"' : ''; printf( '<div id="gform_setting_%s" class="gform-settings-field gform-settings-field__%s" %s>', esc_attr( str_replace( array( '[', ']' ), array( '_', null ), $field->name ) ), $field->type, $hidden ); // Display field label. if ( rgobj( $field, 'label' ) ) { printf( '<div class="gform-settings-field__header"><label class="gform-settings-label" for="%s">%s%s</label>%s</div>', esc_attr( $field->name ), rgobj( $field, 'label' ), $field->required ? '<span class="required">(' . __( 'Required', 'gravityforms' ) . ')</span>' : '', self::maybe_get_tooltip( $field ) ); } // Display field input. echo $field->prepare_markup(); echo '</div>'; } /** * Determine if section is collapsed. * If postback, uses state upon submission. * * @since 2.5 * * @param array $props Section properties. * * @return bool */ private static function is_section_collapsed( $props = array() ) { return empty( $_POST ) ? rgar( $props, 'is_collapsed' ) : (bool) rgpost( 'gform_settings_section_collapsed_' . $props['id'] ); } /** * Display Save button in page header. * * @since 2.5 * * @param string $html Existing Save button HTML. * * @return string */ public function render_save_button( $html = '' ) { /** * If save button has not been created, initialize it. * Save button has to be initialized separately due to translatable strings. */ if ( empty( $this->save_button ) ) { $this->add_save_button(); } // Get Save button properties. $save_props = $this->save_button; // Prepare Save button markup. $html .= sprintf( '<button type="submit" id="gform-settings-save" name="gform-settings-save" value="save" form="gform-settings" class="%2$s"%3$s>%1$s</button>', esc_html( rgar( $save_props, 'value' ) ), esc_attr( $save_props['class'] ), ! $this->is_dependency_met( rgar( $save_props, 'dependency' ) ) ? 'style="display:none;"' : '' ); /** * Modify the output of the settings save button. * * @since 2.5 * * @param string $html HTML of the save button. * @param \Gravity_Forms\Gravity_Forms\Settings\Settings $this Current instance of the Settings Framework. */ $html = apply_filters( 'gform_settings_save_button', $html, $this ); return $html; } /** * Add default save button to a new settings section. * * @since 2.5 * * @param array $props Save button properties. */ private function add_save_button( $props = array() ) { $this->save_button = wp_parse_args( $props, array( 'name' => 'save', 'value' => esc_html__( 'Save Settings', 'gravityforms' ) . ' →', 'class' => 'primary button large', 'messages' => array( 'save' => esc_html__( 'Settings updated.', 'gravityforms' ), 'error' => esc_html__( 'There was an error while saving your settings.', 'gravityforms' ), ), ) ); } // # RENDER HELPER METHODS ----------------------------------------------------------------------------------------- /** * Returns an array of fields that have a registered live dependency. * * @since 2.5 * * @return array */ private function get_live_dependencies() { // Initialize dependencies array. $dependencies = array(); // Get fields. $groups = $this->get_fields(); // Add save button to groups so its dependencies can be registered. $groups[] = array( 'fields' => array( $this->save_button, ), ); // Loop through sections and fields, get field dependencies. foreach ( $groups as $group ) { // Get dependencies in group. $dependencies = array_merge( $dependencies, $this->get_live_dependencies_for_group( $group ) ); // If group does not have live dependencies, skip. if ( ! rgars( $group, 'dependency/live' ) ) { continue; } // Prepare dependency. $dependency = array( 'prefix' => $this->input_name_prefix, 'operator' => rgars( $group, 'dependency/operator' ) ? strtoupper( $group['dependency']['operator'] ) : 'ALL', 'target' => array( 'type' => rgar( $group, 'sections' ) ? 'tab' : ( rgar( $group, 'fields' ) ? 'section' : 'field' ), 'field' => rgar( $group, 'sections' ) || rgar( $group, 'fields' ) ? rgar( $group, 'id' ) : rgar( $group, 'name' ), ), 'fields' => rgars( $group, 'dependency/fields' ), ); // If no target field is defined, skip. if ( ! rgars( $dependency, 'target/field' ) ) { continue; } // Remove brackets for field target. if ( $dependency['target']['type'] === 'field' ) { $dependency['target']['field'] = str_replace( array( '[', ']', ), array( '_', null ), $dependency['target']['field'] ); } // Define callback. if ( rgars( $group, 'dependency/callback/js' ) ) { $dependency['callback'] = $group['dependency']['callback']['js']; } foreach ( $dependency['fields'] as $f => $_field ) { // Get field type. $dependency_field = $this->get_field( $_field['field'] ); $dependency['fields'][ $f ]['field_type'] = $dependency_field->type; // If field is a checkbox, check options. if ( rgar( $dependency_field, 'type' ) === 'checkbox' ) { // If no values were provided or only provided value is "1", set to first choice name. if ( ! rgar( $_field, 'values' ) || ( rgar( $_field, 'values' ) && is_array( $_field['values'] ) && $_field['values'][0] == 1 ) ) { $choices = array_values( rgar( $dependency_field, 'choices', array() ) ); $dependency['fields'][ $f ]['values'] = array( rgars( $choices, '0/name' ) ); } } } // Add to dependencies array. $dependencies[ $dependency['target']['field'] ] = $dependency; } return $dependencies; } /** * Returns an array of fields that have a registered live dependency for a specific group. * * @since 2.5 * * @param array $group Group of sections or fields. * * @return array */ private function get_live_dependencies_for_group( $group ) { $dependencies = array(); // Get nested key. $nested_key = GFCommon::get_nested_key( $group ); // Loop through fields, add dependencies. foreach ( rgar( $group, $nested_key, array() ) as $item ) { // If field has nested fields, add dependencies. if ( rgar( $item, 'sections' ) || rgar( $item, 'fields' ) ) { $dependencies = array_merge( $dependencies, $this->get_live_dependencies_for_group( $item ) ); } // If field does not have a live dependency, skip. if ( ! rgars( $item, 'dependency/live' ) ) { continue; } // Prepare dependency. $dependency = array( 'prefix' => $this->input_name_prefix, 'operator' => rgars( $item, 'dependency/operator' ) ? strtoupper( $item['dependency']['operator'] ) : 'ALL', 'target' => array( 'type' => rgar( $item, 'sections' ) ? 'tab' : ( rgar( $item, 'fields' ) ? 'section' : 'field' ), 'field' => rgar( $item, 'sections' ) || rgar( $item, 'fields' ) ? rgar( $item, 'id' ) : rgar( $item, 'name' ), ), 'fields' => rgars( $item, 'dependency/fields' ), ); // Override target type for save button. if ( $dependency['target']['type'] === 'field' && rgar( $item, 'type' ) === 'save' ) { $dependency['target']['type'] = 'save'; } // If no target field is defined, skip. if ( ! rgars( $dependency, 'target/field' ) && rgars( $dependency, 'target/type' ) !== 'save' ) { continue; } // Remove brackets for field target. if ( $dependency['target']['type'] === 'field' ) { $dependency['target']['field'] = str_replace( array( '[', ']', ), array( '_', null ), $dependency['target']['field'] ); } // Define callback. if ( rgars( $item, 'dependency/callback/js' ) ) { $dependency['callback'] = $item['dependency']['callback']['js']; } foreach ( $dependency['fields'] as $f => $_field ) { // Get field type. $dependency_field = $this->get_field( $_field['field'] ); $dependency['fields'][ $f ]['field_type'] = $dependency_field->type; // If field is a checkbox, check options. if ( rgar( $dependency_field, 'type' ) === 'checkbox' ) { // If no values were provided or only provided value is "1", set to first choice name. if ( ! rgar( $_field, 'values' ) || ( rgar( $_field, 'values' ) && is_array( $_field['values'] ) && $_field['values'][0] == 1 ) ) { $choices = array_values( rgar( $dependency_field, 'choices', array() ) ); $dependency['fields'][ $f ]['values'] = array( rgars( $choices, '0/name' ) ); } } } // Add to dependencies array. $dependencies[ $dependency['target']['field'] ] = $dependency; } return $dependencies; } /** * Returns message to display when settings have been successfully saved. * * @since 2.5 * * @return string */ public function get_save_success_message() { $success_message = rgars( $this->save_button, 'messages/save' ) ? rgars( $this->save_button, 'messages/save' ) : rgars( $this->save_button, 'messages/success' ); return $success_message; } /** * Returns message to display when settings could not be saved. * * @since 2.5 * * @return string */ public function get_save_error_message() { return rgars( $this->save_button, 'messages/error' ); } /** * Determines if the conditions to display a field or section have been met. * * @since 2.5 * * @param string|array|callable|null $dependency Condition to be met. * * @return bool */ public function is_dependency_met( $dependency ) { // If no dependency was provided, return. if ( ! $dependency ) { return true; } // If this is a legacy dependency, process the old way. if ( ! rgar( $dependency, 'fields' ) ) { return $this->is_legacy_dependency_met( $dependency ); } // Handle callback dependency. if ( rgars( $dependency, 'callback/php' ) && is_callable( $dependency['callback']['php'] ) ) { return call_user_func( $dependency['callback']['php'], $this ); } // Define operator, evaluated rules, dependency met. $operator = rgar( $dependency, 'operator' ) ? strtoupper( $dependency['operator'] ) : 'ALL'; $evaluated_rules = 0; $dependency_met = false; // Loop through fields, evaluate rules. foreach ( rgar( $dependency, 'fields' ) as $rule ) { // Get field. $field = $this->get_field( $rule['field'] ); // If field is a checkbox, check options. if ( rgar( $field, 'type' ) === 'checkbox' ) { // If no values were provided or only provided value is "1", set to first choice name. if ( ! rgar( $rule, 'values' ) || ( rgar( $rule, 'values' ) && is_array( $rule['values'] ) && $rule['values'][0] == 1 ) ) { $choices = array_values( rgar( $field, 'choices', array() ) ); $rule['values'] = array( rgars( $choices, '0/name' ) ); } // Loop through values, check for checked checkbox. foreach ( $rule['values'] as $value ) { if ( $this->get_value( $value ) == 1 ) { $evaluated_rules++; continue 2; } } continue; } // Get field value. $field_value = $this->get_value( $rule['field'] ); // Force values to array. if ( rgar( $rule, 'values' ) ) { $rule['values'] = is_array( $rule['values'] ) ? $rule['values'] : array( $rule['values'] ); } else { $rule['values'] = array( '_notempty_' ); } // Loop through values and evaluate. foreach ( $rule['values'] as $value ) { if ( $value === '_notempty_' && ! rgblank( $field_value ) && $field_value != '0' ) { $evaluated_rules++; continue 2; } if ( $value === $field_value ) { $evaluated_rules++; continue 2; } } } // Determine if dependency was met based on rules evaluated and operator. if ( 'ALL' === $operator && $evaluated_rules === count( $dependency['fields'] ) ) { $dependency_met = true; } elseif ( 'ANY' === $operator && $evaluated_rules > 0 ) { $dependency_met = true; } return $dependency_met; } /** * Determines if the conditions to display a field or section have been met. * Handles legacy dependencies for pre-Gravity Forms 2.5. * * @since 2.5 * * @param string|array|callable|null $dependency Condition to be met. * * @return bool */ public function is_legacy_dependency_met( $dependency ) { // If no dependency was provided, return. if ( ! $dependency ) { return true; } // If a callback was provided, run it and return the result. if ( is_callable( $dependency ) ) { return call_user_func( $dependency, $this ); } // Get dependency field and value. if ( is_array( $dependency ) ) { $dependency_field = rgar( $dependency, 'field' ); $dependency_value = rgar( $dependency, 'values' ); } else { $dependency_field = $dependency; $dependency_value = '_notempty_'; } // Set dependency value to an array. if ( ! is_array( $dependency_value ) ) { $dependency_value = array( $dependency_value ); } // Get current field value. $current_value = $this->get_value( $dependency_field ); // Loop through dependency values, look for match. foreach ( $dependency_value as $value ) { // If values are a match, return. if ( $current_value == $value ) { return true; } // If value is not empty, return. if ( '_notempty_' === $value && ! rgblank( $current_value ) ) { return true; } } return false; } /** * Returns the markup for a tooltip, if provided. * * @since 2.5 * * @param array|Fields\Base $props Field/Choice properties. * * @return string */ public static function maybe_get_tooltip( $props ) { // Initialize return string. $html = ''; // If a tooltip property exists, prepare tooltip. if ( isset( $props['tooltip'] ) ) { // If tooltip has already been processed, use it. if ( strpos( $props['tooltip'], 'class="gf_tooltip ' ) !== false ) { $html = $props['tooltip']; } else { $html = ' ' . gform_tooltip( $props['tooltip'], rgar( $props, 'tooltip_class' ), true ); } } return $html; } /** * Determine whether a section gets a card layout. * * If a section has one field, and the field type is card, it gets the card layout. * * @since 2.5.7 * * @param array $section Settings section * * @return bool */ public function has_card_layout( $section ) { if ( ! rgar( $section, 'fields' ) || 1 !== count( $section['fields'] ) ) { return false; } if ( 'card' !== $section['fields'][0]['type'] ) { return false; } return true; } // # SUBMISSION PROCESSING ----------------------------------------------------------------------------------------- /** * Processes the save settings callback. * Validates values. * If valid, filters and saves values. * If invalid, displays error message. * * @since 2.5 */ public function process_postback() { global $_gf_settings_posted_values; // If postback has already been processed, exit. if ( $this->processed_postback ) { return; } // If save dependency is not met, exit. if ( ! $this->is_dependency_met( rgar( $this->save_button, 'dependency' ) ) ) { return; } // Verify nonce. check_admin_referer( 'gform_settings_save', 'gform_settings_save_nonce' ); // If user does not have access, exit. if ( ! $this->current_user_has_access() ) { esc_html_e( 'Access denied.', 'gravityforms' ); return; } // Set previous values. $this->set_previous_values( $this->_saved_values ); // Get posted values. $values = $this->get_posted_values(); // Validate settings. $is_valid = $this->validate( $values ); // Set processed postback flag. $this->processed_postback = true; // If values are valid, filter and save. if ( $is_valid ) { // Filter values. $values = $this->filter_values( $values ); // Save values. $this->save_values( $values ); $this->set_values( $values ); // Reset postback values. $_gf_settings_posted_values = array(); // Set validation message. $this->postback_message = $this->get_save_success_message(); } else { // Set validation message. $this->postback_message = $this->get_save_error_message(); } } /** * Set the save success message after a save redirect. * * @since 2.5 */ public function set_save_message_after_redirect() { $this->postback_message = $this->get_save_success_message(); } /** * Check if the current user has the capabilities to access these settings. * * @since 2.5 * * @return bool */ public function current_user_has_access() { return ! $this->capability || GFCommon::current_user_can_any( $this->capability ); } /** * Filter posted field values. * Runs `save_callback` when defined for field. * * @since 2.5 * * @param array $values Posted field values. * * @return array */ public function filter_values( $values ) { // Get fields. $groups = $this->get_fields(); // Loop through tabs or sections and apply filters. foreach ( $groups as $group ) { $values = $this->filter_group_values( $values, $group ); } return $values; } /** * Filter posted field values for section. * Runs `save_callback` when defined for field. * * @since 2.5 * * @param array $values Posted field values. * @param array $group Array of sections or fields. * * @return array */ private function filter_group_values( $values, $group ) { // If dependency is not met for section, skip. if ( ! $this->is_dependency_met( rgar( $group, 'dependency' ) ) ) { return $values; } $nested_key = GFCommon::get_nested_key( $group ); /** * Loop through items, apply filters. * * @var array|Fields\Base $item */ foreach ( rgar( $group, $nested_key, array() ) as $item ) { // If dependency is not met for field, skip. if ( ! $this->is_dependency_met( rgar( $item, 'dependency' ) ) ) { continue; } // If this is a field, filter values. if ( is_object( $item ) ) { // Get field value. $field_value = $this->get_field_value( $item, $values ); // Filter value. $values = $item->save_field( $values, $field_value ); } // Validate nested fields. if ( rgar( $item, 'fields' ) ) { $values = $this->filter_group_values( $values, array( $item ) ); } } return $values; } /** * Gets the submitted field value. * * Fields with complex names are parsed into an indexed array to facilitate with value lookup. * * @since 2.5 * * @see Fields\Base::get_parsed_name() * * @param Fields\Base $item A Settings Field instance. * @param array $values Array of settings values. * * @return array|\ArrayAccess|mixed|string|null */ private function get_field_value( $item, $values ) { $name = $item->get_parsed_name(); if ( is_array( $name ) ) { return rgars( $values, implode( '/', $name ) ); } return rgar( $values, $name ); } /** * Save posted values. * * @since 2.5 * * @param array $values Posted values. */ public function save_values( $values ) { // Get save callback. $callback = $this->get_save_callback(); // If callback is callable, call it. if ( is_callable( $callback ) ) { call_user_func( $callback, $values ); } else if ( is_string( $callback ) ) { update_option( $callback, $values ); } } // # FIELD HELPER METHODS ------------------------------------------------------------------------------------------ /** * Check if defined fields contain a specific field type. * * @since 2.5 * * @param string $type Field type to search for. * @param array|bool $groups Array of tabs or sections to search through. Defaults to defined fields. * * @return bool */ public function has_field_type( $type, $groups = false ) { // Get fields. if ( ! $groups ) { $groups = $this->get_fields(); } // If no fields are defined, return. if ( empty( $groups ) ) { return false; } // Loop through groups, check for field type. foreach ( $groups as $group ) { // Determine nested key. $nested_key = GFCommon::get_nested_key( $group ); foreach ( $group[ $nested_key ] as $field ) { // If field is a match, return. if ( rgobj( $field, 'type' ) === $type ) { return true; } if ( rgobj( $field, 'fields' ) && $this->has_field_type( $type, $field->fields ) ) { return true; } } } return false; } // # VALIDATION METHODS -------------------------------------------------------------------------------------------- /** * Validate settings fields. * Fields can be invalid when marked as required and have a blank value or fails a custom validation check. * Use the `validation_callback` field property to implement a custom validation check. * * @since 2.5 * * @param array $values Posted field values. * * @return bool */ public function validate( $values ) { // Get fields. $groups = $this->get_fields(); // Loop through tabs or sections and validate. foreach ( $groups as $group ) { $this->validate_group( $values, $group ); } // Get field errors. $field_errors = $this->get_field_errors(); return empty( $field_errors ); } /** * Validate settings field group. * * @since 2.5 * * @param array $values Posted field values. * @param array $group Array of sections or fields. */ private function validate_group( $values, $group ) { // If section dependency is not met, exit. if ( ! $this->is_dependency_met( rgar( $group, 'dependency' ) ) ) { return; } $nested_key = GFCommon::get_nested_key( $group ); /** * Loop through fields and validate. * * @var Fields\Base $field */ foreach ( rgar( $group, $nested_key, array() ) as $field ) { // If field dependency is not met, skip. if ( ! $this->is_dependency_met( rgar( $field, 'dependency' ) ) ) { continue; } // Validate nested fields. if ( rgar( $field, 'fields' ) ) { $this->validate_group( $values, $field ); } // $field needs to be an object to run the subsequent steps, if not, bail. if ( ! is_object( $field ) ) { continue; } if ( method_exists( $field, 'get_values_from_post' ) ) { // Get field value from field object. $field_value = $field->get_values_from_post( $values ); } else { // Get field value. $field_value = $this->get_value( $field->name, null, $values ); } // Validate field. $field->handle_validation( $field_value ); } } /** * Get errors for all fields. * * @since 2.5 * * @return array */ public function get_field_errors() { $errors = array(); // Get fields. $groups = $this->get_fields(); // Loop through tabs or sections, find errors. foreach ( $groups as $group ) { $errors = array_merge( $errors, $this->get_group_errors( $group ) ); } return $errors; } /** * Get field errors for a section. * * @since 2.5 * * @param array $group Array of sections or fields. * * @return array */ private function get_group_errors( $group ) { $errors = array(); $nested_key = GFCommon::get_nested_key( $group ); /** * Loop through fields in section, find errors. * * @var Fields\Base $field */ foreach ( rgar( $group, $nested_key, array() ) as $field ) { // If field has an error, add it. if ( is_object( $field ) && $field->get_error() ) { $errors[ $field->name ] = $field->get_error(); } // If field has inputs, check inputs for errors. if ( rgobj( $field, 'inputs' ) ) { /** * @var Fields\Base $input */ foreach ( $field->inputs as $input ) { if ( $input->get_error() ) { $errors[ $input->name ] = $input->get_error(); } } } // If field has nested fields, find errors. if ( rgar( $field, 'fields' ) ) { $errors = array_merge( $errors, $this->get_group_errors( $field ) ); } } return $errors; } // # GETTER / SETTER METHODS --------------------------------------------------------------------------------------- /** * Get the tab currently being displayed. * * @since 2.5 * * @return bool|string */ public function get_active_tab() { // If settings are not tabbed, return. if ( ! $this->is_tabbed ) { return false; } // Get tabs, field errors. $tabs = array_values( $this->get_fields() ); $errors = $this->get_field_errors(); if ( empty( $errors ) ) { return rgpost( 'gform_settings_tab' ) ? sanitize_text_field( $_POST['gform_settings_tab'] ) : rgars( $tabs, '0/id' ); } // Get failed field names. $failed_fields = array_keys( $errors ); // Search for first failed field. foreach ( $tabs as $tab ) { foreach ( $tab['sections'] as $section ) { foreach ( $section['fields'] as $field ) { if ( in_array( rgar( $field, 'name' ), $failed_fields ) ) { return rgar( $tab, 'id' ); } } } } return rgars( $tabs, '0/id' ); } /** * Get the current form object. * * @since 2.5 * * @return bool|array */ public function get_current_form() { static $form; if ( isset( $form ) ) { return $form; } // Get current form ID. $form_id = $this->current_form ? $this->current_form : rgget( 'id' ); // If there is no form ID, return. if ( ! $form_id ) { return false; } // Get form. $form = GFAPI::get_form( $form_id ); if ( ! $form ) { return false; } if ( is_admin() ) { $form = gf_apply_filters( array( 'gform_admin_pre_render', $form_id ), $form ); } return $form; } /** * Get the current form ID. * * @since 2.5 * * @return bool|int */ public function get_current_form_id() { // Get the current form. $form = $this->get_current_form(); return $form ? rgar( $form, 'id' ) : false; } /** * Add a field to existing defined fields. * * @since 2.5 * * @param string $name Name of field to insert before/after. * @param array|Fields\Base[] $fields Field(s) to add. * @param string $position Insert field "before" or "after" existing field. * @param array|false $groups Array of sections/fields. * * @return array */ public function add_field( $name, $fields, $position, $groups = false ) { // If only one Field object is provided, push to array. if ( rgar( $fields, 'name' ) ) { $fields = array( $fields ); } // Initialize fields. $fields = $this->initialize_fields( $fields ); // Determine position. $position = $position === 'before' ? 0 : 1; // Get fields. if ( ! $groups ) { $groups = $this->get_fields(); } // Loop through groups, add field. foreach ( $groups as &$group ) { $group = $this->add_field_to_group( $group, $name, $fields, $position ); } // Update fields. $this->fields = $groups; return $this->fields; } /** * Add field to a group of fields. * * @since 2.5 * * @param array|Fields\Base $group Group of sections/fields. * @param string $name Name of field to insert before/after. * @param Fields\Base[] $fields Field(s) to add. * @param int $position Insert field before or after existing field. * * @return array|Fields\Base */ private function add_field_to_group( &$group, $name, $fields, $position ) { // Get nested key. $nested_key = GFCommon::get_nested_key( $group ); // If nested key does not exist or is empty, return. if ( ! isset( $group[ $nested_key ] ) || empty( $group[ $nested_key ] ) ) { return $group; } foreach ( $group[ $nested_key ] as $i => &$item ) { // If item name matches, add field(s). if ( rgar( $item, 'name' ) === $name ) { array_splice( $group[ $nested_key ], $i + $position, 0, $fields ); return $group; } // If field has its own fields, search within. if ( rgar( $item, 'fields' ) ) { $item = $this->add_field_to_group( $item, $name, $fields, $position ); } } return $group; } /** * Remove a field from existing defined fields. * * @since 2.5 * * @param string $name Name of field to remove. * @param array|false $groups Array of sections/fields. * * @return array */ public function remove_field( $name, $groups = false ) { // Get fields. if ( ! $groups ) { $groups = $this->get_fields(); } // Loop through groups, add field. foreach ( $groups as &$group ) { $group = $this->remove_field_from_group( $group, $name ); } // Update fields. $this->fields = $groups; return $this->fields; } /** * Remove field from a group of fields. * * @since 2.5 * * @param array|Fields\Base $group Group of sections/fields. * @param string $name Name of field to remove. * * @return array|Fields\Base */ private function remove_field_from_group( &$group, $name ) { // Get nested key. $nested_key = GFCommon::get_nested_key( $group ); // If nested key does not exist or is empty, return. if ( ! isset( $group[ $nested_key ] ) || empty( $group[ $nested_key ] ) ) { return $group; } foreach ( $group[ $nested_key ] as $i => &$item ) { // If item name matches, add field(s). if ( rgar( $item, 'name' ) === $name ) { array_splice( $group[ $nested_key ], $i, 1 ); return $group; } // If field has its own fields, search within. if ( rgar( $item, 'fields' ) ) { $item = $this->remove_field_from_group( $item, $name ); } } return $group; } /** * Replace an existing defined field. * * @since 2.5 * * @param string $name Name of field to replace. * @param array|Fields\Base[] $fields Field(s) to replace. * @param array|false $groups Array of sections/fields. * * @return array */ public function replace_field( $name, $fields, $groups = false ) { // If only one Field object is provided, push to array. if ( rgar( $fields, 'name' ) ) { $fields = array( $fields ); } // Initialize fields. $fields = $this->initialize_fields( $fields ); // Get fields. if ( ! $groups ) { $groups = $this->get_fields(); } // Loop through groups, add field. foreach ( $groups as &$group ) { $group = $this->replace_field_in_group( $group, $name, $fields ); } // Update fields. $this->fields = $groups; return $this->fields; } /** * Replace field in a group of fields. * * @since 2.5 * * @param array|Fields\Base $group Group of sections/fields. * @param string $name Name of field to replace. * @param Fields\Base[] $fields Field(s) to replace. * * @return array|Fields\Base */ private function replace_field_in_group( &$group, $name, $fields ) { // Get nested key. $nested_key = GFCommon::get_nested_key( $group ); // If nested key does not exist or is empty, return. if ( ! isset( $group[ $nested_key ] ) || empty( $group[ $nested_key ] ) ) { return $group; } foreach ( $group[ $nested_key ] as $i => &$item ) { // If item name matches, replace field(s). if ( rgar( $item, 'name' ) === $name ) { array_splice( $group[ $nested_key ], $i, 1, $fields ); return $group; } // If field has its own fields, search within. if ( rgar( $item, 'fields' ) ) { $item = $this->replace_field_in_group( $item, $name, $fields ); } } return $group; } /** * Get a specific settings field. * * @since 2.5 * * @param string $name Name of field to retrieve. * @param array|bool $groups Array of tabs or sections to search through. Defaults to defined fields. * * @return Fields\Base|bool */ public function get_field( $name, $groups = false ) { // If fields were not provided, use defined fields. if ( ! $groups ) { $groups = $this->get_fields(); } // Loop through groups, look for field. foreach ( $groups as $group ) { // Determine nested key. $nested_key = GFCommon::get_nested_key( $group ); foreach ( rgar( $group, $nested_key ) as $field ) { // If field is a match, return. if ( rgar( $field, 'name' ) === $name ) { return $field; } // If field has nested fields, search within. if ( rgar( $field, 'fields' ) ) { $found = $this->get_field( $name, array( $field ) ); if ( $found ) { return $found; } } // If field has nested inputs, search within. if ( rgar( $field, 'inputs' ) ) { $found = $this->get_field( $name, array( $field ) ); if ( $found ) { return $found; } } } } return false; } /** * Returns fields of a specific field type. * * @since 2.5 * * @param string $type Field type to search for. * @param array|bool $groups Array of tabs or sections to search through. Defaults to defined fields. * * @return array */ public function get_fields_by_type( $type, $groups = false ) { // Initialize return array. $fields_for_type = array(); // Get fields. if ( ! $groups ) { $groups = $this->get_fields(); } // If no fields are defined, return. if ( empty( $groups ) ) { return $fields_for_type; } // Loop through groups, check for field type. foreach ( $groups as $group ) { // Determine nested key. $nested_key = GFCommon::get_nested_key( $group ); foreach ( rgar( $group, $nested_key, array() ) as $field ) { // If field type matches, add to return array. if ( is_object( $field ) && $field->type === $type ) { $fields_for_type[] = $field; } // If field has its own fields, search within. if ( rgar( $field, 'fields' ) ) { $fields_for_type = array_merge( $fields_for_type, $this->get_fields_by_type( $type, $field['fields'] ) ); } } } return $fields_for_type; } /** * Get registered settings fields. * * @since 2.5 * * @return array */ public function get_fields() { return $this->fields; } /** * Define fields to be rendered. * * @since 2.5 * * @param array $fields Array of settings fields. */ public function set_fields( $fields = array() ) { // If fields are tabbed, set flag. $this->is_tabbed = false; foreach ( $fields as $group ) { if ( array_key_exists( 'sections', $group ) ) { $this->is_tabbed = true; } } // Loop through tabs or sections, initialize fields. if ( $this->is_tabbed ) { foreach ( $fields as $i => &$tab ) { // Set tab ID. if ( ! rgar( $tab, 'id' ) ) { $tab['id'] = rgar( $tab, 'name' ) ? $tab['name'] : sprintf( 'tab_%02d', $i ); } // Initialize fields. foreach ( $tab['sections'] as $s => &$section ) { $section['fields'] = $this->initialize_fields( $section['fields'] ); if ( empty( $section['fields'] ) ) { unset( $fields[ $i ]['sections'][ $s ] ); } } } } else { foreach ( $fields as $s => &$section ) { $section['fields'] = $this->initialize_fields( $section['fields'] ); if ( empty( $section['fields'] ) ) { unset( $fields[ $s ] ); } } } $this->fields = $fields; // Set save button. if ( ! $this->save_button ) { $this->add_save_button(); } } /** * Initialize fields as field objects. * * @since 2.5 * * @param array $fields Array of settings fields. * * @return array */ private function initialize_fields( $fields = array() ) { foreach ( $fields as $f => &$field ) { // Handle save button separately. if ( $field['type'] === 'save' ) { $this->add_save_button( $field ); unset( $fields[ $f ] ); } // If field is already initialized, skip. if ( is_object( $field ) ) { continue; } // Initialize field object. $f = Fields::create( $field, $this ); // If field was created, save. if ( ! is_wp_error( $f ) ) { $field = $f; } // If field has fields, initialize them. if ( rgobj( $field, 'fields' ) ) { $field->fields = $this->initialize_fields( $field->fields ); } } return $fields; } /** * Returns the input name prefix. * * @since 2.5 * * @return string */ public function get_input_name_prefix() { return $this->input_name_prefix; } /** * Set the input name prefix. * * @since 2.5 * * @param string $input_name_prefix Input name prefix. */ public function set_input_name_prefix( $input_name_prefix = '_gform_setting' ) { $this->input_name_prefix = $input_name_prefix; } /** * Returns the current field values. * If this is a postback request, returns posted values. * Otherwise, returns saved values passed in to constructor. * * @since 2.5 * * @return array */ public function get_current_values() { // Get posted values. $values = $this->get_posted_values(); // If no values have been posted, get saved values. if ( empty( $values ) ) { $values = $this->_saved_values; } return $values; } /** * Get previous field values. * * @since 2.5 * * @return bool|array */ public function get_previous_values() { return $this->_previous_values; } /** * Returns posted field values. * * @since 2.5 * * @return array */ public function get_posted_values() { global $_gf_settings_posted_values; // If posted values have already been retrieved, return. if ( isset( $_gf_settings_posted_values ) ) { return $_gf_settings_posted_values; } // Initialize posted values array. $_gf_settings_posted_values = array(); // If no values have been posted, return. if ( count( $_POST ) <= 0 ) { return $_gf_settings_posted_values; } // Strip input name prefix from keys. foreach ( $_POST as $key => $value ) { if ( preg_match( '|' . $this->input_name_prefix . '_(.*)|', $key, $matches ) ) { $_gf_settings_posted_values[ $matches[1] ] = GFCommon::maybe_decode_json( stripslashes_deep( $value ) ); if ( is_string( $_gf_settings_posted_values[ $matches[1] ] ) ) { $_gf_settings_posted_values[ $matches[1] ] = trim( $_gf_settings_posted_values[ $matches[1] ] ); } } } return $_gf_settings_posted_values; } /** * Get current value for field. * Use default value if not found. * * @since 2.5 * * @param string $name Field name. * @param string $default_value Default value. * @param array|bool $values Current field values. * * @return bool|array|string */ public function get_value( $name, $default_value = '', $values = false ) { // Get current values. if ( ! $values || ! is_array( $values ) ) { $values = $this->get_current_values(); } // If no default value was provided, get default value for field. if ( ! $default_value ) { // Get field. $field = $this->get_field( $name ); // If field was found, get default value. if ( $field && rgobj( $field, 'default_value' ) ) { $default_value = $field['default_value']; } } // If no values are defined, return default value. if ( false === $values ) { return $default_value; } // If field name contains a bracket, get value from array. if ( false !== strpos( $name, '[' ) ) { // Explode field name. $name_parts = explode( '[', $name ); // Loop through field name parts, look for value. foreach ( $name_parts as $part ) { // Get current part. $part = trim( $part, ']' ); if ( '0' != $part ) { if ( empty( $part ) ) { return $values; } } if ( false === isset( $values[ $part ] ) ) { return $default_value; } $values = rgar( $values, $part ); } $value = $values; } else { // If field is not found in values array, return default value. if ( false === isset( $values[ $name ] ) ) { return $default_value; } $value = $values[ $name ]; } return $value; } /** * Save current field values. * * @since 2.5 * * @param array|string $values Field values to be saved. */ public function set_values( $values = array() ) { // If values is a string and serialized, unserialize. if ( is_string( $values ) && is_serialized( $values ) ) { $values = unserialize( $values ); } else if ( is_string( $values ) && ! is_serialized( $values ) ) { $values = get_option( $values, array() ); } $this->_saved_values = $values; } /** * Save previous field values. * * @since 2.5 * * @param array $values Field values to be saved. */ public function set_previous_values( $values = array() ) { $this->_previous_values = $values; } /** * Get the save callback. * * @since 2.5 * * @return string|callable */ public function get_save_callback() { return $this->_save_callback; } /** * Set the save callback. * * @since 2.5 * * @param string|callable $callback Option name or callable function values will be saved to. */ public function set_save_callback( $callback = '' ) { $this->_save_callback = $callback; } /** * Set the postback message callback. * * @since 2.5 * * @param callable $callback Callable function to use when displaying success message. * * @return bool|WP_Error */ public function set_postback_message_callback( $callback ) { // If callback is not callable, return. if ( ! is_callable( $callback ) ) { return new WP_Error( 'not_callable', 'Provided callback is not callable.' ); } $this->postback_message_callback = $callback; return true; } // # MISC HELPER METHODS ------------------------------------------------------------------------------------------- /** * Determines if Save button was pressed. * * @since 2.5 * * @return bool */ public static function is_save_postback() { return ! rgempty( 'gform-settings-save' ); } }