“Erro: página de opções não encontrada” no envio da página de configurações para um plug-in OOP

19

Estou desenvolvendo um plug-in usando o repositório Boilerplate de Tom McFarlin como modelo, que utiliza práticas de POO. Eu tenho tentado descobrir exatamente por que não consigo enviar corretamente minhas configurações. Tentei definir o atributo action como uma string vazia, conforme sugerido em outra pergunta por aqui, mas isso não ajudou ...

Abaixo está a configuração geral do código que estou usando ...

O formulário (/views/admin.php):

<div class="wrap">
    <h2><?php echo esc_html( get_admin_page_title() ); ?></h2>
    <form action="options.php" method="post">
        <?php
        settings_fields( $this->plugin_slug );
        do_settings_sections( $this->plugin_slug );
        submit_button( 'Save Settings' );
        ?>
    </form>
</div>

Para o código a seguir, suponha que todos os retornos de chamada para add_settings_field () e add_settings_section () existam, exceto para 'option_list_selection'.

A classe Admin do plug-in (/ {plugin_name} -class-admin.php):

namespace wp_plugin_name;

class Plugin_Name_Admin
{
    /**
     * Note: Some portions of the class code and method functions are missing for brevity
     * Let me know if you need more information...
     */

    private function __construct()
    {
        $plugin              = Plugin_Name::get_instance();

        $this->plugin_slug   = $plugin->get_plugin_slug();
        $this->friendly_name = $plugin->get_name(); // Get "Human Friendly" presentable name

        // Adds all of the options for the administrative settings
        add_action( 'admin_init', array( $this, 'plugin_options_init' ) );

        // Add the options page and menu item
        add_action( 'admin_menu', array( $this, 'add_plugin_admin_menu' ) );


    }

    public function add_plugin_admin_menu()
    {

        // Add an Options Page
        $this->plugin_screen_hook_suffix =
        add_options_page(
            __( $this->friendly_name . " Options", $this->plugin_slug ),
            __( $this->friendly_name, $this->plugin_slug ),
            "manage_options", 
            $this->plugin_slug,
            array( $this, "display_plugin_admin_page" )
        );

    }

    public function display_plugin_admin_page()
    {
        include_once( 'views/admin.php' );
    }

    public function plugin_options_init()
    {
        // Update Settings
        add_settings_section(
            'maintenance',
            'Maintenance',
            array( $this, 'maintenance_section' ),
            $this->plugin_slug
        );

        // Check Updates Option
        register_setting( 
            'maintenance',
            'plugin-name_check_updates',
            'wp_plugin_name\validate_bool'
        );

        add_settings_field(
            'check_updates',
            'Should ' . $this->friendly_name . ' Check For Updates?',
            array( $this, 'check_updates_field' ),
            $this->plugin_slug,
            'maintenance'
        );

        // Update Period Option
        register_setting(
            'maintenance',
            'plugin-name_update_period',
            'wp_plugin_name\validate_int'
        );

        add_settings_field(
            'update_frequency',
            'How Often Should ' . $this->friendly_name . ' Check for Updates?',
            array( $this, 'update_frequency_field' ),
            $this->plugin_slug,
            'maintenance'
        );

        // Plugin Option Configurations
        add_settings_section(
            'category-option-list', 'Widget Options List',
            array( $this, 'option_list_section' ),
            $this->plugin_slug
        );
    }
}

Algumas atualizações solicitadas:

Alterando o atributo action para:

<form action="../../options.php" method="post">

... simplesmente resulta em um erro 404. A seguir, é apresentado o trecho dos logs do Apache. Observe que os scripts padrão do WordPress e as filas CSS são removidos:

# Changed to ../../options.php
127.0.0.1 - - [01/Apr/2014:15:59:43 -0400] "GET /wp-admin/options-general.php?page=pluginname-widget HTTP/1.1" 200 18525
127.0.0.1 - - [01/Apr/2014:15:59:43 -0400] "GET /wp-content/plugins/PluginName/admin/assets/css/admin.css?ver=0.1.1 HTTP/1.1" 304 -
127.0.0.1 - - [01/Apr/2014:15:59:43 -0400] "GET /wp-content/plugins/PluginName/admin/assets/js/admin.js?ver=0.1.1 HTTP/1.1" 304 -
127.0.0.1 - - [01/Apr/2014:15:59:52 -0400] "POST /options.php HTTP/1.1" 404 1305
127.0.0.1 - - [01/Apr/2014:16:00:32 -0400] "POST /options.php HTTP/1.1" 404 1305

