"Kesalahan: Opsi Halaman Tidak Ditemukan" pada Pengaturan Halaman Pengajuan untuk Plugin OOP

19

Saya sedang mengembangkan plugin menggunakan repositori Boilerplate Tom McFarlin sebagai templat, yang memanfaatkan praktik OOP. Saya telah mencoba mencari tahu mengapa saya tidak dapat mengirimkan pengaturan dengan benar. Saya sudah mencoba mengatur atribut aksi ke string kosong seperti yang disarankan pada pertanyaan lain di sini, tetapi itu tidak membantu ...

Di bawah ini adalah pengaturan kode umum yang saya gunakan ...

Formulir (/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>

Untuk kode berikut, anggap semua callback untuk add_settings_field () dan add_settings_section () ada, kecuali untuk 'option_list_selection'.

Plugin Admin Class (/ {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
        );
    }
}

Beberapa Pembaruan yang Diminta:

Mengubah atribut tindakan ke:

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

... cukup menghasilkan 404 Kesalahan. Berikut ini adalah kutipan dari Log Apache. Perhatikan bahwa skrip WordPress default dan en-antrian CSS dihapus:

# 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

File php-errors.log dan file debug.log ketika WP_DEBUG benar benar kosong.

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

    }

}

Referensi:

gate_engineer
sumber
Uraian karunia melaporkan: "Harap berikan beberapa informasi tentang praktik terbaik " . Menggunakan lajang dengan konstruktor pribadi dan banyak tindakan di dalamnya: praktik buruk dan sulit untuk diuji, bukan kesalahan Anda.
gmazzap
1
gunakan ../../options.php setelah menguji kode Anda.
ravi patel
Tolong tunjukkan get_plugin_slug ().
vancoder
@vancoder Saya telah mengedit posting di atas dengan informasi yang relevan ...
gate_engineer
Mengapa ada backslash di callback sanitasi Anda di register_settings Anda? Saya tidak berpikir itu akan berhasil.
Bjorn

Jawaban:

21

"Kesalahan: Opsi Halaman Tidak Ditemukan" Bug

Ini adalah masalah yang diketahui di WP Settings API. Ada tiket yang dibuka bertahun-tahun yang lalu, dan ditandai telah dipecahkan - tetapi bug tetap ada di versi terbaru WordPress. Inilah yang dikatakan laman Codex (sekarang dihapus) tentang ini :

"Kesalahan: halaman opsi tidak ditemukan." masalah (termasuk solusi dan penjelasan):

Masalahnya kemudian adalah, bahwa filter 'whitelist_options' belum mendapatkan indeks yang tepat untuk data Anda. Itu diterapkan pada options.php # 98 (WP 3.4).

register_settings()menambahkan data Anda ke global $new_whitelist_options. Ini kemudian digabung dengan global $whitelist_optionsdi dalam panggilan balik ( option_update_filter()resp add_option_whitelist()). Panggilan balik itu menambahkan data Anda ke global $new_whitelist_optionsdengan $option_groupindeks as. Ketika Anda menemukan "Kesalahan: halaman opsi tidak ditemukan." itu berarti indeks Anda belum dikenali. Hal yang menyesatkan adalah bahwa argumen pertama digunakan sebagai indeks dan dinamai $options_group, ketika check-in aktual options.php # 112 terjadi $options_page, yang merupakan $hook_suffix, yang Anda dapatkan sebagai nilai retret dariadd_submenu_page() .

Singkatnya, solusi yang mudah adalah $option_groupmencocokkan $option_name. Penyebab lain untuk kesalahan ini adalah memiliki nilai $pageparameter yang tidak valid saat memanggil salah satu add_settings_section( $id, $title, $callback, $page )atauadd_settings_field( $id, $title, $callback, $page, $section, $args ) .

Petunjuk: $pageharus sesuai $menu_slugdari Function Reference / add page theme.

Memperbaiki sederhana

