Eroarea "Options Page Not Found" la trimiterea paginii de setări pentru un plugin OOP

30 mar. 2014, 08:36:44
Vizualizări: 30.1K
Voturi: 20

Dezvolt un plugin folosind repository-ul Boilerplate al lui Tom McFarlin ca șablon, care utilizează practici OOP. Încerc să înțeleg exact de ce nu pot trimite corect setările mele. Am încercat să setez atributul action ca string gol așa cum s-a sugerat într-o altă întrebare de aici, dar nu a ajutat...

Mai jos este configurația generală a codului pe care îl folosesc...

Formularul (/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( 'Salvează Setările' );
        ?>
    </form>
</div>

Pentru următorul cod, presupuneți că toate callback-urile pentru add_settings_field() și add_settings_section() există, cu excepția 'option_list_selection'.

Clasa Admin a Plugin-ului (/{plugin_name}-class-admin.php):

namespace wp_plugin_name;

class Plugin_Name_Admin
{
    /**
     * Notă: Unele părți ale codului clasei și funcțiile metodelor lipsesc pentru brevitate
     * Anunțați-mă dacă aveți nevoie de mai multe informații...
     */

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

        $this->plugin_slug   = $plugin->get_plugin_slug();
        $this->friendly_name = $plugin->get_name(); // Obține numele "prietenos" prezentabil

        // Adaugă toate opțiunile pentru setările administrative
        add_action( 'admin_init', array( $this, 'plugin_options_init' ) );

        // Adaugă pagina de opțiuni și elementul de meniu
        add_action( 'admin_menu', array( $this, 'add_plugin_admin_menu' ) );
    }

    public function add_plugin_admin_menu()
    {
        // Adaugă o Pagină de Opțiuni
        $this->plugin_screen_hook_suffix =
        add_options_page(
            __( $this->friendly_name . " Opțiuni", $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()
    {
        // Actualizează Setările
        add_settings_section(
            'maintenance',
            'Întreținere',
            array( $this, 'maintenance_section' ),
            $this->plugin_slug
        );

        // Opțiune Verificare Actualizări
        register_setting( 
            'maintenance',
            'plugin-name_check_updates',
            'wp_plugin_name\validate_bool'
        );

        add_settings_field(
            'check_updates',
            'Ar trebui ca ' . $this->friendly_name . ' să verifice actualizările?',
            array( $this, 'check_updates_field' ),
            $this->plugin_slug,
            'maintenance'
        );

        // Opțiune Perioada de Actualizare
        register_setting(
            'maintenance',
            'plugin-name_update_period',
            'wp_plugin_name\validate_int'
        );

        add_settings_field(
            'update_frequency',
            'Cât de des ar trebui ' . $this->friendly_name . ' să verifice actualizările?',
            array( $this, 'update_frequency_field' ),
            $this->plugin_slug,
            'maintenance'
        );

        // Configurări Opțiuni Plugin
        add_settings_section(
            'category-option-list', 'Listă Opțiuni Widget',
            array( $this, 'option_list_section' ),
            $this->plugin_slug
        );
    }
}

Actualizări Solicitate:

Schimbarea atributului action la:

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

...rezultă doar într-o eroare 404. Mai jos este un extras din logurile Apache. Rețineți că script-urile implicite WordPress și CSS en-queues sunt eliminate:

# Schimbat la ../../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

#Schimbat la 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

Atât fișierul php-errors.log cât și fișierul debug.log când WP_DEBUG este true sunt goale.

Clasa 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()
    {
        // Încarcă domeniul de text al plugin-ului
        add_action( 'init',
                    array(
            $this,
            'load_plugin_textdomain' ) );

        // Activează plugin-ul când este adăugat un blog nou
        add_action( 'wpmu_new_blog',
                    array(
            $this,
            'activate_new_site' ) );

        // Încarcă foaia de stil și JavaScript pentru interfața publică
        add_action( 'wp_enqueue_scripts',
                    array(
            $this,
            'enqueue_styles' ) );
        add_action( 'wp_enqueue_scripts',
                    array(
            $this,
            'enqueue_scripts' ) );

        /* Definește funcționalitatea personalizată.
         * Consultă 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()
    {
        // Dacă instanța unică nu a fost setată, setează-o acum
        if ( null == self::$instance )
        {
            self::$instance = new self;
        }

        return self::$instance;
    }

    /**
     * Funcțiile membre activate(), deactivate(), și update() sunt foarte similare.
     * Vezi plugin-ul Boilerplate pentru mai multe detalii...
     */

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

        /**
         * Testează dacă aceasta este o instalare nouă 
         */
        if ( get_option( 'plugin-name_version' ) === false )
        {
            // Obține timpul ca Timestamp Unix și adaugă o săptămână
            $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 );

            // Creează tabelul de opțiuni
            table_update();

            // Informează utilizatorul că PluginName a fost instalat cu succes
            is_admin() && add_filter( 'gettext', 'finalization_message', 99, 3 );
        }
        else
        {
            // Informează utilizatorul că PluginName a fost activat cu succes
            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() );

        /**
         * Află ce versiune a plugin-ului nostru rulăm și compară cu versiunea
         * definită aici
         */
        if ( $cache_plugin_version > self::VERSION )
        {
            $cache_deferred_admin_notices[] = array(
                'error',
                "Se pare că încerci să revii la o versiune mai veche a " . $this->get_name() . ". Revenirea prin funcția de actualizare nu este suportată."
            );
        }
        else if ( $cache_plugin_version === self::VERSION )
        {
            $cache_deferred_admin_notices[] = array(
                'updated',
                "Folosești deja ultima versiune a " . $this->get_name() . "!"
            );
            return;
        }

        /**
         * Dacă nu putem determina la ce versiune este tabelul, actualizează-l...
         */
        if ( !is_int( $cache_table_version ) )
        {
            update_option( 'plugin-name_table_version', TABLE_VERSION );
            table_update();
        }

        /**
         * Altfel, vom verifica doar dacă este necesară o actualizare
         */
        else if ( $cache_table_version < TABLE_VERSION )
        {
            table_update();
        }

        /**
         * Tabelul nu necesita actualizare.
         * Notă: nu putem actualiza alte opțiuni deoarece nu putem presupune că sunt încă
         * valorile implicite pentru plugin-ul nostru... (cu excepția cazului în care le-am stocat în baza de date)
         */
    }

    private static function single_deactivate()
    {
        // Determină dacă utilizatorul curent are permisiunile necesare
        if ( !current_user_can( 'activate_plugins' ) )
            return;

        // Există date în request?
        $plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';

        // Verifică dacă nonce-ul a fost valid
        check_admin_referer( "deactivate-plugin_{$plugin}" );

        // Tehnic, plugin-ul nu este inclus când este dezactivat, deci...
        // Nu face nimic
    }

    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 . " a fost <strong>activat cu succes</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 = "Căpitane, Nucleul este stabil și PluginName a fost <strong>instalat cu succes</strong> și este gata pentru Viteza Warp";

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

        return $translated_text;
    }
}

