Dobra, zrobię to dźgnięciem. Kilka ograniczeń, które napotkałem po drodze:
W podklasach WP_List_Table nie ma wielu filtrów, a przynajmniej nie tam, gdzie ich potrzebujemy.
Z powodu tego braku filtrów nie możemy tak naprawdę utrzymywać dokładnej listy typów wtyczek na górze.
Musimy także użyć niesamowitych (czytaj: brudnych) hacków JavaScript, aby wyświetlić wtyczki jako aktywne.
Zawijam kod administratora w klasie, więc nazwy funkcji nie są poprzedzane. Możesz zobaczyć cały ten kod tutaj . Proszę przyczynić się!
Centralny interfejs API
Po prostu prosta funkcja, która ustawia zmienną globalną, która będzie zawierać nasze katalogi wtyczek w tablicy asocjacyjnej. $key
Będzie coś używanego wewnętrznie do pobierania wtyczek itd $dir
jest albo pełna ścieżka lub coś w stosunku do wp-content
katalogu. $label
będzie przeznaczone do wyświetlania w obszarze administracyjnym (np. ciąg do przetłumaczenia).
<?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
);
}
Następnie oczywiście musimy załadować wtyczki. Spóźnij się plugins_loaded
i przejrzyj aktywne wtyczki, ładując każdą z nich.
Obszar administracyjny
Ustawmy naszą funkcjonalność w klasie.
<?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
Wkroczymy plugins_loaded
naprawdę wcześnie i skonfigurujemy dozwolone „akcje”, których będziemy używać. Będą one obsługiwać aktywację i dezaktywację wtyczek, ponieważ wbudowane funkcje nie mogą tego zrobić z niestandardowymi katalogami.
function setup_actions()
{
$tmp = array(
'custom_activate',
'custom_deactivate'
);
$this->actions = apply_filters( 'custom_plugin_actions', $tmp );
}
Następnie jest podłączona funkcja load-plugins.php
. Robi to wiele fajnych rzeczy.
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' ) );
}
}
Przejrzyjmy to po kolei. get_plugins
metodą jest owinięcie wokół innej funkcji. Wypełnia atrybut plugins
danymi.
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_plugins
jest zgrywaniem wbudowanej get_plugins
funkcji bez sztywnego WP_CONTENT_DIR
i plugins
biznesowego. Zasadniczo: pobierz katalog z $wp_plugin_directories
globalnego, otwórz go, znajdź wszystkie pliki wtyczek. Przechowuj je w pamięci podręcznej na później.
<?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;
}
Następną kwestią jest nieznośny proces aktywacji i dezaktywacji wtyczek. Aby to zrobić, używamy handle_actions
metody. Jest to znowu rażąco zdzierane z góry głównego wp-admin/plugins.php
pliku.
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;
}
}
Ponownie kilka niestandardowych funkcji. cd_apd_activate_plugin
(oderwany od activate_plugin
) i cd_apd_deactivate_plugins
(oderwany od deactivate_plugins
). Oba są takie same, jak odpowiadające im funkcje „macierzyste” bez katalogów zakodowanych na stałe.
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;
}
I funkcja dezaktywacji
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 );
}
Istnieje również cd_apd_validate_plugin
funkcja, która oczywiście jest zrywaniem validate_plugin
bez zakodowanych śmieci.
<?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;
}
W porządku, z tym na uboczu. Możemy zacząć mówić o wyświetlaniu tabeli list
Krok 1: dodaj nasze widoki do listy u góry tabeli. Odbywa się to poprzez filtrowanie views_{$screen->id}
wewnątrz naszej init
funkcji.
add_filter( 'views_' . $screen->id, array( &$this, 'views' ) );
Wtedy rzeczywista funkcja zaczepiona po prostu zapętla się przez $wp_plugin_directories
. Jeśli jeden z nowo zarejestrowanych katalogów ma wtyczki, uwzględnimy go na wyświetlaczu.
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;
}
Pierwszą rzeczą, którą musimy zrobić, jeśli oglądamy niestandardową stronę katalogu wtyczek, jest ponowne filtrowanie widoków. Musimy pozbyć się inactive
liczby, ponieważ nie będzie dokładna. Konsekwencją braku filtrów tam, gdzie są potrzebne. Zaczep ponownie ...
if( $this->get_plugin_status() )
{
add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
}
I szybkie rozbrojenie ...
function views_again( $views )
{
if( isset( $views['inactive'] ) ) unset( $views['inactive'] );
return $views;
}
Następnie pozbądźmy się wtyczek, które w innym przypadku byłyby widoczne w tabeli list, i zastąpmy je naszymi niestandardowymi wtyczkami. Zaczep się all_plugins
.
if( $this->get_plugin_status() )
{
add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
add_filter( 'all_plugins', array( &$this, 'filter_plugins' ) );
}
Ponieważ już skonfigurowaliśmy nasze wtyczki i dane (patrz setup_plugins
wyżej), filter_plugins
metoda just (1) zapisuje liczbę wszystkich wtyczek na później i (2) zastępuje wtyczki w tabeli list.
function filter_plugins( $plugins )
{
if( $key = $this->get_plugin_status() )
{
$this->all_count = count( $plugins );
$plugins = $this->plugins[$key];
}
return $plugins;
}
A teraz zabijemy akcje masowe. Sądzę, że można je z łatwością wesprzeć?
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' );
}
Domyślne linki akcji wtyczek nie będą dla nas działać. Zamiast tego musimy skonfigurować własne (z niestandardowymi akcjami itp.). W init
funkcji.
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 );
}
Jedyne, co się tutaj zmienia, to (1) zmieniamy akcje, (2) utrzymujemy status wtyczki i (3) zmieniamy nieco nazwy 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&plugin=' . $plugin_file . '&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&plugin=' . $plugin_file . '&plugin_status=' . esc_attr( $context ), 'custom_deactivate-' . $plugin_file ),
__( 'Deactivate' )
);
}
return $links;
}
I na koniec, musimy tylko dodać trochę kodu JavaScript, aby go uzupełnić. W init
funkcji ponownie (tym razem wszystko razem).
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' ) );
}
Podczas kolejkowania naszego JS użyjemy również wp_localize_script
do uzyskania wartości całkowitej liczby „wszystkich wtyczek”.
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 )
)
);
}
I oczywiście JS to tylko kilka fajnych hacków, aby poprawnie włączyć / wyłączyć nieaktywne wtyczki tabeli listy. Wkleimy również prawidłową liczbę wszystkich wtyczek z powrotem do All
linku.
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');
}
});
});
Zakończyć
Rzeczywiste ładowanie dodatkowych katalogów wtyczek jest dość nieciekawe. Utrudnione jest uzyskanie prawidłowego wyświetlania tabeli list. Nadal nie jestem w pełni usatysfakcjonowany tym, jak się okazało, ale może ktoś może poprawić kod