WordPress 插件设置未保存并在提交时被重定向到 /wp-admin/options.php(生成 404)

WordPress plugin settings not saving and getting redirected to /wp-admin/options.php (which generates a 404) on submit

我正在使用 https://github.com/nadeem-khan/WordPress-Plugin-Template 作为入门模板创建 WordPress。

这是我的设置 class 文件,名为 'class-xyz.php':

<?php
if (!defined('ABSPATH'))
    exit;

class Xyz_Settings {

    /**
     * The single instance of Xyz_Settings.
     * @var     object
     * @access  private
     * @since   1.0.0
     */
    private static $_instance = null;

    /**
     * The main plugin object.
     * @var     object
     * @access  public
     * @since   1.0.0
     */
    public $parent = null;

    /**
     * Prefix for plugin settings.
     * @var     string
     * @access  public
     * @since   1.0.0
     */
    public $base = '';

    /**
     * Available settings for plugin.
     * @var     array
     * @access  public
     * @since   1.0.0
     */
    public $settings = array();

    public function __construct($parent) {
        $this->parent = $parent;

        $this->base = 'xyzsms_';

        // Initialise settings
        add_action('admin_init', array($this, 'init_settings'));

        // Register plugin settings
        add_action('admin_init', array($this, 'register_settings'));

        // Add settings page to menu
        add_action('admin_menu', array($this, 'add_menu_item'));

        // Add settings link to plugins page
        add_filter('plugin_action_links_' . plugin_basename($this->parent->file), array($this, 'add_settings_link'));
    }

    /**
     * Initialise settings
     * @return void
     */
    public function init_settings() {
        $this->settings = $this->settings_fields();
    }

    /**
     * Add settings page to admin menu
     * @return void
     */
    public function add_menu_item() {
        $page = add_options_page(__('XYZ Settings', 'xyz-template'), __('XYZ Settings', 'xyz-template'), 'manage_options', 'xyz_template_settings', array($this, 'settings_page'));
        add_action('admin_print_styles-' . $page, array($this, 'settings_assets'));
    }

    /**
     * Load settings JS & CSS
     * @return void
     */
    public function settings_assets() {

        // We're including the farbtastic script & styles here because they're needed for the colour picker
        // If you're not including a colour picker field then you can leave these calls out as well as the farbtastic dependency for the wpt-admin-js script below
        wp_enqueue_style('farbtastic');
        wp_enqueue_script('farbtastic');

        // We're including the WP media scripts here because they're needed for the image upload field
        // If you're not including an image upload then you can leave this function call out
        wp_enqueue_media();

        wp_register_script($this->parent->_token . '-settings-js', $this->parent->assets_url . 'js/settings' . $this->parent->script_suffix . '.js', array('farbtastic', 'jquery'), '1.0.0');
        wp_enqueue_script($this->parent->_token . '-settings-js');
    }

    /**
     * Add settings link to plugin list table
     * @param  array $links Existing links
     * @return array        Modified links
     */
    public function add_settings_link($links) {
        $settings_link = '<a href="options-general.php?page=xyz_template_settings">' . __('Settings', 'xyz-template') . '</a>';
        array_push($links, $settings_link);
        return $links;
    }

