self::FIELD_ID_WIDGET_KEY, "label" => "Widget key", "render_fn" => array("AllEarsOptions", "render_text"), // "validation_fn" is a function that takes in input a value, and returns "null" if // validation succeeded, or an error message if it failed. "validation_fn" => array("AllEarsOptions", "validate_widget_key"), "sanitize_fn" => array("AllEarsOptions", "sanitize_text"), "section_id" => self::SECTION_ID_WIDGET, // "id" and all tha follows are added to $args of "render_fn" by AllEarsOptions::add_settings_field() "db_id" => self::DB_ID_WIDGET, "db_key_id" => self::WIDGET_DB_WIDGET_KEY, ); const FIELD_DEF_WIDGET_DEFAULT_ATTS = array( "id" => "allEars_field_widget_default_atts", "label" => "Widget default attributes", "render_fn" => array("AllEarsOptions", "render_widget_default_atts"), "validation_fn" => array("AllEarsOptions", "validate_widget_default_atts"), "sanitize_fn" => array("AllEarsOptions", "sanitize_widget_default_atts"), "section_id" => self::SECTION_ID_WIDGET, // "id" and all tha follows are added to $args of "render_fn" by AllEarsOptions::add_settings_field() "db_id" => self::DB_ID_WIDGET, "db_key_id" => "default_atts", "help" => "Set the default attributes to be automatically added to all [allears-widget] shortcodes on all posts.
" . "This setting applies to shortcodes added explicitly as well as shortcodes added automatically. " . "Use the same syntax you use for the shortcode attributes. " . "To override a default for a specific shortcode, simply define the attribute again on the shortcode itself." ); const FIELD_DEF_WIDGET_AUTO = array( "id" => "allEars_field_widget_auto", "label" => "Widget on all posts", "render_fn" => array("AllEarsOptions", "render_checkbox"), // No validation_fn needed for a checkbox "sanitize_fn" => array("AllEarsOptions", "sanitize_checkbox"), "section_id" => self::SECTION_ID_WIDGET, // "id" and all tha follows are added to $args of "render_fn" by AllEarsOptions::add_settings_field() "db_id" => self::DB_ID_WIDGET, "db_key_id" => "auto", "help" => "If you check this box, the plugin will automatically add a widget to the top of all posts that don't explicitly have the shortcode. ". "If this feature is enabled here, you can disable it on individual posts in the post editor." ); // OBSOLETE // const FIELD_DEF_WIDGET_AUTO_PARAMS = array( // "id" => "allEars_field_widget_auto_params", // "label" => "Widget parameters", // "render_fn" => array("AllEarsOptions", "render_widget_default_atts"), // "validation_fn" => array("AllEarsOptions", "validate_widget_default_atts"), // "sanitize_fn" => array("AllEarsOptions", "sanitize_widget_default_atts"), // "section_id" => self::SECTION_ID_WIDGET, // // "id" and all tha follows are added to $args of "render_fn" by AllEarsOptions::add_settings_field() // "db_id" => self::DB_ID_WIDGET, // "db_key_id" => "auto_params", // "help" => "Set the parameters to be used when the widget is added automatically to a post. " . // "This setting does not apply to shortcodes added explicitly to a post. ". // "Use the syntax of the shortcode parameters." // ); const WIDGET_FIELDS = array( self::FIELD_DEF_WIDGET_KEY, self::FIELD_DEF_WIDGET_DEFAULT_ATTS, self::FIELD_DEF_WIDGET_AUTO, // self::FIELD_DEF_WIDGET_AUTO_PARAMS ); // Returns a (unsanitized) value, or "" if the $db_id is missing or if $db_key_id is // missing from $db_id. // We want to check if $db_key_id exists, mostly because we want to "future proof" this // logic, so that it can work later if we add new $db_key_id that won't be found in // existing installation before upgrading. private static function get_db_value($db_id, $db_key_id) { // Note that we're not using self::get_option() here, because this function // is called once for every key in the option, and self::get_option() creates // a new empty array with all the option keys if "$db_id" is not found. That's // a bit expensive for the use case in this function. // // The first argument is the name of the settings stored in the DB. // The function returns "false" if there's no settings stored in the DB, which // means the user has not stored anything there yet... $option = get_option($db_id); if($option !== false && isset($option[$db_key_id])) { $db_value = $option[$db_key_id]; } else { $db_value = ""; } return $db_value; } // The return value is NOT sanitized, the caller must take whatever action she wants. public static function get_widget_key() { return self::get_db_value(self::DB_ID_WIDGET, self::FIELD_DEF_WIDGET_KEY["db_key_id"]); } public static function get_widget_default_atts() { return self::get_db_value(self::DB_ID_WIDGET, self::FIELD_DEF_WIDGET_DEFAULT_ATTS["db_key_id"]); } public static function is_widget_auto() { return self::get_db_value(self::DB_ID_WIDGET, self::FIELD_DEF_WIDGET_AUTO["db_key_id"]); } // public static function get_widget_auto_params() { // return self::get_db_value(self::DB_ID_WIDGET, self::FIELD_DEF_WIDGET_AUTO_PARAMS["db_key_id"]); // } public static function section_widget_text() { $support_email = ALLEARS_GLOBALS["support_email"]; $email_subject = "allEars widget key request"; // For the use of "%0D%0A", see // https://stackoverflow.com/questions/22765834/insert-a-line-break-in-mailto-body // It looks like esc_url() below does not attempt to double-escape "%0D%0A" // (encoded "\r\n"), so it's safe to use it this way. // Also tried: // - No line terminator: esc_url() doesn't encode the "native" CR LF from the file. // - Add "\r\n" to the text below: esc_url() removes them from the output, no encoding. // // The closing "EOT" MUST BE at the beginning of the line, don't indent it... $email_body = <<.

