Tambahkan beberapa direktori plugin

39

Tugas

Anda dapat mendaftar dan menambah direktori Tema menggunakan register_theme_directory()untuk instalasi WP Anda. Sayangnya core tidak memberikan fungsi yang sama untuk plugin. Kami sudah memiliki MU-Plugin, Drop-Ins, Plugins dan Tema. Tetapi kami membutuhkan lebih banyak untuk organisasi file yang lebih baik.

Inilah daftar tugas yang harus dicapai:

  • Tambahkan direktori plugin tambahan
  • Untuk setiap direktori plugin, "tab" baru diperlukan seperti yang ditunjukkan di sini [1]
  • Direktori tambahan akan memiliki fungsi yang sama dengan direktori plugin default

Apa yang ada di sana untukmu?

Jawaban terbaik dan terlengkap akan diberikan hadiah.


[1] Tab tambahan untuk folder / direktori plugin baru

kaisar
sumber
3
Karena struktur direktori sangat terikat dengan konstanta direktori saya ragu bahwa melakukan ini pada tingkat sistem file adalah praktis (tanpa adopsi inti). Lapisan virtual organisasi di admin mungkin lebih mudah dicapai pada tingkat ekstensi.
Paling lambat
@ Pertama Yang seharusnya tidak menahan Anda dari menambahkan pemikiran Anda :)
kaiser
Ini akan menjadi fitur yang hebat.
ltfishie
Fitur terdengar bagus. Hanya perlu membalikkan inti insinyur, mencari tahu bagaimana hal itu harus dilakukan (cara WP) dan kemudian mengirimkan tambalan ke Devs ... Anda ingin melihat register_theme_directory () - search_theme_directories () - get_raw_theme_root () - get_theme_roots () - get_theme () - get_themes ()
Sterling Hamilton
2
Guys: Kirim apa ? Ini adalah pertanyaan, bukan jawaban dengan kode penuh :) FYI: Tiket baru di trac untuk menulis ulangget_themes() ke kelas.
kaiser

Jawaban:

28

Oke, saya akan coba ini. Beberapa batasan yang saya temui di sepanjang jalan:

  1. Tidak ada banyak filter dalam subkelas WP_List_Table, setidaknya tidak di tempat yang kita inginkan.

  2. Karena kurangnya filter, kami tidak dapat mempertahankan daftar tipe plugin yang akurat di bagian atas.

  3. Kita juga harus menggunakan beberapa hack JavaScript yang mengagumkan (baca: kotor) untuk menampilkan plugin sebagai aktif.

Saya membungkus kode area admin saya di dalam kelas, jadi nama fungsi saya tidak diawali. Anda dapat melihat semua kode ini di sini . Silakan berkontribusi!

API pusat

Hanya fungsi sederhana yang mengatur variabel global yang akan berisi direktori plugin kami dalam array asosiatif. Ini $keyakan menjadi sesuatu yang digunakan secara internal untuk mengambil plugin, dll. $dirAdalah path lengkap atau sesuatu yang relatif terhadap wp-contentdirektori. $labelakan menjadi untuk tampilan kami di area admin (mis. string yang dapat diterjemahkan).

<?php
function register_plugin_directory( $key, $dir, $label )
{
    global $wp_plugin_directories;
    if( empty( $wp_plugin_directories ) ) $wp_plugin_directories = array();

    if( ! file_exists( $dir ) && file_exists( trailingslashit( WP_CONTENT_DIR ) . $dir ) )
    {
        $dir = trailingslashit( WP_CONTENT_DIR ) . $dir;
    }

    $wp_plugin_directories[$key] = array(
        'label' => $label,
        'dir'   => $dir
    );
}

Maka, tentu saja, kita perlu memuat plugin. Menghubungkan ke plugins_loadedjalan terlambat dan pergi melalui plugin aktif, memuat masing-masing.

Area Admin

Mari kita atur fungsionalitas kita di dalam kelas.

<?php
class CD_APD_Admin
{

    /**
     * The container for all of our custom plugins
     */
    protected $plugins = array();

    /**
     * What custom actions are we allowed to handle here?
     */
    protected $actions = array();

