«Erreur: page d'options introuvable» sur la soumission de la page des paramètres pour un plug-in OOP

19

Je développe un plugin en utilisant le référentiel Boilerplate de Tom McFarlin comme modèle, qui utilise les pratiques de POO. J'ai essayé de comprendre exactement pourquoi je ne pouvais pas soumettre correctement mes paramètres. J'ai essayé de définir l'attribut action sur une chaîne vide comme suggéré dans une autre question ici, mais cela n'a pas aidé ...

Voici la configuration générale du code que j'utilise ...

Le formulaire (/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>

Pour le code suivant, supposez que tous les rappels pour add_settings_field () et add_settings_section () existent, à l'exception de 'option_list_selection'.

La classe Admin du plugin (/ {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
        );
    }
}

Quelques mises à jour demandées:

Modification de l'attribut d'action en:

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

... entraîne simplement une erreur 404. Ce qui suit est l'extrait des journaux Apache. Notez que les scripts WordPress et les files d'attente CSS par défaut sont supprimés:

# 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

Le fichier php-errors.log et le fichier debug.log lorsque WP_DEBUG est vrai sont tous deux vides.

La classe de plugin (/{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;

    }

}

Les références:

gate_engineer
la source
La description de la prime rapporte: "Veuillez fournir quelques informations sur les meilleures pratiques " . Utiliser des singletons avec des constructeurs privés et un tas d'actions en leur sein: mauvaise pratique et difficile à tester, pas de votre faute, cependant.
gmazzap
1
utilisez ../../options.php après avoir testé votre code.
ravi patel
Pouvez-vous s'il vous plaît montrer get_plugin_slug ().
vancoder
@vancoder J'ai édité le post ci-dessus avec les informations pertinentes ...
gate_engineer
Pourquoi y a-t-il des barres obliques inverses dans vos rappels de nettoyage dans vos paramètres de registre? Je ne pense pas que cela fonctionnerait.
Bjorn

Réponses:

21

Bug «Erreur: page d'options introuvable»

Il s'agit d'un problème connu dans l'API des paramètres WP. Il y avait un billet il y a ouvert des années, et il a été marqué comme résolu - mais les bugs dans de persiste les dernières versions de WordPress. Voici ce que la page du Codex (maintenant supprimée) a dit à ce sujet :

La page "Erreur: options introuvable." problème (y compris une solution et une explication):

Le problème est alors que le filtre 'whitelist_options' n'a pas le bon index pour vos données. Il est appliqué sur options.php # 98 (WP 3.4).

register_settings()ajoute vos données au global $new_whitelist_options. Cela se a ensuite fusionné avec le monde à l' $whitelist_optionsintérieur du option_update_filter()(resp. add_option_whitelist()Callback) (s). Ces rappels ajoutent vos données au global $new_whitelist_optionsavec l' $option_groupindex as. Lorsque vous rencontrez "Erreur: page d'options introuvable." cela signifie que votre index n'a pas été reconnu. Ce qui est trompeur, c'est que le premier argument est utilisé comme index et nommé $options_group, lorsque l' archivage réel options.php # 112 se produit $options_page, qui est le $hook_suffix, que vous obtenez en tant que valeur @return add_submenu_page().

Bref, une solution simple est de faire $option_groupcorrespondre $option_name. Une autre cause de cette erreur est d'avoir une valeur non valide pour le $pageparamètre lors de l'appel de add_settings_section( $id, $title, $callback, $page )ou add_settings_field( $id, $title, $callback, $page, $section, $args ).

Astuce: $pagedoit correspondre à $menu_slugpartir de la référence de fonction / ajouter une page de thème.

Fix simple

Utiliser le nom de page personnalisé (dans votre cas:) $this->plugin_slugcomme identifiant de section contournerait le problème. Cependant, toutes vos options devraient être contenues dans une seule section.

Solution

Pour une solution plus robuste, apportez ces modifications à votre Plugin_Name_Adminclasse:

Ajouter au constructeur:

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

Ajoutez ces méthodes:

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

Et le changement add_settings_section()appelle à: $this->add_settings_section().


Autres notes sur votre code

  • Votre code de formulaire est correct. Votre formulaire doit être soumis à options.php, comme indiqué par @Chris_O, et comme indiqué dans la documentation de l' API WP Settings .
  • L'espace de noms a ses avantages, mais il peut rendre le débogage plus complexe et réduit la compatibilité de votre code (nécessite PHP> = 5.3, d'autres plugins / thèmes qui utilisent des chargeurs automatiques, etc.). Donc, s'il n'y a aucune bonne raison d'espacer votre fichier, ne le faites pas. Vous évitez déjà les conflits de noms en encapsulant votre code dans une classe. Rendez vos noms de classe plus spécifiques et apportez vos validate()rappels dans la classe en tant que méthodes publiques.
  • En comparant votre plugin de passe-partout cité avec votre code, il semble que votre code soit en fait basé sur une fourche ou une ancienne version du passe-partout. Même les noms de fichiers et les chemins d'accès sont différents. Vous pouvez migrer votre plugin vers la dernière version, mais notez que ce passe-partout de plugin peut ne pas convenir à vos besoins. Il utilise des singletons, qui sont généralement découragés . Il y a des cas où le modèle singleton est sensé , mais cela devrait être une décision consciente, pas la solution goto.
Stephen M. Harris
la source
1
C'est agréable de savoir qu'il y a un bug dans l'API. Je donne toujours la tentative de parcourir le code que j'écris pour les bogues que je peux introduire. Bien sûr, cela suppose que je connais une chose ou deux.
gate_engineer
Pour tous ceux qui rencontrent ce problème: jetez un œil à l'exemple de POO dans le codex: codex.wordpress.org/Creating_Options_Pages#Example_.232
maysi
5

Je viens de trouver ce message en cherchant le même problème. La solution est beaucoup plus simple qu'elle n'y paraît car la documentation est trompeuse: dans register_setting () le premier argument nommé $option_groupest votre slug de page, pas la section dans laquelle vous souhaitez afficher le paramètre.

Dans le code ci-dessus, vous devez utiliser

    // 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
la source
Ce n'est pas correct. Veuillez voir cet exemple de travail (pas le mien) - gist.github.com/annalinneajohansson/5290405
Xdg
2

Lors de l'enregistrement de la page d'options avec:

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

Et l'enregistrement des paramètres avec

register_setting( string $option_group, string $option_name );

$option_group devrait être identique à $menu_slug

Cafer Elgin
la source
1

J'ai eu la même erreur mais je l'ai obtenue d'une manière différente:

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

Je ne sais pas pourquoi cela s'est produit, mais il semble que cela register_settingne devrait pas être dans le rappel 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');

J'espère que ça aide

Un nouveau 1
la source
0

Je fais face à ce problème depuis quelques jours maintenant, cette erreur s'est arrêtée lorsque j'ai mis dans les commentaires la ligne de:

// settings_fields($this->plugin_slug);

après cela, je redirige vers options.php mais je ne peux pas encore résoudre le problème de setting_fields.

G.Karles
la source
je l'ai corrigé depuis la fonction de validation !! ;)
G.Karles