Referințe:

7
Comentarii

Descrierea recompensei raportează: "Vă rugăm să oferiți câteva informații despre cele mai bune practici". Folosirea singleton-ului cu constructori privați și o grămadă de acțiuni în interiorul lor: practică proastă și greu de testat, nu este vina ta, totuși.

gmazzap gmazzap
1 apr. 2014 10:25:37

folosește ../../options.php după ce testezi codul.

Ravi Patel Ravi Patel
1 apr. 2014 12:53:38

Poți, te rog, să arăți get_plugin_slug().

vancoder vancoder
1 apr. 2014 21:17:51

@vancoder Am editat postul de mai sus cu informațiile relevante...

gate_engineer gate_engineer
1 apr. 2014 23:22:42

De ce există backslash-uri în funcțiile tale de sanatizare din register_settings? Nu cred că ar funcționa așa.

Bjorn Bjorn
2 apr. 2014 00:46:39

Am plasat acele funcții de sanatizare într-un namespace, totuși, acel namespace este același cu cel listat pentru fiecare clasă de mai sus. Le voi elimina și voi vedea dacă asta e problema.

gate_engineer gate_engineer
2 apr. 2014 01:33:16

Tot primesc același lucru. Aș mai dori să adaug că construiesc secțiuni în formularul meu pe aceeași pagină. Mă întreb dacă asta are vreun efect...