    /**
     * The original count of the plugins
     */
    protected $all_count = 0;

    /**
     * constructor
     * 
     * @since 0.1
     */
    function __construct()
    {
        add_action( 'load-plugins.php', array( &$this, 'init' ) );
        add_action( 'plugins_loaded', array( &$this, 'setup_actions' ), 1 );

    }

} // end class

Kita akan terhubung ke plugins_loadedawal dan mengatur "tindakan" yang diizinkan yang akan kita gunakan. Ini akan menangani aktivasi dan penonaktifan plugin karena fungsi bawaan tidak dapat melakukannya dengan direktori khusus.

function setup_actions()
{
    $tmp = array(
        'custom_activate',
        'custom_deactivate'
    );
    $this->actions = apply_filters( 'custom_plugin_actions', $tmp );
}

Lalu ada fungsi yang terhubung load-plugins.php. Ini melakukan semua hal yang menyenangkan.

function init()
{
    global $wp_plugin_directories;

    $screen = get_current_screen();

    $this->get_plugins();

    $this->handle_actions();

    add_filter( 'views_' . $screen->id, array( &$this, 'views' ) );

    // check to see if we're using one of our custom directories
    if( $this->get_plugin_status() )
    {
        add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
        add_filter( 'all_plugins', array( &$this, 'filter_plugins' ) );
        // TODO: support bulk actions
        add_filter( 'bulk_actions-' . $screen->id, '__return_empty_array' );
        add_filter( 'plugin_action_links', array( &$this, 'action_links' ), 10, 2 );
        add_action( 'admin_enqueue_scripts', array( &$this, 'scripts' ) );
    }
}

Mari kita bahas satu per satu. yang get_pluginsmetode, adalah bungkus sekitar fungsi lain. Itu mengisi atribut pluginsdengan data.

function get_plugins()
{
    global $wp_plugin_directories;
    foreach( array_keys( $wp_plugin_directories ) as $key )
    {
       $this->plugins[$key] = cd_apd_get_plugins( $key );
    }
}

cd_apd_get_pluginsadalah rip dari get_pluginsfungsi bawaan tanpa hardcoded WP_CONTENT_DIRdan pluginsbisnis. Pada dasarnya: dapatkan direktori dari $wp_plugin_directoriesglobal, buka, cari semua file plugin. Simpan di cache untuk nanti.

<?php
function cd_apd_get_plugins( $dir_key ) 
{
    global $wp_plugin_directories;

    // invalid dir key? bail
    if( ! isset( $wp_plugin_directories[$dir_key] ) )
    {
        return array();
    }
    else
    {
        $plugin_root = $wp_plugin_directories[$dir_key]['dir'];
    }

    if ( ! $cache_plugins = wp_cache_get( 'plugins', 'plugins') )
        $cache_plugins = array();

    if ( isset( $cache_plugins[$dir_key] ) )
        return $cache_plugins[$dir_key];

    $wp_plugins = array();

    $plugins_dir = @ opendir( $plugin_root );
    $plugin_files = array();
    if ( $plugins_dir ) {
        while ( ( $file = readdir( $plugins_dir ) ) !== false ) {
            if ( substr($file, 0, 1) == '.' )
                continue;
            if ( is_dir( $plugin_root.'/'.$file ) ) {
                $plugins_subdir = @ opendir( $plugin_root.'/'.$file );
                if ( $plugins_subdir ) {
                    while (($subfile = readdir( $plugins_subdir ) ) !== false ) {
                        if ( substr($subfile, 0, 1) == '.' )
                            continue;
                        if ( substr($subfile, -4) == '.php' )
                            $plugin_files[] = "$file/$subfile";
                    }
                    closedir( $plugins_subdir );
                }
            } else {
                if ( substr($file, -4) == '.php' )
                    $plugin_files[] = $file;
            }
        }
        closedir( $plugins_dir );
    }

    if ( empty($plugin_files) )
        return $wp_plugins;

    foreach ( $plugin_files as $plugin_file ) {
        if ( !is_readable( "$plugin_root/$plugin_file" ) )
            continue;

        $plugin_data = get_plugin_data( "$plugin_root/$plugin_file", false, false ); //Do not apply markup/translate as it'll be cached.

        if ( empty ( $plugin_data['Name'] ) )
            continue;

        $wp_plugins[trim( $plugin_file )] = $plugin_data;
    }

    uasort( $wp_plugins, '_sort_uname_callback' );

    $cache_plugins[$dir_key] = $wp_plugins;
    wp_cache_set('plugins', $cache_plugins, 'plugins');

    return $wp_plugins;
}