#Changed to options.php
127.0.0.1 - - [01/Apr/2014:16:00:35 -0400] "GET /wp-admin/options-general.php?page=pluginname-widget HTTP/1.1" 200 18519
127.0.0.1 - - [01/Apr/2014:16:00:35 -0400] "GET /wp-content/plugins/PluginName/admin/assets/css/admin.css?ver=0.1.1 HTTP/1.1" 304 -
127.0.0.1 - - [01/Apr/2014:16:00:35 -0400] "GET /wp-content/plugins/PluginName/admin/assets/js/admin.js?ver=0.1.1 HTTP/1.1" 304 -
127.0.0.1 - - [01/Apr/2014:16:00:38 -0400] "POST /wp-admin/options.php HTTP/1.1" 500 2958

O arquivo php-errors.log e o arquivo debug.log quando WP_DEBUG é verdadeiro estão vazios.

A classe de plug-in (/{plugin-name}-class.php)

namespace wp_plugin_name;

class Plugin_Name
{
    const VERSION = '1.1.2';
    const TABLE_VERSION = 1;
    const CHECK_UPDATE_DEFAULT = 1;
    const UPDATE_PERIOD_DEFAULT = 604800;

    protected $plugin_slug = 'pluginname-widget';
    protected $friendly_name = 'PluginName Widget';

    protected static $instance = null;

    private function __construct()
    {

        // Load plugin text domain
        add_action( 'init',
                    array(
            $this,
            'load_plugin_textdomain' ) );

        // Activate plugin when new blog is added
        add_action( 'wpmu_new_blog',
                    array(
            $this,
            'activate_new_site' ) );

        // Load public-facing style sheet and JavaScript.
        add_action( 'wp_enqueue_scripts',
                    array(
            $this,
            'enqueue_styles' ) );
        add_action( 'wp_enqueue_scripts',
                    array(
            $this,
            'enqueue_scripts' ) );

        /* Define custom functionality.
         * Refer To http://codex.wordpress.org/Plugin_API#Hooks.2C_Actions_and_Filters
         */

    }

    public function get_plugin_slug()
    {
        return $this->plugin_slug;
    }

    public function get_name()
    {
        return $this->friendly_name;
    }

    public static function get_instance()
    {

        // If the single instance hasn't been set, set it now.
        if ( null == self::$instance )
        {
            self::$instance = new self;
        }

        return self::$instance;

    }

    /**
     * The member functions activate(), deactivate(), and update() are very similar.
     * See the Boilerplate plugin for more details...
     *
     */

    private static function single_activate()
    {
        if ( !current_user_can( 'activate_plugins' ) )
            return;

        $plugin_request = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';

        check_admin_referer( "activate-plugin_$plugin_request" );

        /**
         *  Test to see if this is a fresh installation
         */
        if ( get_option( 'plugin-name_version' ) === false )
        {
            // Get the time as a Unix Timestamp, and add one week
            $unix_time_utc = time() + Plugin_Name::UPDATE_PERIOD_DEFAULT;

            add_option( 'plugin-name_version', Plugin_Name::VERSION );
            add_option( 'plugin-name_check_updates',
                        Plugin_Name::CHECK_UPDATE_DEFAULT );
            add_option( 'plugin-name_update_frequency',
                        Plugin_Name::UPDATE_PERIOD_DEFAULT );
            add_option( 'plugin-name_next_check', $unix_time_utc );

            // Create options table
            table_update();

            // Let user know PluginName was installed successfully
            is_admin() && add_filter( 'gettext', 'finalization_message', 99, 3 );
        }
        else
        {
            // Let user know PluginName was activated successfully
            is_admin() && add_filter( 'gettext', 'activate_message', 99, 3 );
        }

    }

    private static function single_update()
    {
        if ( !current_user_can( 'activate_plugins' ) )
            return;

        $plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';

        check_admin_referer( "activate-plugin_{$plugin}" );

        $cache_plugin_version         = get_option( 'plugin-name_version' );
        $cache_table_version          = get_option( 'plugin-name_table_version' );
        $cache_deferred_admin_notices = get_option( 'plugin-name_admin_messages',
                                                    array() );

        /**
         * Find out what version of our plugin we're running and compare it to our
         * defined version here
         */
        if ( $cache_plugin_version > self::VERSION )
        {
            $cache_deferred_admin_notices[] = array(
                'error',
                "You seem to be attempting to revert to an older version of " . $this->get_name() . ". Reverting via the update feature is not supported."
            );
        }
        else if ( $cache_plugin_version === self::VERSION )
        {
            $cache_deferred_admin_notices[] = array(
                'updated',
                "You're already using the latest version of " . $this->get_name() . "!"
            );
            return;
        }

        /**
         * If we can't determine what version the table is at, update it...
         */
        if ( !is_int( $cache_table_version ) )
        {
            update_option( 'plugin-name_table_version', TABLE_VERSION );
            table_update();
        }

        /**
         * Otherwise, we'll just check if there's a needed update
         */
        else if ( $cache_table_version < TABLE_VERSION )
        {
            table_update();
        }

        /**
         * The table didn't need updating.
         * Note we cannot update any other options because we cannot assume they are still
         * the defaults for our plugin... ( unless we stored them in the db )
         */

    }

