Acknowledgify. * * Every form control is rendered through one of the render_*_row() helpers * below. That is deliberate: the previous version built each row by hand and * several inputs ended up with no associated label, because the visible text * lived in a that was never tied to the field. Going through a helper * makes a label mandatory by construction. * * @package Acknowledgify */ namespace Acknowledgify\Admin; use Acknowledgify\Includes\Helper; use Acknowledgify\Includes\Humans_Txt; use Acknowledgify\Includes\Meta_Tags; use Acknowledgify\Includes\Tokens; // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Renders the settings screen and handles saving, export and import. */ class Settings { /** * Nonce action and field name shared by export and import. */ const NONCE_ACTION = 'acknowledgify_settings_nonce'; const NONCE_NAME = 'acknowledgify_nonce'; /** * Transient prefix for a notice that has to survive a page reload. */ const NOTICE_TRANSIENT = 'acknowledgify_notice'; /** * Tab slugs, in display order. * * @return array Slug => array( 'label' => string, 'toggle' => string|null ). */ private static function get_tabs() { return array( 'general' => array( 'label' => __( 'General', 'acknowledgify' ), 'toggle' => null, ), 'humans' => array( 'label' => __( 'humans.txt', 'acknowledgify' ), 'toggle' => 'enable_humans_txt', ), 'meta' => array( 'label' => __( 'Meta Tags', 'acknowledgify' ), 'toggle' => 'enable_meta_tags', ), 'footer' => array( 'label' => __( 'Footer Link', 'acknowledgify' ), 'toggle' => 'enable_footer_link', ), ); } /** * Human-readable labels for the meta tag fields. * * @return array Setting key => label. */ private static function get_meta_labels() { return array( 'meta_author' => __( 'Author', 'acknowledgify' ), 'meta_designer' => __( 'Designer', 'acknowledgify' ), 'meta_developer' => __( 'Developer', 'acknowledgify' ), 'meta_copyright' => __( 'Copyright', 'acknowledgify' ), 'meta_owner' => __( 'Owner', 'acknowledgify' ), 'meta_publisher' => __( 'Publisher', 'acknowledgify' ), 'meta_manage' => __( 'Managed by', 'acknowledgify' ), 'meta_power' => __( 'Powered by', 'acknowledgify' ), 'meta_acknowledgment' => __( 'Acknowledgment', 'acknowledgify' ), ); } /** * DOM id for a setting's control. * * @param string $key Setting key. * @return string */ private static function field_id( $key ) { return 'ack-' . str_replace( '_', '-', $key ); } /** * Field name attribute for a setting. * * @param string $key Setting key. * @return string */ private static function field_name( $key ) { return ACKNOWLEDGIFY_OPTION . '[' . $key . ']'; } /** * Which tab to open. * * Read from the query string so the tab survives the round trip through * options.php, which redirects without the fragment. This only selects * which panel is visible, so it needs sanitising against the tab list but * no nonce. * * @return string Tab slug. */ private static function get_active_tab() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display preference only, validated against a fixed list. $requested = isset( $_GET['ack_tab'] ) ? sanitize_key( wp_unslash( $_GET['ack_tab'] ) ) : ''; return array_key_exists( $requested, self::get_tabs() ) ? $requested : 'general'; } /** * Print the description and code sample that follow a control. * * @param array $args Row arguments. * @return void */ private static function render_help( $args ) { if ( '' !== $args['description'] ) { printf( '

%2$s

', esc_attr( $args['describedby'] ), esc_html( $args['description'] ) ); } if ( '' !== $args['code'] ) { printf( '

%s

', esc_html( $args['code'] ) ); } } /** * Shared argument handling for every row helper. * * @param string $key Setting key. * @param string $label Visible label text. * @param array $args Optional overrides. * @return array */ private static function prepare_row( $key, $label, $args ) { $args = wp_parse_args( $args, array( 'type' => 'text', 'description' => '', 'code' => '', 'placeholder' => '', 'class' => 'regular-text', 'row_id' => '', 'tokens' => false, ) ); if ( $args['tokens'] && '' !== $args['description'] ) { $args['description'] .= ' ' . __( 'Placeholder tokens work here.', 'acknowledgify' ); } $args['id'] = self::field_id( $key ); $args['name'] = self::field_name( $key ); $args['label'] = $label; $args['describedby'] = $args['id'] . '-description'; return $args; } /** * Open a form-table row and print its label cell. * * @param array $args Prepared row arguments. * @return void */ private static function open_row( $args ) { printf( '', $args['row_id'] ? ' id="' . esc_attr( $args['row_id'] ) . '"' : '', esc_attr( $args['id'] ), esc_html( $args['label'] ) ); } /** * Render a single-line input row. * * @param string $key Setting key. * @param string $label Visible label. * @param array $args Optional overrides. * @return void */ private static function render_text_row( $key, $label, $args = array() ) { $args = self::prepare_row( $key, $label, $args ); self::open_row( $args ); printf( '', esc_attr( $args['type'] ), esc_attr( $args['id'] ), esc_attr( $args['name'] ), esc_attr( (string) Helper::get_setting( $key, '' ) ), esc_attr( $args['class'] ), $args['placeholder'] ? ' placeholder="' . esc_attr( $args['placeholder'] ) . '"' : '', '' !== $args['description'] ? ' aria-describedby="' . esc_attr( $args['describedby'] ) . '"' : '' ); self::render_help( $args ); echo ''; } /** * Render a textarea row. * * @param string $key Setting key. * @param string $label Visible label. * @param array $args Optional overrides. * @return void */ private static function render_textarea_row( $key, $label, $args = array() ) { $args = self::prepare_row( $key, $label, wp_parse_args( $args, array( 'rows' => 8, 'class' => 'large-text code', ) ) ); self::open_row( $args ); printf( '', esc_attr( $args['id'] ), esc_attr( $args['name'] ), (int) $args['rows'], esc_attr( $args['class'] ), '' !== $args['description'] ? ' aria-describedby="' . esc_attr( $args['describedby'] ) . '"' : '', esc_textarea( (string) Helper::get_setting( $key, '' ) ) ); self::render_help( $args ); echo ''; } /** * Render a select row. * * Options come from Helper::get_allowed_values(), so the control can only * ever offer values the sanitiser will accept. * * @param string $key Setting key. * @param string $label Visible label. * @param array $labels Value => visible option text. * @param array $args Optional overrides. * @return void */ private static function render_select_row( $key, $label, $labels, $args = array() ) { $args = self::prepare_row( $key, $label, $args ); self::open_row( $args ); printf( ''; self::render_help( $args ); echo ''; } /** * Render a colour picker row. * * @param string $key Setting key. * @param string $label Visible label. * @param array $args Optional overrides. * @return void */ private static function render_color_row( $key, $label, $args = array() ) { $args = self::prepare_row( $key, $label, wp_parse_args( $args, array( 'class' => 'regular-text ack-color-picker' ) ) ); self::open_row( $args ); printf( '', esc_attr( $args['id'] ), esc_attr( $args['name'] ), esc_attr( (string) Helper::get_setting( $key, '' ) ), esc_attr( $args['class'] ), esc_attr( (string) Helper::get_default( $key ) ), '' !== $args['description'] ? ' aria-describedby="' . esc_attr( $args['describedby'] ) . '"' : '' ); self::render_help( $args ); echo ''; } /** * Render a feature toggle. * * The switch appearance is driven entirely by :checked in CSS. The previous * version mirrored the state into a data-toggle attribute with JavaScript, * which meant the visual state was wrong without JavaScript, and gave the * checkbox an empty