Selanjutnya adalah bisnis sial untuk benar-benar mengaktifkan dan menonaktifkan plugin. Untuk melakukan ini, kami menggunakan handle_actionsmetode. Ini, sekali lagi, secara terang-terangan diambil dari bagian atas wp-admin/plugins.phpfile inti .

function handle_actions()
{
    $action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : '';

    // not allowed to handle this action? bail.
    if( ! in_array( $action, $this->actions ) ) return;

    // Get the plugin we're going to activate
    $plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : false;
    if( ! $plugin ) return;

    $context = $this->get_plugin_status();

    switch( $action )
    {
        case 'custom_activate':
            if( ! current_user_can('activate_plugins') )
                    wp_die( __('You do not have sufficient permissions to manage plugins for this site.') );

            check_admin_referer( 'custom_activate-' . $plugin );

            $result = cd_apd_activate_plugin( $plugin, $context );
            if ( is_wp_error( $result ) ) 
            {
                if ( 'unexpected_output' == $result->get_error_code() ) 
                {
                    $redirect = add_query_arg( 'plugin_status', $context, self_admin_url( 'plugins.php' ) );
                    wp_redirect( add_query_arg( '_error_nonce', wp_create_nonce( 'plugin-activation-error_' . $plugin ), $redirect ) ) ;
                    exit();
                } 
                else 
                {
                    wp_die( $result );
                }
            }

            wp_redirect( add_query_arg( array( 'plugin_status' => $context, 'activate' => 'true' ), self_admin_url( 'plugins.php' ) ) );
            exit();
            break;
        case 'custom_deactivate':
            if ( ! current_user_can( 'activate_plugins' ) )
                wp_die( __('You do not have sufficient permissions to deactivate plugins for this site.') );

            check_admin_referer('custom_deactivate-' . $plugin);
            cd_apd_deactivate_plugins( $plugin, $context );
            if ( headers_sent() )
                echo "<meta http-equiv='refresh' content='" . esc_attr( "0;url=plugins.php?deactivate=true&plugin_status=$status&paged=$page&s=$s" ) . "' />";
            else
                wp_redirect( self_admin_url("plugins.php?deactivate=true&plugin_status=$context") );
            exit();
            break;
        default:
            do_action( 'custom_plugin_dir_' . $action );
            break;
    }

}

Beberapa fungsi kustom di sini lagi. cd_apd_activate_plugin(ripped off from activate_plugin) dan cd_apd_deactivate_plugins(ripped off from deactivate_plugins). Keduanya sama dengan fungsi "induk" masing-masing tanpa direktori kode keras.

function cd_apd_activate_plugin( $plugin, $context, $silent = false ) 
{
    $plugin = trim( $plugin );

    $redirect = add_query_arg( 'plugin_status', $context, admin_url( 'plugins.php' ) );
    $redirect = apply_filters( 'custom_plugin_redirect', $redirect );

    $current = get_option( 'active_plugins_' . $context, array() );

    $valid = cd_apd_validate_plugin( $plugin, $context );
    if ( is_wp_error( $valid ) )
        return $valid;

    if ( !in_array($plugin, $current) ) {
        if ( !empty($redirect) )
            wp_redirect(add_query_arg('_error_nonce', wp_create_nonce('plugin-activation-error_' . $plugin), $redirect)); // we'll override this later if the plugin can be included without fatal error
        ob_start();
        include_once( $valid );

        if ( ! $silent ) {
            do_action( 'custom_activate_plugin', $plugin, $context );
            do_action( 'custom_activate_' . $plugin, $context );
        }

        $current[] = $plugin;
        sort( $current );
        update_option( 'active_plugins_' . $context, $current );

        if ( ! $silent ) {
            do_action( 'custom_activated_plugin', $plugin, $context );
        }

        if ( ob_get_length() > 0 ) {
            $output = ob_get_clean();
            return new WP_Error('unexpected_output', __('The plugin generated unexpected output.'), $output);
        }
        ob_end_clean();
    }

    return true;
}