{$args["help"]}

"; } } public static function render_widget_default_atts($args) { $html_id = $args["id"]; $db_id = $args["db_id"]; $db_key_id = $args["db_key_id"]; $db_value = self::get_db_value($db_id, $db_key_id); $output = ""; if($db_value != "") { foreach($db_value as $key => $val) { if($output != "") { // Add a separator from the second value on... (a whitespace is sufficient) $output .= " "; } $output .= self::sanitize_text($key) . "=\"" . self::sanitize_text($val) . "\""; } } // "id" must be the same ID used as arg (0) of add_settings_field(). // "name" must start with the same DB_ID used in register_settings(). If it doesn't, you'll // get option_validate() being called with "$input == null", and you'll scratch your head // trying to figure out why the heck... echo ""; self::render_help($args); } public static function render_text($args) { $html_id = $args["id"]; $db_id = $args["db_id"]; $db_key_id = $args["db_key_id"]; $db_value = self::sanitize_text(self::get_db_value($db_id, $db_key_id)); // "id" must be the same ID used as arg (0) of add_settings_field(). // "name" must start with the same DB_ID used in register_settings(). If it doesn't, you'll // get option_validate() being called with "$input == null", and you'll scratch your head // trying to figure out why the heck... echo ""; self::render_help($args); } public static function render_checkbox($args) { $html_id = $args["id"]; $db_id = $args["db_id"]; $db_key_id = $args["db_key_id"]; $db_value = self::sanitize_checkbox(self::get_db_value($db_id, $db_key_id)); $checked = ($db_value != "") ? "checked" : ""; // "id" must be the same ID used as arg (0) of add_settings_field(). // "name" must start with the same DB_ID used in register_settings(). If it doesn't, you'll // get option_validate() being called with "$input == null", and you'll scratch your head // trying to figure out why the heck... echo ""; self::render_help($args); } public static function add_settings_field($field_def) { $render_fn_args = array( "id" => $field_def["id"], "db_id" => $field_def["db_id"], "db_key_id" => $field_def["db_key_id"], "help" => isset($field_def["help"]) ? $field_def["help"] : "" ); add_settings_field( // field ID $field_def["id"], // field title/label $field_def["label"], // rendering function $field_def["render_fn"], // name of page (see do_settings_section()) self::PAGE_SLUG, // "section ID" this field goes into (see first argument of add_settings_section()). $field_def["section_id"], // "$args" passed to "render_fn" when invoked $render_fn_args ); } // See http://ottopress.com/2009/wordpress-settings-api-tutorial/ and // https://codex.wordpress.org/Creating_Options_Pages public static function admin_init() { register_setting( // group label self::GROUP_LABEL_WIDGET, // name of the settings stored in DB. // Use http://yoursite.com/wp-admin/options.php to see all the options currently stored self::DB_ID_WIDGET, // validation function array("AllEarsOptions", "option_validate") ); add_settings_section( // section ID self::SECTION_ID_WIDGET, // section title. Note that this gets rendered as an

, so be careful about // what you use in render_options_page() for the page title... "Widget settings", // section description rendering function // For the "callable syntax" for static functions, see here: https://developer.wordpress.org/reference/functions/add_action/ array("AllEarsOptions", "section_widget_text"), // name of page (see do_settings_section()) self::PAGE_SLUG ); foreach(self::WIDGET_FIELDS as $def) { self::add_settings_field($def); } } public static function render_options_page() { /* "" is the "short tag" version for "" */ ?>

post_type); // Check if the current user has permission to edit the post if(!current_user_can($post_type->cap->edit_post, $post_id)) { return; } // Get the data in the meta box and sanitize it // Note that get_post_meta() uses the empty string to mean "key not found", so it's // safe to do the same here, we use the empty string to mean "no value". $box_meta_aec = self::get_box_url_value(self::INPUT_ID_AEC); $box_meta_debug = self::get_box_checkbox_value(self::INPUT_ID_DEBUG); if($box_meta_aec !== false) { self::update_meta($post_id, self::META_KEY_AEC, $box_meta_aec); } // Else silently discard the invalid value self::update_meta($post_id, self::META_KEY_DEBUG, $box_meta_debug); if(AllEarsOptions::is_widget_auto()) { // Since we display this option only when the site-wide "auto" flag is on, // we can't try to save the value unless "auto" is on as well. $box_meta_disable_auto = self::get_box_checkbox_value(self::INPUT_ID_DISABLE_AUTO); self::update_meta($post_id, self::META_KEY_DISABLE_AUTO, $box_meta_disable_auto); } } /* Display the post meta box. */ public static function render_box($post) { wp_nonce_field(basename( __FILE__ ), self::NONCE); $aec = esc_attr(self::get_aec_url($post->ID)); $debug = self::get_debug($post->ID); $debug_checked = ($debug) ? "checked" : ""; $widget_key = AllEarsOptions::get_widget_key(); $widget_warning = "
"; if($widget_key === "") { $submenu_url = menu_page_url(AllEarsOptions::PAGE_SLUG , false); $widget_warning = "

Warning: the following settings are going to be ignored while no widget key is set. ". "Go to Settings->allEars to set your widget key.

"; } ?>


ID); $disable_auto_checked = ($disable_auto) ? "checked" : ""; ?> />

/>