Menggunakan nama halaman khusus (dalam kasus Anda: $this->plugin_slug sebagai id bagian Anda akan menyelesaikan masalah. Namun, semua opsi Anda harus terkandung dalam satu bagian.

Larutan

Untuk solusi yang lebih kuat, buat perubahan ini untuk Anda Plugin_Name_Admin kelas :

Tambahkan ke konstruktor:

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

Tambahkan metode ini:

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

Dan perubahan add_settings_section()panggilan ke: $this->add_settings_section().


Catatan lain pada kode Anda

  • Kode formulir Anda benar. Formulir Anda harus mengirimkan ke options.php, seperti yang ditunjukkan kepada saya oleh @Chris_O, dan seperti yang ditunjukkan dalam dokumentasi API Pengaturan WP .
  • Namespacing memiliki kelebihannya, tetapi dapat membuatnya lebih rumit untuk di-debug, dan menurunkan kompatibilitas kode Anda (membutuhkan PHP> = 5.3, plugin / tema lain yang menggunakan autoloaders, dll). Jadi jika tidak ada alasan yang baik untuk namespace file Anda, jangan. Anda sudah menghindari konflik penamaan dengan membungkus kode Anda di kelas. Jadikan nama kelas Anda lebih spesifik, dan bawa validate()panggilan balik Anda ke kelas sebagai metode publik.
  • Membandingkan boilerplate plugin yang dikutip dengan kode Anda, sepertinya kode Anda sebenarnya didasarkan pada garpu atau versi lama dari boilerplate. Bahkan nama file dan jalurnya berbeda. Anda dapat memigrasikan plugin Anda ke versi terbaru, tetapi perhatikan bahwa plugin boilerplate ini mungkin tidak sesuai dengan kebutuhan Anda. Itu menggunakan lajang, yang umumnya berkecil hati . Ada kasus di mana pola tunggal masuk akal , tetapi ini harus menjadi keputusan sadar, bukan solusi goto.
Stephen M. Harris
sumber
1
Senang mengetahui bahwa ada bug di api. Saya selalu berusaha melihat kode yang saya tulis untuk bug yang saya perkenalkan. Tentu saja, itu mengasumsikan saya tahu satu atau dua hal.
gate_engineer
Bagi siapa pun yang menemukan masalah ini: lihat contoh OOP dalam codex: codex.wordpress.org/Creating_Options_Pages#Example_.232
maysi
5

Saya baru saja menemukan posting ini sambil mencari masalah yang sama. Solusinya jauh lebih sederhana daripada yang terlihat karena dokumentasinya menyesatkan: dalam register_setting () argumen pertama dinamai$option_group adalah halaman Anda siput, bukan bagian di mana Anda ingin menampilkan pengaturan.

Pada kode di atas Anda harus menggunakan

    // 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
sumber
Ini tidak benar. Silakan lihat contoh kerja ini (bukan milik saya) - gist.github.com/annalinneajohansson/5290405
Xdg
2

Saat mendaftarkan halaman opsi dengan:

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

Dan mendaftarkan pengaturan dengan

register_setting( string $option_group, string $option_name );

$option_group harus sama dengan $menu_slug

Cafer Elgin
sumber
1

Saya memiliki kesalahan yang sama tetapi mendapatkannya dengan cara yang berbeda:

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

Saya tidak tahu mengapa ini terjadi, tetapi sepertinya itu register_settingtidak harus di panggil kembaliadd_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');

saya harap ini membantu

1 baru
sumber
0

Saya telah menghadapi masalah ini juga selama beberapa hari sekarang, kesalahan ini telah berhenti ketika saya memberi komentar pada baris:

// settings_fields($this->plugin_slug);

setelah itu saya mengarahkan ke options.php tetapi saya belum bisa menyelesaikan masalah setting_fields.

G.Karles
sumber
saya memperbaikinya dari fungsi validasi !! ;)
G.Karles