    /**
     * Build settings fields
     * @return array Fields to be displayed on settings page
     */
    private function settings_fields() {
        $settings['social-bar-position'] = array(
            'title' => __('Bar Position', 'xyz-plugin-bar'),
            'fields' => array(
                array(
                    'id' => 'xyz_position',
                    'label' => __('Position', 'xyz-plugin-bar'),
                    'description' => __('Select the position', 'xyz-plugin-bar'),
                    'type' => 'radio',
                    'options' => array('left' => 'Left', 'right' => 'Right'),
                    'default' => 'left'
                ),
                array(
                    'id' => 'xyz_margin_top',
                    'label' => __('Bar Margin Top', 'xyz-plugin-bar'),
                    'description' => __('Define the margin top', 'xyz-plugin-bar'),
                    'type' => 'text',
                    'placeholder' => __('Enter margin.', 'xyz-plugin-bar'),
                    'default' => '200px'
                )
            )
        );
        $settings['social-networks-bar'] = array(
            'title' => __('Social Networks', 'xyz-plugin-bar'),
            'fields' => array(
                array(
                    'id' => 'xyz_networks',
                    'label' => __('Social', 'xyz-plugin-bar'),
                    'description' => __('Select social.', 'xyz-plugin-bar'),
                    'type' => 'checkbox_multi',
                    'options' => array(
                        'facebook' => 'Facebook'
                    )
                )
            )
        );

        $settings['links-bar'] = array(
            'title' => __('Links', 'xyz-plugin-bar'),
            'description' => __('Enter the Urls for all the selected social networks:', 'xyz-plugin-bar'),
            'fields' => array(
                array(
                    'id' => 'facebook_url',
                    'label' => __('Facebook URL', 'xyz-plugin-bar'),
                    'description' => __('Enter the URL for Facebook.', 'xyz-plugin-bar'),
                    'type' => 'text',
                    'placeholder' => __('Enter Facebook URL', 'xyz-plugin-bar'),
                    'default' => ''
                )
            )
        );

        $settings['custom-classes-bar'] = array(
            'title' => __('Custom Classes', 'xyz-plugin-bar'),
            'description' => __('Add custom classes in the main parent container of the bar (will affect shortcodes as well):', 'xyz-plugin-bar'),
            'fields' => array(
                array(
                    'id' => 'xyz_custom_class',
                    'label' => __('Add Custom Classes in the Bar Parent Container'),
                    'description' => __('Add your own custom CSS class (like customclass1 customclass2). Leave empty if default styling is desired.', 'xyz-plugin-bar'),
                    'type' => 'textarea',
                    'default' => '',
                    'placeholder' => __('Add custom classes like .custom_class1 .custom_class2 .custom_class3 to the main container of the social bar.', 'xyz-plugin-bar')
                )
            )
        );

        $settings['social-bar-custom-colors'] = array(
            'title' => __('Bar Icons Colors', 'xyz-plugin-bar'),
            'description' => __('Select the colors', 'xyz-plugin-bar'),
            'fields' => array(
                array(
                    'id' => 'bar_facebook_icon_custom_color',
                    'label' => __('Facebook Icon Color', 'xyz-plugin-bar'),
                    'description' => __('Default color: #FEFEFE ', 'xyz-plugin-bar'),
                    'type' => 'color',
                    'default' => '#FEFEFE'
                )
            )
        );


        $settings['add-custom-css-bar'] = array(
            'title' => __('Add CSS', 'xyz-plugin-bar'),
            'label' => __('Add CSS', 'xyz-plugin-bar'),
            'description' => __('Paste your CSS', 'xyz-plugin-bar'),
            'fields' => array(
                array(
                    'id' => 'xyz_add_custom_css',
                    'label' => __('Custom CSS', 'wordpress'),
                    'description' => __('Do not style', 'xyz-plugin-bar'),
                    'type' => 'textarea',
                    'default' => '',
                    'placeholder' => __('Do not style .', 'xyz-plugin-bar')
                )
            )
        );

        $settings = apply_filters('xyz_fields', $settings);

        return $settings;
    }

    /**
     * Register plugin settings
     * @return void
     */
    public function register_settings() {
        if (is_array($this->settings)) {
            foreach ($this->settings as $section => $data) {

                // Add section to page
                add_settings_section($section, $data['title'], array($this, 'settings_section'), 'xyz_template_settings');

                foreach ($data['fields'] as $field) {

                    // Validation callback for field
                    $validation = '';
                    if (isset($field['callback'])) {
                        $validation = $field['callback'];
                    }

                    // Register field
                    $option_name = $this->base . $field['id'];
                    register_setting('xyz_template_settings', $option_name, $validation);

                    // Add field to page
                    add_settings_field($field['id'], $field['label'], array($this, 'display_field'), 'xyz_template_settings', $section, array('field' => $field));
                }
            }
        }
    }

    public function settings_section($section) {
        $html = '<p> ' . $this->settings[$section['id']]['description'] . '</p>' . "\n";
        echo $html;
    }