gate_engineer gate_engineer
2 apr. 2014 01:48:07
Arată celelalte 2 comentarii
Toate răspunsurile la întrebare 5
2
21

Eroare: "Pagina de opțiuni nu a fost găsită"

Acesta este un problema cunoscută în WP Settings API. A existat un tichet deschis acum ani și a fost marcat ca rezolvat - dar bug-ul persistă în cele mai recente versiuni de WordPress. Iată ce spunea pagina Codex (acum eliminată) despre acest lucru:

Problema "Eroare: pagina de opțiuni nu a fost găsită." (inclusiv o soluție și explicație):

Problema este că filtrul 'whitelist_options' nu are indexul corect pentru datele dumneavoastră. Acesta este aplicat în options.php#98 (WP 3.4).

register_settings() adaugă datele dumneavoastră la globalul $new_whitelist_options. Acesta este apoi îmbinat cu globalul $whitelist_options în interiorul callback-ului option_update_filter() (respectiv add_option_whitelist()). Aceste callback-uri adaugă datele dumneavoastră la globalul $new_whitelist_options cu $option_group ca index.

Când întâlniți "Eroare: pagina de opțiuni nu a fost găsită.", înseamnă că indexul dumneavoastră nu a fost recunoscut. Lucrul înșelător este că primul argument este folosit ca index și numit $options_group, când verificarea reală din options.php#112 se face împotriva $options_page, care este $hook_suffix, pe care îl obțineți ca valoare @return din add_submenu_page().

Pe scurt, o soluție simplă este să faceți ca $option_group să se potrivească cu $option_name. O altă cauză pentru această eroare este având o valoare invalidă pentru parametrul $page când apelați fie add_settings_section( $id, $title, $callback, $page ) fie add_settings_field( $id, $title, $callback, $page, $section, $args ).

Sugestie: $page ar trebui să se potrivească cu $menu_slug din Function Reference/add theme page.

Soluție simplă

Folosirea numelui paginii personalizate (în cazul dumneavoastră: $this->plugin_slug) ca id al secțiunii ar rezolva problema. Totuși, toate opțiunile dumneavoastră ar trebui să fie conținute într-o singură secțiune.

Soluție

Pentru o soluție mai robustă, faceți aceste modificări în clasa Plugin_Name_Admin:

Adăugați în constructor:

// Urmărește noile secțiuni pentru whitelist_custom_options_page()
$this->page_sections = array();
// Trebuie să ruleze după `option_update_filter()` din WP, deci prioritate > 10
add_action( 'whitelist_options', array( $this, 'whitelist_custom_options_page' ),11 );

Adăugați aceste metode:

// White-list pentru opțiuni pe pagini personalizate.
// Soluție pentru a doua problemă: http://j.mp/Pk3UCF
public function whitelist_custom_options_page( $whitelist_options ){
    // Opțiunile personalizate sunt mapate după id-ul secțiunii; Re-mapează după slug-ul paginii.
    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 pentru `add_settings_section()` din WP care urmărește secțiunile personalizate
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;
    }
}

Și schimbați apelurile add_settings_section() în: $this->add_settings_section().


Alte note despre codul dumneavoastră

  • Codul formularului este corect. Formularul dumneavoastră trebuie să trimită către options.php, așa cum mi-a indicat @Chris_O și cum este specificat în documentația WP Settings API.
  • Namespace-urile au avantajele lor, dar pot face debugging-ul mai complex și reduc compatibilitatea codului (necesită PHP>=5.3, alte plugin-uri/teme care folosesc autoloadere, etc). Deci dacă nu există un motiv bun pentru a folosi namespace-uri în fișierul dumneavoastră, nu o faceți. Deja evitați conflictele de nume încadrând codul într-o clasă. Faceți numele claselor mai specifice și mutați callback-urile validate() în clasă ca metode publice.
  • Comparând plugin boilerplate citat de dumneavoastră cu codul dumneavoastră, se pare că codul dumneavoastră este de fapt bazat pe o bifurcație sau pe o versiune veche a boilerplate-ului. Chiar și numele fișierelor și căile sunt diferite. Puteți migra plugin-ul la cea mai recentă versiune, dar rețineți că acest plugin boilerplate poate să nu fie potrivit pentru nevoile dumneavoastră. Acesta folosește singleton-uri, care sunt în general dezaprobate. Există cazuri în care modelul singleton este rezonabil, dar aceasta ar trebui să fie o decizie conștientă, nu soluția implicită.