Dan fungsi penonaktifan

function cd_apd_deactivate_plugins( $plugins, $context, $silent = false ) {
    $current = get_option( 'active_plugins_' . $context, array() );

    foreach ( (array) $plugins as $plugin ) 
    {
        $plugin = trim( $plugin );
        if ( ! in_array( $plugin, $current ) ) continue;

        if ( ! $silent )
            do_action( 'custom_deactivate_plugin', $plugin, $context );

        $key = array_search( $plugin, $current );
        if ( false !== $key ) {
            array_splice( $current, $key, 1 );
        }

        if ( ! $silent ) {
            do_action( 'custom_deactivate_' . $plugin, $context );
            do_action( 'custom_deactivated_plugin', $plugin, $context );
        }
    }

    update_option( 'active_plugins_' . $context, $current );
}

Ada juga cd_apd_validate_pluginfungsi, yang tentunya merupakan perampasan validate_plugintanpa sampah kode keras.

<?php
function cd_apd_validate_plugin( $plugin, $context ) 
{
    $rv = true;
    if ( validate_file( $plugin ) )
    {
        $rv = new WP_Error('plugin_invalid', __('Invalid plugin path.'));
    }

    global $wp_plugin_directories;
    if( ! isset( $wp_plugin_directories[$context] ) )
    {
        $rv = new WP_Error( 'invalid_context', __( 'The context for this plugin does not exist' ) );
    }

    $dir = $wp_plugin_directories[$context]['dir'];
    if( ! file_exists( $dir . '/' . $plugin) )
    {
        $rv = new WP_Error( 'plugin_not_found', __( 'Plugin file does not exist.' ) );
    }

    $installed_plugins = cd_apd_get_plugins( $context );
    if ( ! isset($installed_plugins[$plugin]) )
    {
        $rv = new WP_Error( 'no_plugin_header', __('The plugin does not have a valid header.') );
    }

    $rv = $dir . '/' . $plugin;
    return $rv;
}

Baiklah, dengan hal itu. Kita sebenarnya dapat mulai berbicara tentang tampilan tabel daftar

Langkah 1: tambahkan pandangan kami ke daftar di bagian atas tabel. Ini dilakukan dengan memfilter views_{$screen->id}di dalam initfungsi kita .

add_filter( 'views_' . $screen->id, array( &$this, 'views' ) );

Kemudian fungsi bengkok yang sebenarnya hanya loop melalui $wp_plugin_directories. Jika salah satu direktori yang baru terdaftar memiliki plugin, kami akan memasukkannya di layar.

function views( $views )
{
    global $wp_plugin_directories;

    // bail if we don't have any extra dirs
    if( empty( $wp_plugin_directories ) ) return $views;

    // Add our directories to the action links
    foreach( $wp_plugin_directories as $key => $info )
    {
        if( ! count( $this->plugins[$key] ) ) continue;
        $class = $this->get_plugin_status() == $key ? ' class="current" ' : '';
        $views[$key] = sprintf( 
            '<a href="%s"' . $class . '>%s <span class="count">(%d)</span></a>',
            add_query_arg( 'plugin_status', $key, 'plugins.php' ),
            esc_html( $info['label'] ),
            count( $this->plugins[$key] )
        );
    }
    return $views;
}

Hal pertama yang perlu kita lakukan jika kita melihat halaman direktori plugin kustom adalah menyaring tampilan lagi. Kita perlu menyingkirkan inactivehitungan karena itu tidak akan akurat. Konsekuensi dari tidak adanya filter di mana kita membutuhkannya. Hubungkan lagi ...

if( $this->get_plugin_status() )
{
    add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
}

Dan pengaturan cepat ...

function views_again( $views )
{
    if( isset( $views['inactive'] ) ) unset( $views['inactive'] );
    return $views;
}

Selanjutnya, mari kita singkirkan plugin yang seharusnya Anda lihat di tabel daftar, dan ganti dengan plugin khusus kami. Hubungkan ke all_plugins.

