ermissions', 'manage_woocommerce', $menu_slug); } /** * Renders all settings sections added to a particular settings page. This * method is an almost exact clone of global do_settings_sections(), the main * difference is that each section is wrapped in its own
. * * Part of the Settings API. Use this in a settings page callback function * to output all the sections and fields that were added to that $page with * add_settings_section() and add_settings_field(). * * @global $wp_settings_sections Storage array of all settings sections added to admin pages * @global $wp_settings_fields Storage array of settings fields and info about their pages/sections * @since 2.7.0 * * @param string $page The slug name of the page whos settings sections you want to output */ protected function render_settings_sections($page) { global $wp_settings_sections, $wp_settings_fields; $settings_sections = get_value($page, $wp_settings_sections); if(empty($settings_sections)) { return; } //foreach((array)$wp_settings_sections[$page] as $section) { $settings_tabs = get_value($page, $this->_settings_tabs); $output_tabs = count($settings_tabs) > 1; if($output_tabs) { echo '
'; echo "\n"; } else { echo '
'; } foreach($settings_tabs as $tab_id => $tab_info) { $sections = get_value('sections', $tab_info, array()); echo "
"; foreach($sections as $section_id) { $section = get_value($section_id, $wp_settings_sections[$page]); echo '
'; if($section['title']) { echo "

{$section['title']}

\n"; } if($section['callback']) { call_user_func($section['callback'], $section); } $section_id = get_value('id', $section); if(isset($wp_settings_fields[$page]) && get_value($section_id, $wp_settings_fields[$page], false)) { echo ''; do_settings_fields($page, $section['id']); echo '
'; } echo '
'; } echo '
'; } echo '
'; } /** * Renders the buttons at the bottom of the settings page. */ protected function render_buttons() { submit_button(__('Save Changes', $this->_textdomain), 'primary', 'submit', false); } /** * Renders the Options page for the plugin. */ public function render_options_page() { // Prepare settings page for rendering $this->init_settings_page(); echo '
'; echo '
'; echo '

' . $this->page_title() . '

'; echo '

' . $this->page_description() . '

'; settings_errors(); echo '
'; settings_fields($this->_settings_key); $this->render_settings_sections($this->_settings_key); echo '
'; $this->render_buttons(); echo '
'; echo '
'; echo '
'; // Closing
} /** * Adds a link to Settings Page in WC Admin menu. */ public function add_settings_page() { $settings_page = add_submenu_page( self::WC_MENU_ITEM_ID, $this->page_title(), $this->menu_title(), $this->get_plugin_settings_menu_permission($this->menu_slug()), $this->menu_slug(), array($this, 'render_options_page') ); add_action('load-' . $settings_page, array($this, 'options_page_load')); } /** * Takes an associative array of attributes and returns them as a string of * param="value" sets to be placed in an input, select, textarea, etc tag. * * @param array attributes An associative array of attribute key => value * pairs to be converted to a string. A number of "reserved" keys will be * ignored. * @return string */ protected function attributes_to_string(array $attributes) { $reserved_attributes = array( 'id', 'name', 'value', 'method', 'action', 'type', 'for', 'multiline', 'default', 'textfield', 'valuefield', 'includenull', 'yearrange', 'fields', 'inlineerrors', 'description'); $result = array(); // Build string from array if(is_array($attributes)) { foreach($attributes as $attribute => $value) { // Ignore reserved attributes if(!in_array(strtolower($attribute), $reserved_attributes)) { $result[] = $attribute . '="' . $value . '"'; } } } return implode(' ', $result); } /** * Extracts the Field ID and Field Name for an input element from the arguments * originally passed to a rendering function. * * @param array field_args The arguments passed to the rendering function which * is going to render the input field. * @param string field_id Output argument. It will contain the ID of the field. * @param string field_name Output argument. It will contain the name of the field. */ protected function get_field_ids(array $field_args, &$field_id, &$field_name) { // Determine field ID and Name $field_id = $field_args['id']; if(empty($field_id)) { throw new InvalidArgumentException(__('Field ID must be specified.', $this->_textdomain)); } $field_name = $field_args['name'] ?? $field_args['attributes']['name'] ?? $field_id; // If a Settings Key has been passed, modify field ID and Name to make them // part of the Settings Key array $settings_key = $field_args['settings_key'] ?? null; $field_id = $this->group_field($field_id, $settings_key); $field_name = $this->group_field($field_name, $settings_key); } /** * Takes a field ID and transforms it so that it becomes part of a field group. * Example * - Field ID: MyField * - Group: SomeGroup * * Result: SomeGroup[MyField] * This allows to group fields together and access them as an array. * * @param string id The Field ID. * @param string group The group to which the field should be added. * @return string The new field name. */ protected function group_field($id, $group) { return empty($group) ? $id : $group . '[' . $id . ']'; } /*** Rendering methods ***/ /** * Renders a select box. * * @param array args An array of arguments passed by add_settings_field(). * @param bool display Indicates if the HTML for the field should be printed * out directly (true) or just returned (false). * @see add_settings_field(). */ public function render_dropdown($args, $display = true) { $this->get_field_ids($args, $field_id, $field_name); // Retrieve the options that will populate the dropdown $dropdown_options = $args['options']; if(!is_array($dropdown_options)) { throw new InvalidArgumentException(__('Argument "options" must be an array.', $this->_textdomain)); } // Retrieve the selected Option elements $selected_options = get_value('selected', $args, array()); if(!is_array($selected_options)) { $selected_options = array($selected_options); } // Retrieve the HTML attributes $attributes = get_value('attributes', $args, array()); // If we are about to render a multi-select dropdown, add two square brackets // so that all selected values will be returned as an array // TODO Make search in array case-insensitive if(in_array('multiple', $attributes)) { $field_name .= '[]'; } $html = ''; if(!empty($attributes['description'])) { $html .= '

' . $attributes['description'] . '

'; } if($display) { echo $html; } return $html; } /** * Build the HTML to represent an element. * * @param string type The type of input. It can be text, password, hidden, * checkbox or radio. * @param string field_id The ID of the field. * @param string value The field value. * @param array attribues Additional field attributes. * @param string field_name The name of the field. If unspecified, the field * ID will be taken. * @return string The HTML representation of the field. */ protected function get_input_html($type, $field_id, $value, $attributes, $field_name = null) { $field_name = !empty($field_name) ? $field_name : $field_id; $html = 'attributes_to_string($attributes) . ' />'; if(!empty($attributes['description'])) { if($type == 'checkbox') { $element = 'span'; } else { $element = 'p'; } $html .= '<' . $element . ' class="description">' . $attributes['description'] . ''; } return $html; } /** * Build the HTML to represent a '; if(!empty($attributes['description'])) { $html .= '

' . $attributes['description'] . '

'; } return $html; } /** * Renders a hidden field. * * @param array args An array of arguments passed by add_settings_field(). * @param bool display Indicates if the HTML for the field should be printed * out directly (true) or just returned (false). * @see add_settings_field(). */ public function render_hidden($args, $display = true) { $this->get_field_ids($args, $field_id, $field_name); // Retrieve the HTML attributes $attributes = get_value('attributes', $args, array()); $value = get_value('value', $args, ''); $html = $this->get_input_html('hidden', $field_id, $value, $attributes, $field_name); if($display) { echo $html; } return $html; } /** * Renders a text box (input or textarea). To render a textarea, pass an * attribute named "multiline" set to true. * * @param array args An array of arguments passed by add_settings_field(). * @param bool display Indicates if the HTML for the field should be printed * out directly (true) or just returned (false). * @see add_settings_field(). */ public function render_textbox($args, $display = true) { $this->get_field_ids($args, $field_id, $field_name); // Retrieve the HTML attributes $attributes = get_value('attributes', $args, array()); $value = get_value('value', $args, ''); $multiline = get_value('multiline', $attributes); if($multiline) { $html = $this->get_textarea_html($field_id, $value, $attributes, $field_name); } else { $field_type = get_value('type', $args, 'text'); $html = $this->get_input_html($field_type, $field_id, $value, $attributes, $field_name); } if($display) { echo $html; } return $html; } /** * Renders a checkbox. * * @param array args An array of arguments passed by add_settings_field(). * @param bool display Indicates if the HTML for the field should be printed * out directly (true) or just returned (false). * @see add_settings_field(). */ public function render_checkbox($args, $display = true) { $this->get_field_ids($args, $field_id, $field_name); // Retrieve the HTML attributes $attributes = get_value('attributes', $args, array()); if(get_value('checked', $attributes, false)) { $attributes['checked'] = 'checked'; } else { unset($attributes['checked']); } $value = get_value('value', $args, 1); /* The hidden input is a "trick" to get a value also when a checkbox is not * ticked. Unticked checkboxes are not POSTed with a form, therefore the only * way to get a "non selected" value would be to check if each checkbox field * was posted. By adding a hidden field just before it, with a value of zero, * we can ensure that something is always POSTed, whether the checkbox field * is ticked or not: * - When it's ticked, its value is sent with the form, overriding the hidden field. * - When it's not ticked, the value of the hidden field is sent instead. * * This allows to skip a lot of checks, and to just take the field value. */ $html = $this->get_input_html('hidden', $field_id . '_unticked', 0, array(), $field_name); $html .= $this->get_input_html('checkbox', $field_id, $value, $attributes, $field_name); if($display) { echo $html; } return $html; } /** * Event handler, fired when setting page is loaded. */ public function options_page_load() { if(get_value('settings-updated', $_GET)) { //plugin settings have been saved. Display a message, or do anything you like. } } /** * Initialises the settings page. */ public function init_settings_page() { $this->add_settings_tabs(); $this->add_settings_sections(); $this->add_settings_fields(); } /** * Renders a simple text field. * * @param string section The section where the field will be rendered. * @param string field_id The field ID. * @param string label The field label. * @param string description The field description. * @param string css_class The CSS class to give to the rendered input field. * @param array attributes Additional HTML attributes. * @param string type The field type. */ protected function render_text_field($section, $field_id, $label, $description = '', $css_class = '', $attributes = array(), $type = 'text') { // Fetch the value from the attributes passed with the field definition, if present. If absent, fetch // the value from current settings. This allows 3rd parties to inject custom fields whose value has // to be determined using some specific logic // @since 2.1.1.201208 $value = $attributes['value'] ?? $this->current_settings($field_id, $this->default_settings($field_id, '')); add_settings_field( $field_id, $label, array($this, 'render_textbox'), $this->_settings_key, $section, array( 'settings_key' => $attributes['settings_key'] ?? $this->_settings_key, 'id' => $field_id, 'label_for' => $field_id, 'value' => $value, // Field type // @since 2.0.9.191108 'type' => $type, // Input field attributes 'attributes' => array_merge(array( 'class' => $css_class . ' ' . $field_id, 'description' => $description, ), $attributes), ) ); } /** * Renders a simple checkbox field. * * @param string section The section where the field will be rendered. * @param string field_id The field ID. * @param string label The field label. * @param string description The field description. * @param string css_class The CSS class to give to the rendered input field. * @param array attributes Additional HTML attributes. */ protected function render_checkbox_field($section, $field_id, $label, $description = '', $css_class = '', $attributes = array()) { // Fetch the value from the attributes passed with the field definition, if present. If absent, fetch // the value from current settings. This allows 3rd parties to inject custom fields whose value has // to be determined using some specific logic // @since 2.1.1.201208 $value = $attributes['value'] ?? $this->current_settings($field_id, $this->default_settings($field_id, '')); add_settings_field( $field_id, $label, array($this, 'render_checkbox'), $this->_settings_key, $section, array( 'settings_key' => $attributes['settings_key'] ?? $this->_settings_key, 'id' => $field_id, 'label_for' => $field_id, // Input field attributes 'attributes' => array_merge(array( 'class' => $css_class . ' ' . $field_id, 'description' => $description, 'checked' => $value, ), $attributes), ) ); } /** * Renders a dropdown field. * * @param string section The section where the field will be rendered. * @param string field_id The field ID. * @param string label The field label. * @param array options An associative array of value => label entries. It * will be used to populate the options available for the dropdown field. * @param string description The field description. * @param string css_class The CSS class to give to the rendered input field. * @param array attributes Additional HTML attributes (e.g. "multiple"). */ protected function render_dropdown_field($section, $field_id, $label, $options, $description = '', $css_class = '', $attributes = array()) { // Fetch the value from the attributes passed with the field definition, if present. If absent, fetch // the value from current settings. This allows 3rd parties to inject custom fields whose value has // to be determined using some specific logic // @since 2.1.1.201208 $value = $attributes['value'] ?? $this->current_settings($field_id, $this->default_settings($field_id, '')); add_settings_field( $field_id, $label, array($this, 'render_dropdown'), $this->_settings_key, $section, array( 'settings_key' => $attributes['settings_key'] ?? $this->_settings_key, 'id' => $field_id, 'label_for' => $field_id, 'options' => $options, 'selected' => $value, // Input field attributes 'attributes' => array_merge(array( 'class' => $css_class . ' ' . $field_id, 'description' => $description, ), $attributes), ) ); } /** * Handlesa custom field, which will be rendered using a dedicated callback function. * * @param string $section * @param string $field_id * @param string $field_label * @param array $attributes * @param callable $render_callback * @since 2.1.0.201112 */ protected function render_custom_field($section, $field_id, $field_label, array $attributes, $render_callback) { if(!is_callable($render_callback)) { // Invalid callback, report issue return; } // Add "Exchange Rates" table add_settings_field( $field_id, $field_label, $render_callback, $this->_settings_key, $section, $attributes ); } }