Bien, voy a apuñalar esto. Algunas limitaciones que encontré en el camino:
No hay muchos filtros en las subclases de WP_List_Table, al menos no donde necesitamos que estén.
Debido a la falta de filtros, realmente no podemos mantener una lista precisa de tipos de complementos en la parte superior.
También tenemos que usar algunos hacks de JavaScript impresionantes (léase: sucios) para mostrar los complementos como activos.
Envolví mi código de área de administración dentro de una clase, por lo que los nombres de mis funciones no tienen prefijo. Puedes ver todo este código aquí . Por favor contribuya!
API central
Solo una función simple que configura una variable global que contendrá nuestros directorios de complementos en una matriz asociativa. El $key
va a ser algo que se usa internamente en busca de plugins, etc $dir
es una ruta o algo lleno en relación con el wp-content
directorio. $label
va a ser para nuestra visualización en el área de administración (por ejemplo, una cadena traducible).
<?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
);
}
Entonces, por supuesto, necesitamos cargar los complementos. Conéctate plugins_loaded
tarde y ve a través de los complementos activos, cargando cada uno.
Área de administración
Configuremos nuestra funcionalidad dentro de una clase.
<?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
Nos conectaremos plugins_loaded
muy pronto y configuraremos las "acciones" permitidas que usaremos. Estos manejarán la activación y desactivación del complemento ya que las funciones integradas no pueden hacerlo con directorios personalizados.
function setup_actions()
{
$tmp = array(
'custom_activate',
'custom_deactivate'
);
$this->actions = apply_filters( 'custom_plugin_actions', $tmp );
}
Luego está la función enganchada load-plugins.php
. Esto hace todo tipo de cosas divertidas.
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' ) );
}
}
Veamos esa cosa a la vez. El get_plugins
método, es un contenedor alrededor de otra función. Llena el atributo plugins
con datos.
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
es una copia de la get_plugins
función incorporada sin el hardcoded WP_CONTENT_DIR
y el plugins
negocio. Básicamente: obtenga el directorio del $wp_plugin_directories
global, ábralo, encuentre todos los archivos de complemento. Guárdelos en el caché para más tarde.
<?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;
}
El siguiente es el molesto negocio de activar y desactivar complementos. Para hacer esto, usamos el handle_actions
método. Esto es, nuevamente, descaradamente arrancado de la parte superior del wp-admin/plugins.php
archivo central .
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;
}
}
Un par de funciones personalizadas aquí nuevamente. cd_apd_activate_plugin
(arrancado de activate_plugin
) y cd_apd_deactivate_plugins
(arrancado de deactivate_plugins
). Ambos son los mismos que sus respectivas funciones "principales" sin los directorios codificados.
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;
}
Y la función de desactivación
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 );
}
También hay una cd_apd_validate_plugin
función, que por supuesto, es una estafa validate_plugin
sin la basura codificada.
<?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;
}
Muy bien, con eso fuera del camino. Podemos comenzar a hablar sobre la visualización de la tabla de listas
Paso 1: agregue nuestras vistas a la lista en la parte superior de la tabla. Esto se hace filtrando views_{$screen->id}
dentro de nuestra init
función.
add_filter( 'views_' . $screen->id, array( &$this, 'views' ) );
Luego, la función enganchada real solo recorre el $wp_plugin_directories
. Si uno de los directorios recién registrados tiene complementos, lo incluiremos en la pantalla.
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;
}
Lo primero que debemos hacer si estamos viendo una página de directorio de complementos personalizada es filtrar las vistas nuevamente. Necesitamos deshacernos del inactive
conteo porque no será preciso. Una consecuencia de que no hay filtros donde los necesitamos. Enganchar de nuevo ...
if( $this->get_plugin_status() )
{
add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
}
Y un rápido desarmado ...
function views_again( $views )
{
if( isset( $views['inactive'] ) ) unset( $views['inactive'] );
return $views;
}
A continuación, eliminemos los complementos que de otro modo habría visto en la tabla de lista y reemplácelos con nuestros complementos personalizados. Enganchar all_plugins
.
if( $this->get_plugin_status() )
{
add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
add_filter( 'all_plugins', array( &$this, 'filter_plugins' ) );
}
Como ya configuramos nuestros complementos y datos (ver setup_plugins
arriba), el filter_plugins
método solo (1) guarda el recuento de todos los complementos para más adelante, y (2) reemplaza los complementos en la tabla de la lista.
function filter_plugins( $plugins )
{
if( $key = $this->get_plugin_status() )
{
$this->all_count = count( $plugins );
$plugins = $this->plugins[$key];
}
return $plugins;
}
Y ahora mataremos las acciones masivas. Estos podrían ser fácilmente compatibles, supongo.
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' );
}
Los enlaces de acciones de complementos predeterminados no funcionarán para nosotros. Entonces, en cambio, necesitamos configurar el nuestro (con las acciones personalizadas, etc.). En la init
funcion.
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 );
}
Las únicas cosas que se cambian aquí son (1) estamos cambiando las acciones, (2) manteniendo el estado del complemento y (3) cambiando un poco los nombres 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;
}
Y finalmente, solo necesitamos poner en cola JavaScript para completarlo. En la init
función de nuevo (todos juntos esta vez).
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' ) );
}
Al poner en cola ourJS, también usaremos wp_localize_script
para obtener el valor del recuento total de "todos los complementos".
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 )
)
);
}
Y, por supuesto, el JS es solo un buen truco para que la lista de complementos activos / inactivos de la tabla se muestre correctamente. También pegaremos el recuento correcto de todos los complementos en el All
enlace.
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');
}
});
});
Envolver
La carga real de directorios de complementos adicionales es bastante poco emocionante. Lograr que la tabla de la lista se muestre correctamente es la parte más difícil. Todavía no estoy completamente satisfecho con cómo resultó, pero tal vez alguien pueda mejorar el código