"Errore: Options Page Not Found" durante l'invio della pagina Impostazioni per un plugin OOP
Sto sviluppando un plugin utilizzando il repository Boilerplate di Tom McFarlin come template, che utilizza pratiche OOP. Sto cercando di capire esattamente perché non riesco a sottomettere correttamente le mie impostazioni. Ho provato ad impostare l'attributo action su una stringa vuota come suggerito in un'altra domanda, ma non ha aiutato...
Di seguito è riportata la configurazione generale del codice che sto utilizzando...
Il Form (/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( 'Salva Impostazioni' );
?>
</form>
</div>
Per il seguente codice, assumere che tutte le callback per add_settings_field() e add_settings_section() esistano, eccetto 'option_list_selection'.
La Classe Admin del Plugin (/{plugin_name}-class-admin.php):
namespace wp_plugin_name;
class Plugin_Name_Admin
{
/**
* Nota: Alcune parti del codice della classe e delle funzioni dei metodi sono omesse per brevità
* Fatemi sapere se avete bisogno di maggiori informazioni...
*/
private function __construct()
{
$plugin = Plugin_Name::get_instance();
$this->plugin_slug = $plugin->get_plugin_slug();
$this->friendly_name = $plugin->get_name(); // Ottiene il nome "Human Friendly" presentabile
// Aggiunge tutte le opzioni per le impostazioni amministrative
add_action( 'admin_init', array( $this, 'plugin_options_init' ) );
// Aggiunge la pagina delle opzioni e la voce di menu
add_action( 'admin_menu', array( $this, 'add_plugin_admin_menu' ) );
}
public function add_plugin_admin_menu()
{
// Aggiunge una Pagina delle Opzioni
$this->plugin_screen_hook_suffix =
add_options_page(
__( $this->friendly_name . " Opzioni", $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()
{
// Aggiorna Impostazioni
add_settings_section(
'maintenance',
'Manutenzione',
array( $this, 'maintenance_section' ),
$this->plugin_slug
);
// Opzione Controllo Aggiornamenti
register_setting(
'maintenance',
'plugin-name_check_updates',
'wp_plugin_name\validate_bool'
);
add_settings_field(
'check_updates',
$this->friendly_name . ' deve controllare gli aggiornamenti?',
array( $this, 'check_updates_field' ),
$this->plugin_slug,
'maintenance'
);
// Opzione Periodo di Aggiornamento
register_setting(
'maintenance',
'plugin-name_update_period',
'wp_plugin_name\validate_int'
);
add_settings_field(
'update_frequency',
'Con quale frequenza ' . $this->friendly_name . ' deve controllare gli aggiornamenti?',
array( $this, 'update_frequency_field' ),
$this->plugin_slug,
'maintenance'
);
// Configurazioni delle Opzioni del Plugin
add_settings_section(
'category-option-list', 'Lista Opzioni Widget',
array( $this, 'option_list_section' ),
$this->plugin_slug
);
}
}
Alcuni Aggiornamenti Richiesti:
Cambiando l'attributo action in:
<form action="../../options.php" method="post">
...risulta semplicemente in un Errore 404. Di seguito l'estratto dei Log di Apache. Nota che gli script WordPress predefiniti e gli enqueue CSS sono stati rimossi:
# Cambiato in ../../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
#Cambiato in 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
Entrambi i file php-errors.log e debug.log quando WP_DEBUG è true sono vuoti.
La Classe 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()
{
// Carica il domain del testo del plugin
add_action( 'init',
array(
$this,
'load_plugin_textdomain' ) );
// Attiva il plugin quando viene aggiunto un nuovo blog
add_action( 'wpmu_new_blog',
array(
$this,
'activate_new_site' ) );
// Carica i fogli di stile pubblici e JavaScript
add_action( 'wp_enqueue_scripts',
array(
$this,
'enqueue_styles' ) );
add_action( 'wp_enqueue_scripts',
array(
$this,
'enqueue_scripts' ) );
/* Definisce funzionalità personalizzate.
* Fare riferimento a 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()
{
// Se l'istanza singola non è stata impostata, impostala ora
if ( null == self::$instance )
{
self::$instance = new self;
}
return self::$instance;
}
/**
* Le funzioni membro activate(), deactivate(), e update() sono molto simili.
* Vedere il plugin Boilerplate per maggiori dettagli...
*/
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" );
/**
* Verifica se questa è una nuova installazione
*/
if ( get_option( 'plugin-name_version' ) === false )
{
// Ottiene il tempo come Timestamp Unix e aggiunge una settimana
$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 );
// Crea la tabella delle opzioni
table_update();
// Informa l'utente che PluginName è stato installato con successo
is_admin() && add_filter( 'gettext', 'finalization_message', 99, 3 );
}
else
{
// Informa l'utente che PluginName è stato attivato con successo
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() );
/**
* Scopre quale versione del nostro plugin stiamo eseguendo e la confronta con la
* versione definita qui
*/
if ( $cache_plugin_version > self::VERSION )
{
$cache_deferred_admin_notices[] = array(
'error',
"Sembra che tu stia tentando di tornare a una versione precedente di " . $this->get_name() . ". Il ripristino tramite la funzione di aggiornamento non è supportato."
);
}
else if ( $cache_plugin_version === self::VERSION )
{
$cache_deferred_admin_notices[] = array(
'updated',
"Stai già utilizzando l'ultima versione di " . $this->get_name() . "!"
);
return;
}
/**
* Se non possiamo determinare a quale versione si trova la tabella, aggiornala...
*/
if ( !is_int( $cache_table_version ) )
{
update_option( 'plugin-name_table_version', TABLE_VERSION );
table_update();
}
/**
* Altrimenti, controlleremo semplicemente se è necessario un aggiornamento
*/
else if ( $cache_table_version < TABLE_VERSION )
{
table_update();
}
/**
* La tabella non necessitava di aggiornamento.
* Nota che non possiamo aggiornare altre opzioni perché non possiamo presumere che siano ancora
* i valori predefiniti per il nostro plugin... (a meno che non li abbiamo memorizzati nel db)
*/
}
private static function single_deactivate()
{
// Determina se l'utente corrente ha i permessi appropriati
if ( !current_user_can( 'activate_plugins' ) )
return;
// Ci sono dati nella richiesta?
$plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : '';
// Controlla se il nonce era valido
check_admin_referer( "deactivate-plugin_{$plugin}" );
// Beh, tecnicamente il plugin non è incluso quando è disattivato quindi...
// Non fare nulla
}
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 . " è stato <strong>attivato con successo</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 = "Capitano, il Core è stabile e PluginName è stato <strong>installato con successo</strong> ed è pronto per la velocità Warp";
if ( $untranslated_text === $old )
$translated_text = $new;
return $translated_text;
}
}
Riferimenti:

Bug "Errore: Pagina delle opzioni non trovata"
Questo è un problema noto nell'API delle impostazioni di WP. Era stato aperto un ticket anni fa ed era stato segnato come risolto, ma il bug persiste nelle ultime versioni di WordPress. Questo è ciò che la pagina del Codex (ora rimossa) diceva al riguardo:
Il problema "Errore: pagina delle opzioni non trovata" (inclusa una soluzione e spiegazione):
Il problema è che il filtro 'whitelist_options' non ha l'indice corretto per i tuoi dati. Viene applicato su options.php#98 (WP 3.4).
register_settings()
aggiunge i tuoi dati al globale$new_whitelist_options
. Questo poi viene unito con il globale$whitelist_options
all'interno dei callbackoption_update_filter()
(risp.add_option_whitelist()
). Questi callback aggiungono i tuoi dati al globale$new_whitelist_options
con$option_group
come indice. Quando incontri "Errore: pagina delle opzioni non trovata" significa che il tuo indice non è stato riconosciuto. La cosa fuorviante è che il primo argomento viene usato come indice e chiamato$options_group
, quando il controllo effettivo in options.php#112 avviene rispetto a$options_page
, che è$hook_suffix
, che ottieni come valore di @return daadd_submenu_page()
.In breve, una soluzione semplice è fare in modo che
$option_group
corrisponda a$option_name
. Un'altra causa di questo errore è avere un valore non valido per il parametro$page
quando si chiamaadd_settings_section( $id, $title, $callback, $page )
oadd_settings_field( $id, $title, $callback, $page, $section, $args )
.Suggerimento:
$page
dovrebbe corrispondere a$menu_slug
dalla Funzione di Riferimento/add theme page.
Soluzione Semplice
Usare il nome personalizzato della pagina (nel tuo caso: $this->plugin_slug
) come id della sezione risolverebbe il problema. Tuttavia, tutte le tue opzioni dovrebbero essere contenute in una singola sezione.
Soluzione
Per una soluzione più robusta, apporta queste modifiche alla tua classe Plugin_Name_Admin
:
Aggiungi al costruttore:
// Tiene traccia delle nuove sezioni per whitelist_custom_options_page()
$this->page_sections = array();
// Deve essere eseguito dopo `option_update_filter()` di wp, quindi priorità > 10
add_action( 'whitelist_options', array( $this, 'whitelist_custom_options_page' ),11 );
Aggiungi questi metodi:
// Aggiunge alla whitelist le opzioni sulle pagine personalizzate.
// Soluzione alternativa per il secondo problema: http://j.mp/Pk3UCF
public function whitelist_custom_options_page( $whitelist_options ){
// Le opzioni personalizzate sono mappate per id sezione; Rimappale per slug pagina.
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 per `add_settings_section()` di wp che tiene traccia delle sezioni personalizzate
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 cambia le chiamate a add_settings_section()
con: $this->add_settings_section()
.
Altre note sul tuo codice
- Il codice del tuo form è corretto. Il tuo form deve inviare a options.php, come mi ha fatto notare @Chris_O e come indicato nella documentazione dell'API delle impostazioni di WP.
- L'uso dei namespace ha i suoi vantaggi, ma può rendere più complesso il debug e riduce la compatibilità del tuo codice (richiede PHP>=5.3, altri plugin/temi che usano autoloader, ecc.). Quindi se non c'è una buona ragione per usare i namespace nel tuo file, non farlo. Stai già evitando conflitti di nomi racchiudendo il tuo codice in una classe. Rendi i nomi delle tue classi più specifici e sposta i callback di
validate()
all'interno della classe come metodi pubblici. - Confrontando il tuo plugin boilerplate citato con il tuo codice, sembra che il tuo codice sia in realtà basato su un fork o una vecchia versione del boilerplate. Anche i nomi dei file e i percorsi sono diversi. Potresti migrare il tuo plugin all'ultima versione, ma nota che questo plugin boilerplate potrebbe non essere adatto alle tue esigenze. Fa uso di singleton, che sono generalmente sconsigliati. Ci sono casi in cui il pattern singleton è sensato, ma questa dovrebbe essere una decisione consapevole, non la soluzione predefinita.

È bello sapere che c'è un bug nell'API. Cerco sempre di controllare il codice che scrivo per individuare eventuali bug che potrei aver introdotto. Ovviamente, questo presuppone che ne sappia qualcosa.

Per chiunque si imbatta in questo problema: date un'occhiata all'esempio OOP nel codex: https://codex.wordpress.org/Creating_Options_Pages#Example_.232

Ho appena trovato questo post cercando lo stesso problema. La soluzione è molto più semplice di quanto sembri perché la documentazione è fuorviante: in register_setting() il primo argomento chiamato $option_group
è lo slug della tua pagina, non la sezione in cui vuoi visualizzare l'impostazione.
Nel codice sopra dovresti usare
// Aggiornamento Impostazioni
add_settings_section(
'maintenance', // slug della sezione
'Manutenzione', // titolo della sezione
array( $this, 'maintenance_section' ), // callback di visualizzazione della sezione
$this->plugin_slug // slug della pagina
);
// Opzione Controllo Aggiornamenti
register_setting(
$this->plugin_slug, // slug della pagina, non lo slug della sezione
'plugin-name_check_updates', // slug dell'impostazione
'wp_plugin_name\validate_bool' // non valido, dovrebbe essere un array di opzioni, vedi documentazione per maggiori info
);
add_settings_field(
'plugin-name_check_updates', // slug dell'impostazione
$this->friendly_name . ' dovrebbe controllare gli aggiornamenti?', // titolo dell'impostazione
array( $this, 'check_updates_field' ), // callback di visualizzazione dell'impostazione
$this->plugin_slug, // slug della pagina
'maintenance' // slug della sezione
);

Questo non è corretto. Per favore guarda questo esempio funzionante (non mio) - https://gist.github.com/annalinneajohansson/5290405

Durante la registrazione della pagina delle opzioni con:
add_submenu_page( string $parent_slug, string $page_title, string $menu_title, string $capability, string $menu_slug, callable $function = '' )
E la registrazione delle impostazioni con:
register_setting( string $option_group, string $option_name );
$option_group
dovrebbe essere uguale a $menu_slug

Ho avuto lo stesso errore ma l'ho ottenuto in un modo diverso:
// nessun codice effettivo
// questo ha fallito
add_settings_field('id','titolo', /*callback*/ function($argomenti) {
// echo $codice_html;
register_setting('gruppo_opzione', 'nome_opzione');
}), 'pagina', 'sezione');
Non so perché sia successo, ma sembra che register_setting
non dovrebbe essere nella callback di add_settings_field
// nessun codice effettivo
// questo ha funzionato
add_settings_field('id','titolo', /*callback*/ function($argomenti) {echo $codice_html;}), 'pagina', 'sezione');
register_setting('gruppo_opzione', 'nome_opzione');
Spero che questo aiuti
