Eroarea "Options Page Not Found" la trimiterea paginii de setări pentru un plugin OOP
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:

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-uluioption_update_filter()
(respectivadd_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 dinadd_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 fieadd_settings_section( $id, $title, $callback, $page )
fieadd_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ă.

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.

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

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

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

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

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
