Sie können mit register_theme_directory()
zusätzliche Themes-Verzeichnisse für Ihre WP -Installation registrieren. Leider bietet core nicht die gleiche Funktionalität für Plugins. Wir haben bereits MU-Plugins, Drop-Ins, Plugins und Themes. Für eine bessere Dateiorganisation benötigen wir jedoch mehr.
Die beste und vollständigste Antwort wird mit einem Kopfgeld belohnt.
[1] Zusätzliche Registerkarte für einen neuen Plugin-Ordner/-Verzeichnis
Okay, ich werde es versuchen. Einige Einschränkungen, auf die ich unterwegs gestoßen bin:
Es gibt nicht viele Filter in den Unterklassen von WP_List_Table, zumindest nicht dort, wo wir sie brauchen.
Aufgrund des Fehlens von Filtern können wir oben keine genaue Liste der Plugin-Typen führen.
Wir müssen auch einige großartige (read: dirty) JavaScript-Hacks verwenden, um Plugins als aktiv anzuzeigen.
Ich habe meine Admin-Vorwahl in eine Klasse eingeschlossen, damit meinen Funktionsnamen kein Präfix vorangestellt wird. Sie können den gesamten Code sehen hier . Bitte tragen Sie bei!
Zentrale API
Nur eine einfache Funktion, die eine globale Variable erstellt, die unsere Plugin-Verzeichnisse in einem assoziativen Array enthält. Der $key
wird intern zum Abrufen von Plugins usw. verwendet. $dir
ist entweder ein vollständiger Pfad oder etwas relativ zum wp-content
-Verzeichnis. $label
wird für unsere Anzeige im Admin-Bereich verwendet (z. B. eine übersetzbare Zeichenfolge).
<?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
);
}
Dann müssen wir natürlich die Plugins laden. Hängen Sie sich spät in plugins_loaded
ein und gehen Sie die aktiven Plugins durch, wobei Sie die einzelnen Plugins laden.
Admin-Bereich
Lassen Sie uns unsere Funktionalität in einer Klasse einrichten.
<?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
Wir werden uns sehr früh in plugins_loaded
einklinken und die erlaubten "Aktionen" einrichten, die wir verwenden werden. Diese behandeln die Aktivierung und Deaktivierung des Plugins, da die eingebauten Funktionen dies mit benutzerdefinierten Verzeichnissen nicht tun können.
function setup_actions()
{
$tmp = array(
'custom_activate',
'custom_deactivate'
);
$this->actions = apply_filters( 'custom_plugin_actions', $tmp );
}
Dann gibt es die Funktion, die in load-plugins.php
eingebunden ist. Das macht alle möglichen lustigen Sachen.
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' ) );
}
}
Lassen Sie uns diese eine Sache nach der anderen durchgehen. Die get_plugins
-Methode ist ein Wrapper um eine andere Funktion. Es füllt das Attribut plugins
mit Daten.
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
ist eine Kopie der integrierten get_plugins
-Funktion ohne das fest codierte Geschäft mit WP_CONTENT_DIR
und plugins
. Grundsätzlich gilt: Hole das Verzeichnis aus dem $wp_plugin_directories
global, öffne es, finde alle Plugin-Dateien. Speichern Sie sie für später im Cache.
<?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;
}
Als nächstes geht es darum, Plugins tatsächlich zu aktivieren und zu deaktivieren. Dazu verwenden wir die Methode handle_actions
. Dies ist wiederum krass von der Spitze der Kerndatei wp-admin/plugins.php
abgerissen.
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;
}
}
Ein paar benutzerdefinierte Funktionen hier wieder. cd_apd_activate_plugin
(von activate_plugin
abgerissen) und cd_apd_deactivate_plugins
(von deactivate_plugins
abgerissen). Beide sind die gleichen wie ihre jeweiligen "Eltern" -Funktionen ohne die fest codierten Verzeichnisse.
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;
}
Und die Deaktivierungsfunktion
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 );
}
Es gibt auch die Funktion cd_apd_validate_plugin
, die natürlich eine Abzocke von validate_plugin
ohne den fest codierten Müll ist.
<?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;
}
Okay, damit aus dem Weg. Wir können tatsächlich anfangen, über die Listentabelle Anzeige zu sprechen
Schritt 1: Fügen Sie unsere Ansichten zur Liste oben in der Tabelle hinzu. Dies geschieht durch Filtern von views_{$screen->id}
in unserer Funktion init
.
add_filter( 'views_' . $screen->id, array( &$this, 'views' ) );
Dann durchläuft die eigentliche Hook-Funktion einfach den $wp_plugin_directories
. Wenn eines der neu registrierten Verzeichnisse Plugins enthält, werden diese in die Anzeige aufgenommen.
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;
}
Das erste, was wir tun müssen, wenn wir eine benutzerdefinierte Plugin-Verzeichnisseite anzeigen, ist, die Ansichten erneut zu filtern. Wir müssen die Anzahl inactive
loswerden, weil sie nicht genau sein wird. Eine Folge davon, dass es keine Filter gibt, wo wir sie brauchen. Haken Sie wieder ein ...
if( $this->get_plugin_status() )
{
add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
}
Und ein kurzes Loslassen ...
function views_again( $views )
{
if( isset( $views['inactive'] ) ) unset( $views['inactive'] );
return $views;
}
Lassen Sie uns als nächstes die Plugins entfernen, die Sie sonst in der Listentabelle gesehen hätten, und sie durch unsere benutzerdefinierten Plugins ersetzen. Hängen Sie sich in all_plugins
ein.
if( $this->get_plugin_status() )
{
add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
add_filter( 'all_plugins', array( &$this, 'filter_plugins' ) );
}
Da wir bereits unsere Plugins und Daten eingerichtet haben (siehe setup_plugins
oben), speichert die filter_plugins
-Methode nur (1) die Anzahl aller Plugins für einen späteren Zeitpunkt und (2) ersetzt die Plugins in der Listentabelle.
function filter_plugins( $plugins )
{
if( $key = $this->get_plugin_status() )
{
$this->all_count = count( $plugins );
$plugins = $this->plugins[$key];
}
return $plugins;
}
Und jetzt werden wir die Massenaktionen töten. Diese könnten leicht unterstützt werden, nehme ich an?
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' );
}
Die standardmäßigen Plugin-Aktionslinks funktionieren bei uns nicht. Also müssen wir stattdessen unsere eigenen einrichten (mit den benutzerdefinierten Aktionen usw.). In der Funktion init
.
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 );
}
Die einzigen Dinge, die hier geändert werden, sind: (1) Wir ändern die Aktionen, (2) den Plugin-Status beizubehalten und (3) die Nonce-Namen ein wenig zu ändern.
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;
}
Und zum Schluss müssen wir nur noch JavaScript in die Warteschlange stellen, um das Ganze abzurunden. Wieder in der Funktion init
(diesmal alle zusammen).
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' ) );
}
Während wir unser JS in die Warteschlange stellen, verwenden wir auch wp_localize_script
, um den Wert der Gesamtzahl aller Plugins abzurufen.
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 )
)
);
}
Und natürlich ist der JS nur ein paar nette Hacks, um die Listentabelle aktiv/inaktiv Plugins richtig anzuzeigen. Wir werden auch die korrekte Anzahl aller Plugins wieder in den All
-Link einfügen.
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');
}
});
});
Zusammenfassung
Das eigentliche Laden zusätzlicher Plugin-Verzeichnisse ist ziemlich aufregend. Schwieriger ist es, die Listentabelle korrekt anzuzeigen. Ich bin immer noch nicht ganz zufrieden mit dem Ergebnis, aber vielleicht kann jemand den Code verbessern
Ich persönlich habe kein Interesse daran, die Benutzeroberfläche zu ändern, aber ich würde aus mehreren Gründen ein besser organisiertes Dateisystem-Layout begrüßen.
Zu diesem Zweck wäre ein anderer Ansatz die Verwendung von Symlinks.
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
Sie können Ihre benutzerdefinierten Plugins in plugins-custom
einrichten, das Teil des Versionskontroll-Repository Ihres Projekts sein kann.
Dann könnten Sie Abhängigkeiten von Drittanbietern in plugins-external
installieren (über Composer, Git-Submodule oder was auch immer Sie bevorzugen).
Dann können Sie ein einfaches Bash-Skript oder einen WP-CLI-Befehl verwenden, der die zusätzlichen Verzeichnisse durchsucht und für jeden gefundenen Unterordner einen Symlink in plugins
erstellt.
plugins
wäre immer noch unübersichtlich, aber es wäre egal, da Sie nur mit plugins-custom
und plugins-external
interagieren müssten.
Das Skalieren in n
zusätzliche Verzeichnisse würde dem gleichen Prozess folgen wie die ersten beiden.