    /**
     * Generate HTML for displaying fields
     * @param  array $args Field data
     * @return void
     */
    public function display_field($args) {

        $field = $args['field'];

        $html = '';

        $option_name = $this->base . $field['id'];
        $option = get_option($option_name);

        $data = '';
        if (isset($field['default'])) {
            $data = $field['default'];
            if ($option) {
                $data = $option;
            }
        }

        switch ($field['type']) {

            case 'text':
            case 'password':
            case 'number':
                $html .= '<input id="' . esc_attr($field['id']) . '" type="' . $field['type'] . '" name="' . esc_attr($option_name) . '" placeholder="' . esc_attr($field['placeholder']) . '" value="' . $data . '"/>' . "\n";
                break;

            case 'text_secret':
                $html .= '<input id="' . esc_attr($field['id']) . '" type="text" name="' . esc_attr($option_name) . '" placeholder="' . esc_attr($field['placeholder']) . '" value=""/>' . "\n";
                break;

            case 'textarea':
                $html .= '<textarea id="' . esc_attr($field['id']) . '" rows="5" cols="50" name="' . esc_attr($option_name) . '" placeholder="' . esc_attr($field['placeholder']) . '">' . $data . '</textarea><br/>' . "\n";
                break;

            case 'checkbox':
                $checked = '';
                if ($option && 'on' == $option) {
                    $checked = 'checked="checked"';
                }
                $html .= '<input id="' . esc_attr($field['id']) . '" type="' . $field['type'] . '" name="' . esc_attr($option_name) . '" ' . $checked . '/>' . "\n";
                break;

            case 'checkbox_multi':
                foreach ($field['options'] as $k => $v) {
                    $checked = false;
                    if (in_array($k, $data)) {
                        $checked = true;
                    }
                    $html .= '<label for="' . esc_attr($field['id'] . '_' . $k) . '"><input type="checkbox" ' . checked($checked, true, false) . ' name="' . esc_attr($option_name) . '[]" value="' . esc_attr($k) . '" id="' . esc_attr($field['id'] . '_' . $k) . '" /> ' . $v . '</label> ';
                }
                break;

            case 'radio':
                foreach ($field['options'] as $k => $v) {
                    $checked = false;
                    if ($k == $data) {
                        $checked = true;
                    }
                    $html .= '<label for="' . esc_attr($field['id'] . '_' . $k) . '"><input type="radio" ' . checked($checked, true, false) . ' name="' . esc_attr($option_name) . '" value="' . esc_attr($k) . '" id="' . esc_attr($field['id'] . '_' . $k) . '" /> ' . $v . '</label> ';
                }
                break;

            case 'select':
                $html .= '<select name="' . esc_attr($option_name) . '" id="' . esc_attr($field['id']) . '">';
                foreach ($field['options'] as $k => $v) {
                    $selected = false;
                    if ($k == $data) {
                        $selected = true;
                    }
                    $html .= '<option ' . selected($selected, true, false) . ' value="' . esc_attr($k) . '">' . $v . '</option>';
                }
                $html .= '</select> ';
                break;

            case 'select_multi':
                $html .= '<select name="' . esc_attr($option_name) . '[]" id="' . esc_attr($field['id']) . '" multiple="multiple">';
                foreach ($field['options'] as $k => $v) {
                    $selected = false;
                    if (in_array($k, $data)) {
                        $selected = true;
                    }
                    $html .= '<option ' . selected($selected, true, false) . ' value="' . esc_attr($k) . '" />' . $v . '</label> ';
                }
                $html .= '</select> ';
                break;

            case 'image':
                $image_thumb = '';
                if ($data) {
                    $image_thumb = wp_get_attachment_thumb_url($data);
                }
                $html .= '<img id="' . $option_name . '_preview" class="image_preview" src="' . $image_thumb . '" /><br/>' . "\n";
                $html .= '<input id="' . $option_name . '_button" type="button" data-uploader_title="' . __('Upload an image', 'xyz-template') . '" data-uploader_button_text="' . __('Use image', 'xyz-template') . '" class="image_upload_button button" value="' . __('Upload new image', 'xyz-template') . '" />' . "\n";
                $html .= '<input id="' . $option_name . '_delete" type="button" class="image_delete_button button" value="' . __('Remove image', 'xyz-template') . '" />' . "\n";
                $html .= '<input id="' . $option_name . '" class="image_data_field" type="hidden" name="' . $option_name . '" value="' . $data . '"/><br/>' . "\n";
                break;

            case 'color':
                ?><div class="color-picker" style="position:relative;">
                    <input type="text" name="<?php esc_attr_e($option_name); ?>" class="color" value="<?php esc_attr_e($data); ?>" />
                    <div style="position:absolute;background:#FFF;z-index:99;border-radius:100%;" class="colorpicker"></div>
                </div>
                <?php
                break;
        }

        switch ($field['type']) {

            case 'checkbox_multi':
            case 'radio':
            case 'select_multi':
                $html .= '<br/><span class="description">' . $field['description'] . '</span>';
                break;

            default:
                $html .= '<label for="' . esc_attr($field['id']) . '"><span class="description">' . $field['description'] . '</span></label>' . "\n";
                break;
        }

        echo $html;
    }

    /**
     * Validate individual settings field
     * @param  string $data Inputted value
     * @return string       Validated value
     */
    public function validate_field($data) {
        if ($data && strlen($data) > 0 && $data != '') {
            $data = urlencode(strtolower(str_replace(' ', '-', $data)));
        }
        return $data;
    }