if( $this->get_plugin_status() )
{
    add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
    add_filter( 'all_plugins', array( &$this, 'filter_plugins' ) );
}

Karena kami sudah menyiapkan plugin dan data kami (lihat di setup_pluginsatas), filter_pluginsmetode hanya (1) menyimpan hitungan pada semua plugin untuk nanti, dan (2) menggantikan plugin di tabel daftar.

function filter_plugins( $plugins )
{
    if( $key = $this->get_plugin_status() )
    {
        $this->all_count = count( $plugins );
        $plugins = $this->plugins[$key];
    }
    return $plugins;
}

Dan sekarang kita akan membunuh aksi massal. Ini bisa dengan mudah didukung, saya kira?

if( $this->get_plugin_status() )
{
    add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
    add_filter( 'all_plugins', array( &$this, 'filter_plugins' ) );
    // TODO: support bulk actions
    add_filter( 'bulk_actions-' . $screen->id, '__return_empty_array' );
}

Tautan tindakan plugin default tidak akan berfungsi untuk kami. Jadi alih-alih, kita perlu mengatur sendiri (dengan tindakan kustom, dll). Dalam initfungsinya.

if( $this->get_plugin_status() )
{
    add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
    add_filter( 'all_plugins', array( &$this, 'filter_plugins' ) );
    // TODO: support bulk actions
    add_filter( 'bulk_actions-' . $screen->id, '__return_empty_array' );
    add_filter( 'plugin_action_links', array( &$this, 'action_links' ), 10, 2 );
}

Satu-satunya hal yang bisa diubah di sini adalah (1) kami mengubah tindakan, (2) menjaga status plugin, dan (3) sedikit mengubah nama nonce.

function action_links( $links, $plugin_file )
{
    $context = $this->get_plugin_status();

    // let's just start over
    $links = array();
    $links['activate'] = sprintf(
        '<a href="%s" title="Activate this plugin">%s</a>',
        wp_nonce_url( 'plugins.php?action=custom_activate&amp;plugin=' . $plugin_file . '&amp;plugin_status=' . esc_attr( $context ), 'custom_activate-' . $plugin_file ),
        __( 'Activate' )
    );

    $active = get_option( 'active_plugins_' . $context, array() );
    if( in_array( $plugin_file, $active ) )
    {
        $links['deactivate'] = sprintf(
            '<a href="%s" title="Deactivate this plugin" class="cd-apd-deactivate">%s</a>',
            wp_nonce_url( 'plugins.php?action=custom_deactivate&amp;plugin=' . $plugin_file . '&amp;plugin_status=' . esc_attr( $context ), 'custom_deactivate-' . $plugin_file ),
            __( 'Deactivate' )
        );
    }
    return $links;
}

Dan akhirnya, kita hanya perlu membuat beberapa JavaScript untuk menambahkannya. Dalam initfungsi lagi (semua bersama-sama saat ini).

if( $this->get_plugin_status() )
{
    add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
    add_filter( 'all_plugins', array( &$this, 'filter_plugins' ) );
    // TODO: support bulk actions
    add_filter( 'bulk_actions-' . $screen->id, '__return_empty_array' );
    add_filter( 'plugin_action_links', array( &$this, 'action_links' ), 10, 2 );
    add_action( 'admin_enqueue_scripts', array( &$this, 'scripts' ) );
}

Saat memberikan JS kami, kami juga akan menggunakan wp_localize_scriptuntuk mendapatkan nilai dari jumlah total "semua plugin".

function scripts()
{
    wp_enqueue_script(
        'cd-apd-js',
        CD_APD_URL . 'js/apd.js',
        array( 'jquery' ),
        null
    );
    wp_localize_script(
        'cd-apd-js',
        'cd_apd',
        array(
            'count' => esc_js( $this->all_count )
        )
    );
}

Dan tentu saja, JS hanyalah beberapa peretasan yang bagus untuk membuat tabel daftar aktif / tidak aktif untuk ditampilkan dengan benar. Kami juga akan menempel jumlah yang benar dari semua plugin kembali ke Alltautan.