    private static function single_deactivate()
    {

        // Determine if the current user has the proper permissions
        if ( !current_user_can( 'activate_plugins' ) )
            return;

        // Is there any request data?
        $plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';

        // Check if the nonce was valid
        check_admin_referer( "deactivate-plugin_{$plugin}" );

        // We'll, technically the plugin isn't included when deactivated so...
        // Do nothing

    }

    public function load_plugin_textdomain()
    {

        $domain = $this->plugin_slug;
        $locale = apply_filters( 'plugin_locale', get_locale(), $domain );

        load_textdomain( $domain,
                         trailingslashit( WP_LANG_DIR ) . $domain . '/' . $domain . '-' . $locale . '.mo' );
        load_plugin_textdomain( $domain, FALSE,
                                basename( plugin_dir_path( dirname( __FILE__ ) ) ) . '/languages/' );

    }

    public function activate_message( $translated_text, $untranslated_text,
                                      $domain )
    {
        $old = "Plugin <strong>activated</strong>.";
        $new = FRIENDLY_NAME . " was  <strong>successfully activated</strong> ";

        if ( $untranslated_text === $old )
            $translated_text = $new;

        return $translated_text;

    }

    public function finalization_message( $translated_text, $untranslated_text,
                                          $domain )
    {
        $old = "Plugin <strong>activated</strong>.";
        $new = "Captain, The Core is stable and PluginName was <strong>successfully installed</strong> and ready for Warp speed";

        if ( $untranslated_text === $old )
            $translated_text = $new;

        return $translated_text;

    }

}

Referências:

gate_engineer
fonte
A descrição da recompensa informa: "Forneça algumas informações sobre as melhores práticas " . Usando singletons com construtores privados e várias ações dentro deles: más práticas e difíceis de testar, mas não é culpa sua.
gmazzap
1
use ../../options.php depois de testar seu código.
perfil completo de ravi patel
Você pode mostrar get_plugin_slug ().
vancoder
@vancoder Eu editei o post acima com as informações relevantes ...
gate_engineer
Por que existem barras invertidas em seus retornos de chamada de higienização em suas configurações de registro? Eu não acho que isso funcionaria.
Bjorn

Respostas:

21

Erro "Erro: página de opções não encontrada"

Esse é um problema conhecido na API de configurações do WP. Havia um ingresso aberto anos atrás, e foi marcado como resolvido - mas o bug persiste nas últimas versões do WordPress. Isto é o que a página do Codex (agora removida) disse sobre isso :

A "Erro: página de opções não encontrada". problema (incluindo uma solução e explicação):

O problema é que o filtro 'whitelist_options' não possui o índice certo para seus dados. É aplicado no options.php # 98 (WP 3.4).

register_settings()adiciona seus dados ao global $new_whitelist_options. Isso então é mesclado com o global $whitelist_optionsdentro do option_update_filter()(resp. add_option_whitelist()) Retorno de chamada (s). Esses retornos de chamada adicionam seus dados ao global $new_whitelist_optionscom o $option_groupíndice as. Quando você encontra "Erro: página de opções não encontrada". isso significa que seu índice não foi reconhecido. A coisa enganosa é que o primeiro argumento é usado como índice e nomeado $options_group, quando o check-in real options.php # 112 acontece contra $options_page, que é o $hook_suffix, do qual você obtém o valor @return add_submenu_page().

Em suma, uma solução fácil é fazer a $option_groupcorrespondência $option_name. Outra causa para esse erro é ter um valor inválido para o $pageparâmetro ao chamar add_settings_section( $id, $title, $callback, $page )ou add_settings_field( $id, $title, $callback, $page, $section, $args ).

Dica: $pagedeve corresponder $menu_slugna Referência de Função / adicionar página de tema.

Correção simples

Usar o nome da página personalizada (no seu caso $this->plugin_slug:) como seu ID de seção contornaria o problema. No entanto, todas as suas opções teriam que estar contidas em uma única seção.

Solução

Para uma solução mais robusta, faça as seguintes alterações na sua Plugin_Name_Adminclasse:

Adicione ao construtor:

// Tracks new sections for whitelist_custom_options_page()
$this->page_sections = array();
// Must run after wp's `option_update_filter()`, so priority > 10
add_action( 'whitelist_options', array( $this, 'whitelist_custom_options_page' ),11 );

Adicione estes métodos:

// White-lists options on custom pages.
// Workaround for second issue: http://j.mp/Pk3UCF
public function whitelist_custom_options_page( $whitelist_options ){
    // Custom options are mapped by section id; Re-map by page slug.
    foreach($this->page_sections as $page => $sections ){
        $whitelist_options[$page] = array();
        foreach( $sections as $section )
            if( !empty( $whitelist_options[$section] ) )
                foreach( $whitelist_options[$section] as $option )
                    $whitelist_options[$page][] = $option;
            }
    return $whitelist_options;
}

// Wrapper for wp's `add_settings_section()` that tracks custom sections
private function add_settings_section( $id, $title, $cb, $page ){
    add_settings_section( $id, $title, $cb, $page );
    if( $id != $page ){
        if( !isset($this->page_sections[$page]))
            $this->page_sections[$page] = array();
        $this->page_sections[$page][$id] = $id;
    }
}

E a mudança add_settings_section()chama a: $this->add_settings_section().


Outras notas no seu código

  • Seu código de formulário está correto. Seu formulário deve ser enviado para options.php, conforme indicado por @Chris_O e conforme indicado na documentação da API de configurações do WP .
  • O namespacing tem suas vantagens, mas pode tornar a depuração mais complexa e reduz a compatibilidade do seu código (requer PHP> = 5.3, outros plugins / temas que usam carregadores automáticos, etc.). Portanto, se não houver um bom motivo para colocar o namespace em seu arquivo, não. Você já está evitando conflitos de nomenclatura envolvendo seu código em uma classe. Torne seus nomes de classe mais específicos e traga seus validate()retornos de chamada para a classe como métodos públicos.
  • Comparando seu clichê de plug-in citado com seu código, parece que seu código é realmente baseado em uma bifurcação ou em uma versão antiga do clichê. Até os nomes de arquivos e caminhos são diferentes. Você pode migrar seu plug-in para a versão mais recente, mas observe que este modelo de plug-in pode não ser adequado às suas necessidades. Faz uso de singletons, que geralmente são desencorajados . Há casos em que o padrão singleton é sensato , mas isso deve ser uma decisão consciente, não a solução para ir.
Stephen M. Harris
fonte
1
É bom saber que há um bug na API. Eu sempre dou uma olhada no código que escrevo para encontrar bugs. Claro, isso pressupõe que eu sei uma coisa ou duas.
gate_engineer
Para qualquer pessoa que se depare
maysi 30/01
5

Acabei de encontrar este post enquanto procurava o mesmo problema. A solução é muito mais simples do que parece, porque a documentação é enganosa: em register_setting () o primeiro argumento nomeado $option_groupé o slug da sua página, não a seção na qual você deseja exibir a configuração.

No código acima, você deve usar

    // Update Settings
    add_settings_section(
        'maintenance', // section slug
        'Maintenance', // section title
        array( $this, 'maintenance_section' ), // section display callback
        $this->plugin_slug // page slug
    );

    // Check Updates Option
    register_setting( 
        $this->plugin_slug, // page slug, not the section slug
        'plugin-name_check_updates', // setting slug
        'wp_plugin_name\validate_bool' // invalid, should be an array of options, see doc for more info
    );

    add_settings_field(
        'plugin-name_check_updates', // setting slug
        'Should ' . $this->friendly_name . ' Check For Updates?', // setting title
        array( $this, 'check_updates_field' ), //setting display callback
        $this->plugin_slug, // page slug
        'maintenance' // section slug
    );
86Dev
fonte
Isso não está correto. Veja este exemplo de trabalho (não o meu) - gist.github.com/annalinneajohansson/5290405
Xdg
2

Ao registrar a página de opções com:

add_submenu_page( string $parent_slug, string $page_title, string $menu_title, string $capability, string $menu_slug, callable $function = '' )

E registrando configurações com

register_setting( string $option_group, string $option_name );

$option_group deve ser o mesmo que $menu_slug

Cafer Elgin
fonte
1

Eu tive o mesmo erro, mas entendi de uma maneira diferente:

// no actual code
// this failed
add_settings_field('id','title', /*callback*/ function($arguments) {
    // echo $htmlcode; 
    register_setting('option_group', 'option_name');
}), 'page', 'section');

Não sei por que isso aconteceu, mas parece que register_settingnão deveria estar no retorno de chamada deadd_settings_field

// no actual code
// this worked
add_settings_field('id','title', /*callback*/ function($arguments) {echo $htmlcode;}), 'page', 'section');
register_setting('option_group', 'option_name');

Eu espero que isso ajude

Um novo 1
fonte
0

Estou enfrentando esse problema também há alguns dias, esse erro parou quando eu coloquei nos comentários a linha de:

// settings_fields($this->plugin_slug);

Depois disso, estou redirecionando para options.php, mas ainda não consigo resolver o problema setting_fields.

G.Karles
fonte
eu consertei isso da função de validação !! ;)
G.Karles