2 apr. 2014 22:45:43
Comentarii

E bine de știut că există o eroare în API. Întotdeauna încerc să verific codul pe care îl scriu pentru erorile pe care le-aș putea introduce. Desigur, asta presupune că știu câteva lucruri.

gate_engineer gate_engineer
3 apr. 2014 00:23:06

Pentru cei care întâlnesc această problemă: aruncați o privire la exemplul OOP din codex: https://codex.wordpress.org/Creating_Options_Pages#Example_.232

maysi maysi
31 ian. 2019 00:34:06
1

Tocmai am găsit acest post în timp ce căutam soluția pentru aceeași problemă. Soluția este mult mai simplă decât pare, deoarece documentația este înșelătoare: în register_setting(), primul argument numit $option_group este slug-ul paginii tale, nu secțiunea în care dorești să afișezi setarea.

În codul de mai sus, ar trebui să folosești:

    // Actualizare Setări
    add_settings_section(
        'maintenance', // slug-ul secțiunii
        'Maintenance', // titlul secțiunii
        array( $this, 'maintenance_section' ), // callback pentru afișarea secțiunii
        $this->plugin_slug // slug-ul paginii
    );

    // Opțiunea de Verificare a Actualizărilor
    register_setting( 
        $this->plugin_slug, // slug-ul paginii, nu slug-ul secțiunii
        'plugin-name_check_updates', // slug-ul setării
        'wp_plugin_name\validate_bool' // invalid, ar trebui să fie un array de opțiuni, vezi documentația pentru mai multe informații
    );

    add_settings_field(
        'plugin-name_check_updates', // slug-ul setării
        'Should ' . $this->friendly_name . ' Check For Updates?', // titlul setării
        array( $this, 'check_updates_field' ), // callback pentru afișarea setării
        $this->plugin_slug, // slug-ul paginii
        'maintenance' // slug-ul secțiunii
    );
27 oct. 2017 02:37:59
Comentarii

Acest lucru nu este corect. Vă rugăm să consultați acest exemplu funcțional (nu este al meu) - https://gist.github.com/annalinneajohansson/5290405

Xdg Xdg
3 sept. 2018 21:32:37
0

La înregistrarea paginii de opțiuni cu:

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

Și la înregistrarea setărilor cu

register_setting( string $option_group, string $option_name );

$option_group ar trebui să fie același cu $menu_slug

6 oct. 2018 21:49:20
1

Am întâmpinat și eu această problemă în ultimele zile, eroarea a dispărut când am pus în comentariu linia:

// settings_fields($this->plugin_slug);

După aceea fac redirect către options.php, dar încă nu am reușit să rezolv problema cu setting_fields.

15 feb. 2019 11:07:32
Comentarii

am rezolvat din funcția de validare!! ;)

G.Karles G.Karles
15 feb. 2019 14:30:36
0

Am avut aceeași eroare, dar am întâlnit-o într-un mod diferit:

// nu este cod real
// acesta a eșuat
add_settings_field('id','title', /*callback*/ function($arguments) {
    // echo $htmlcode; 
    register_setting('option_group', 'option_name');
}), 'page', 'section');

Nu știu de ce s-a întâmplat asta, dar se pare că register_setting nu ar trebui să fie în callback-ul funcției add_settings_field

// nu este cod real
// acesta a funcționat
add_settings_field('id','title', /*callback*/ function($arguments) {echo $htmlcode;}), 'page', 'section');
register_setting('option_group', 'option_name');

Sper că acest lucru vă va fi de ajutor

26 feb. 2019 12:35:27