it-swarm.com.de

Fügen Sie mehrere Plugin-Verzeichnisse hinzu

Die Aufgabe

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.

Hier ist die Liste der Aufgaben, die zu erfüllen sind:

  • Fügen Sie ein zusätzliches Plugin-Verzeichnis hinzu
  • Für jedes Plugin-Verzeichnis wird ein neuer "Tab" benötigt, wie hier gezeigt [1]
  • Das zusätzliche Verzeichnis hätte die gleiche Funktionalität wie das Standard-Plugin-Verzeichnis

Was ist für dich drin?

Die beste und vollständigste Antwort wird mit einem Kopfgeld belohnt.


[1] Zusätzliche Registerkarte für einen neuen Plugin-Ordner/-Verzeichnis

38
kaiser

Okay, ich werde es versuchen. Einige Einschränkungen, auf die ich unterwegs gestoßen bin:

  1. Es gibt nicht viele Filter in den Unterklassen von WP_List_Table, zumindest nicht dort, wo wir sie brauchen.

  2. Aufgrund des Fehlens von Filtern können wir oben keine genaue Liste der Plugin-Typen führen.

  3. 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&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;
}

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

27
chrisguitarguy

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.

2
Ian Dunn