jQuery(document).ready(function(){
    jQuery('li.all a').removeClass('current').find('span.count').html('(' + cd_apd.count + ')');
    jQuery('.wp-list-table.plugins tr').each(function(){
        var is_active = jQuery(this).find('a.cd-apd-deactivate');
        if(is_active.length) {
            jQuery(this).removeClass('inactive').addClass('active');
            jQuery(this).find('div.plugin-version-author-uri').removeClass('inactive').addClass('active');
        }
    });
});

Bungkus

Pemuatan direktori plugin tambahan sebenarnya tidak menarik. Membuat tabel daftar untuk ditampilkan dengan benar adalah bagian yang lebih sulit. Saya masih belum sepenuhnya puas dengan hasilnya, tapi mungkin seseorang dapat memperbaiki kodenya

chrisguitarguy
sumber
1
Impresif! Kerja yang sangat bagus. Saya akan meluangkan waktu selama akhir pekan untuk mempelajari kode Anda. Catatan: Ada fungsi __return_empty_array().
fuxia
Terima kasih! Umpan balik selalu diterima. Dimasukkan __return_empty_arrayfungsinya!
chrisguitarguy
1
Anda harus mengumpulkan daftar semua tempat di mana filter inti sederhana akan menyelamatkan Anda fungsi yang terpisah. Dan kemudian ... kirimkan tiket Trac.
fuxia
Ini sangat bagus. Akan lebih keren jika kita bisa menjadikan ini sebagai pustaka di dalam Tema (lihat komentar saya di Github: github.com/chrisguitarguy/WP-Plugin-Directories/issues/4 )
julien_c
1
+1 Tidak percaya saya melewatkan jawaban ini - kerja bagus! Saya akan memeriksa kode Anda lebih detail selama akhir pekan :). @Julien_c - mengapa Anda menggunakan ini di dalam tema?
Stephen Harris
2

Saya pribadi tidak tertarik memodifikasi UI, tapi saya suka tata letak sistem file yang lebih teratur, karena beberapa alasan.

Untuk itu, pendekatan lain adalah menggunakan symlink.

wp-content
    |-- plugins
        |-- acme-widgets               -> ../plugins-custom/acme-widgets
        |-- acme-custom-post-types     -> ../plugins-custom/acme-custom-post-types
        |-- acme-business-logic        -> ../plugins-custom/acme-business-logic
        |-- google-authenticator       -> ../plugins-external/google-authenticator
        |-- rest-api                   -> ../plugins-external/rest-api
        |-- quick-navigation-interface -> ../plugins-external/quick-navigation-interface
    |-- plugins-custom
        |-- acme-widgets
        |-- acme-custom-post-types
        |-- acme-business-logic
    |-- plugins-external
        |-- google-authenticator
        |-- rest-api
        |-- quick-navigation-interface

Anda bisa mengatur plugin khusus Anda plugins-custom, yang bisa menjadi bagian dari repositori kontrol versi proyek Anda.

Kemudian Anda dapat menginstal dependensi pihak ketiga ke plugins-external(melalui Komposer, atau Git submodules, atau apa pun yang Anda inginkan).

Kemudian Anda bisa memiliki skrip Bash sederhana atau perintah WP-CLI yang memindai direktori tambahan, dan membuat symlink pluginsuntuk setiap subfolder yang ditemukannya.

pluginsmasih akan berantakan, tetapi itu tidak masalah karena Anda hanya perlu berinteraksi dengan plugins-customdan plugins-external.

Penskalaan ke ndirektori tambahan akan mengikuti proses yang sama dengan dua yang pertama.

Ian Dunn
sumber
-3

Atau Anda juga dapat menggunakan KOMPOSER dengan jalur direktori khusus disetel untuk menunjuk ke folder konten-wp. Jika itu bukan jawaban langsung dari pertanyaan Anda adalah cara baru berpikir wordpress, beralihlah ke komposer sebelum memakan Anda.

Franzscisco Mai
sumber
Selesai pindah ke Komposer sejak lama. Silakan lihat tanggal pertanyaan ini. Selain itu: Ini bukan jawaban. Mungkin menunjukkan bagaimana sebenarnya mengatur ini?
kaiser