    /**
     * Load settings page content
     * @return void
     */
    public function settings_page() {

        // Build page HTML
        $html = '<div class="wrap" id="xyz_settings">' . "\n";
        $html .= '<h2>' . __('XYZ Settings', 'xyz-template') . '</h2>' . "\n";
        $html .= '<form method="post" action="options.php" enctype="multipart/form-data">' . "\n";

        // Setup navigation
        $html .= '<ul id="settings-sections" class="subsubsub hide-if-no-js">' . "\n";
        $html .= '<li><a class="tab all current" href="#all">' . __('All', 'xyz-template') . '</a></li>' . "\n";

        foreach ($this->settings as $section => $data) {
            $html .= '<li>| <a class="tab" href="#' . $section . '">' . $data['title'] . '</a></li>' . "\n";
        }

        $html .= '</ul>' . "\n";

        $html .= '<div class="clear"></div>' . "\n";

        // Get settings fields
        ob_start();
        settings_fields('xyz_template_settings');
        do_settings_sections('xyz_template_settings');
        $html .= ob_get_clean();

        $html .= '<p class="submit">' . "\n";
        $html .= '<input name="Submit" type="submit" class="button-primary" value="' . esc_attr(__('Save Settings', 'xyz-template')) . '" />' . "\n";
        $html .= '</p>' . "\n";
        $html .= '</form>' . "\n";
        $html .= '</div>' . "\n";

        echo $html;
    }

    /**
     * Main Xyz_Settings Instance
     *
     * Ensures only one instance of Xyz_Settings is loaded or can be loaded.
     *
     * @since 1.0.0
     * @static
     * @see Xyz()
     * @return Main Xyz_Settings instance
     */
    public static function instance($parent) {
        if (is_null(self::$_instance)) {
            self::$_instance = new self($parent);
        }
        return self::$_instance;
    }

// End instance()

    /**
     * Cloning is forbidden.
     *
     * @since 1.0.0
     */
    public function __clone() {
        _doing_it_wrong(__FUNCTION__, __('Cheatin&#8217; huh?'), $this->parent->_version);
    }

// End __clone()

    /**
     * Unserializing instances of this class is forbidden.
     *
     * @since 1.0.0
     */
    public function __wakeup() {
        _doing_it_wrong(__FUNCTION__, __('Cheatin&#8217; huh?'), $this->parent->_version);
    }

// End __wakeup()
}

所有设置字段都正确显示,但是一旦按下提交按钮,就会生成 404,并且不会保存任何设置。我在这里错过了什么?

更新: 下面是在另一个文件中初始化 Xyz_Settings class 的方式:

function Xyz_bar_init () {
    $instance = Xyz_bar::instance( __FILE__, '1.0.0' );
    if( is_null( $instance->settings ) ) {
        $instance->settings = Xyz_Settings::instance( $instance );
    }
    return $instance;
}

Xyz_bar_init();

UPDATE(2): 我已经激活了我自己的插件的一个非常精简的版本,只有 2-3 个设置选项,它似乎工作得很好(因为值越来越saved/retrieved 在数据库中正确)。设置选项的剪切数量是否会导致具有 200 多个选项的完整插件出现问题(请注意,我使用的是资源有限的共享主机)?

我已经对此文件进行了更改,现在 post 显示:- class-wordpress-plugin-template-post_type.php 你的代码:-

// Post type name and labels
        $this->post_type = 'post_type';
        $this->plural = _x( 'Posts', 'post type general name' , 'wordpress-plugin-template' );
        $this->single = _x( 'Post', 'post type singular name' , 'wordpress-plugin-template' );

我的代码:-

// Post type name and labels
        $this->post_type = 'post_type1'; // change your value here
        $this->plural = _x( 'Your Posts', 'post type general name' , 'wordpress-plugin-template' );
        $this->single = _x( 'Post', 'post type singular name' , 'wordpress-plugin-template' );

转到 admin/permalinks 并再次保存您的设置,无需更改任何内容。十分之九这将在添加自定义设置后解决 404 问题(自定义模板也是如此)。

您也可以在构造函数的末尾添加“flush_rewrite_rules();”来避免这一步。

[编辑] 好吧,既然这样...您的表单操作仅指向 "options.php"。这意味着您的插件将在插件或根文件夹中查找 options.php。应该是 site_url()."/wp-admin/options.php".

如果要保存大量选项,请确保不要使 max_input_vars 限制饱和,该限制在 PHP 5.3.9 中引入以限制可接受的输入变量的数量- 来自 PHP 文档:

How many input variables may be accepted (limit is applied to $_GET, $_POST and $_COOKIE superglobal separately). Use of this directive mitigates the possibility of denial of service attacks which use hash collisions.

您应该可以在 php.ini 中根据需要修改此值。

另外记得检查你的错误日志。