"Error: Página de opciones no encontrada" en el envío de la página de configuración para un complemento OOP

19

Estoy desarrollando un complemento utilizando el repositorio Boilerplate de Tom McFarlin como plantilla, que utiliza prácticas de OOP. He estado tratando de averiguar exactamente por qué no puedo enviar mi configuración correctamente. Intenté establecer el atributo de acción en una cadena vacía como se sugiere en otra pregunta por aquí, pero eso no ha ayudado ...

A continuación se muestra la configuración del código general que estoy usando ...

El formulario (/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 el siguiente código, suponga que existen todas las devoluciones de llamada para add_settings_field () y add_settings_section (), excepto 'option_list_selection'.

La clase de administrador del complemento (/ {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
        );
    }
}

Algunas actualizaciones solicitadas:

Cambiar el atributo de acción a:

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

... simplemente da como resultado un error 404. Lo siguiente a continuación es el extracto de los registros de Apache. Tenga en cuenta que los scripts predeterminados de WordPress y las colas CSS se eliminan:

# 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

Tanto el archivo php-errors.log como el archivo debug.log cuando WP_DEBUG es verdadero están vacíos.

La clase de complemento (/{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;

    }

}

Referencias

gate_engineer
fuente
La descripción de la recompensa informa: "Proporcione información sobre las mejores prácticas " . Usar singletons con constructores privados y un montón de acciones dentro de ellos: malas prácticas y difíciles de probar, sin embargo, no es tu culpa.
gmazzap
1
use ../../options.php después de probar su código.
ravi patel
¿Puedes mostrar get_plugin_slug ().
vancoder 01 de
@vancoder He editado la publicación anterior con la información relevante ...
gate_engineer
¿Por qué hay barras diagonales inversas en sus devoluciones de llamadas de desinfección en sus configuraciones de registro? No creo que eso funcione.
Bjorn

Respuestas:

21

Error "Error: Página de opciones no encontrada"

Este es un problema conocido en la API de configuración de WP. Hubo un ticket abierto hace años, y se marcó como resuelto, pero el error persiste en las últimas versiones de WordPress. Esto es lo que la página del Codex (ahora eliminada) dijo sobre esto :

El "Error: página de opciones no encontrada". problema (incluida una solución y explicación):

El problema es que el filtro 'whitelist_options' no tiene el índice correcto para sus datos. Se aplica en options.php # 98 (WP 3.4).

register_settings()agrega sus datos a lo global $new_whitelist_options. Esto entonces se fusionó con el mundial $whitelist_optionsdentro de la option_update_filter()(resp. add_option_whitelist()) De devolución de llamada (s). Esas devoluciones de llamada agregan sus datos a la global $new_whitelist_optionscon el $option_groupíndice as. Cuando encuentre "Error: página de opciones no encontrada". significa que su índice no ha sido reconocido. Lo engañoso es que el primer argumento se usa como índice y se nombra $options_group, cuando ocurre el chequeo real en options.php # 112 $options_page, que es el $hook_suffixvalor del cual se obtiene el valor @return add_submenu_page().

En resumen, una solución fácil es hacer $option_groupcoincidir $option_name. Otra causa de este error es tener un valor no válido para el $pageparámetro cuando se llama a add_settings_section( $id, $title, $callback, $page )o add_settings_field( $id, $title, $callback, $page, $section, $args ).

Sugerencia: $pagedebe coincidir $menu_slugdesde la página de referencia de funciones / agregar tema.

Arreglo simple

Usar el nombre de la página personalizada (en su caso:) $this->plugin_slugcomo su ID de sección solucionaría el problema. Sin embargo, todas sus opciones tendrían que estar contenidas en una sola sección.

Solución

Para una solución más robusta, realice estos cambios en su Plugin_Name_Adminclase:

Añadir al constructor:

// 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 );

Agregue estos 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;
    }
}

Y el cambio add_settings_section()llama a: $this->add_settings_section().


Otras notas sobre tu código

  • Su código de formulario es correcto. Su formulario debe enviarse a options.php, como me señaló @Chris_O, y como se indica en la documentación de la API de configuración de WP .
  • El espacio de nombres tiene sus ventajas, pero puede hacer que la depuración sea más compleja y reduce la compatibilidad de su código (requiere PHP> = 5.3, otros complementos / temas que usan cargadores automáticos, etc.). Entonces, si no hay una buena razón para espaciar el nombre de su archivo, no lo haga. Ya está evitando conflictos de nombres al envolver su código en una clase. Haga que los nombres de sus clases sean más específicos y traiga sus validate()devoluciones de llamadas a la clase como métodos públicos.
  • Al comparar su plantilla de plugin citada con su código, parece que su código se basa en una bifurcación o en una versión anterior de la placa de calculo. Incluso los nombres de archivo y las rutas son diferentes. Puede migrar su complemento a la última versión, pero tenga en cuenta que este plugin repetitivo puede no ser adecuado para sus necesidades. Hace uso de singletons, que generalmente se desaconsejan . Hay casos en los que el patrón singleton es sensible , pero esto debería ser una decisión consciente, no la solución de ir a.
Stephen M. Harris
fuente
1
Es bueno saber que hay un error en la API. Siempre intento revisar el código que escribo en busca de errores que pueda introducir. Por supuesto, eso supone que sé una cosa o dos.
gate_engineer
Para cualquiera que se encuentre con este problema: eche un vistazo al ejemplo de OOP en el codex: codex.wordpress.org/Creating_Options_Pages#Example_.232
maysi
5

Acabo de encontrar esta publicación mientras buscaba el mismo problema. La solución es mucho más simple de lo que parece porque la documentación es engañosa: en register_setting (), el primer argumento nombrado $option_groupes su babosa de página, no la sección en la que desea mostrar la configuración.

En el código de arriba debes 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
fuente
Esto no es correcto. Vea este ejemplo de trabajo (no el mío) - gist.github.com/annalinneajohansson/5290405
Xdg
2

Al registrar la página de opciones con:

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

Y registrando configuraciones con

register_setting( string $option_group, string $option_name );

$option_group debería ser igual que $menu_slug

Cafer Elgin
fuente
1

Tuve el mismo error pero lo obtuve de una manera diferente:

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

No sé por qué sucedió esto, pero parece que register_settingno debería estar en la devolución de llamada 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');

espero que esto ayude

Un nuevo 1
fuente
0

También he enfrentado este problema durante algunos días, este error se detuvo cuando puse en comentarios la línea de:

// settings_fields($this->plugin_slug);

después de eso estoy redirigiendo a options.php pero aún no puedo resolver el problema setting_fields.

G.Karles
fuente
Lo arreglé desde la función de validación !! ;)
G.Karles