web-dev-qa-db-fra.com

Ajouter plusieurs répertoires de plugins

La tâche

Vous pouvez enregistrer des répertoires de thèmes supplémentaires en utilisant register_theme_directory() pour votre installation WP. Malheureusement, le noyau ne fournit pas la même fonctionnalité pour les plugins. Nous avons déjà MU-Plugin, Drop-Ins, Plugins et Thèmes. Mais nous avons besoin de plus pour une meilleure organisation des fichiers.

Voici la liste des tâches à accomplir:

  • Ajouter un répertoire de plugin supplémentaire
  • Pour chaque répertoire de plugin, un nouvel "onglet" est nécessaire, comme indiqué ici [1]
  • Le répertoire supplémentaire aurait les mêmes fonctionnalités que le répertoire du plugin par défaut

Qu'y a-t-il pour vous?

La meilleure et la plus complète des réponses se verra attribuer une prime.


[1] Onglet supplémentaire pour un nouveau dossier/répertoire de plugin

38
kaiser

D'accord, je vais tenter un coup. Quelques limitations que j'ai rencontrées en cours de route:

  1. Il n'y a pas beaucoup de filtres dans les sous-classes de WP_List_Table, du moins aucun endroit où nous en avons besoin.

  2. En raison de ce manque de filtres, nous ne pouvons pas vraiment maintenir une liste précise des types de plugins en haut.

  3. Nous devons également utiliser des hacks JavaScript géniaux (read: dirty) pour afficher les plugins comme actifs.

J'ai enveloppé mon code d'administrateur dans une classe afin que mes noms de fonctions ne soient pas préfixés. Vous pouvez voir tout ce code ici . S'il vous plaît contribuer!

API centrale

Juste une simple fonction qui configure une variable globale qui contiendra nos répertoires de plugins dans un tableau associatif. Le $key sera quelque chose utilisé en interne pour récupérer des plugins, etc. $dir est soit un chemin complet, soit quelque chose relatif au répertoire wp-content. $label sera pour notre affichage dans la zone d'administration (par exemple, une chaîne traduisible).

<?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
    );
}

Ensuite, bien sûr, nous devons charger les plugins. Accédez à plugins_loaded de manière tardive et parcourez les plugins actifs, en les chargeant chacun.

Zone Admin

Mettons en place nos fonctionnalités dans une classe.

<?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

Nous allons nous attacher très tôt à plugins_loaded et configurer les "actions" autorisées que nous allons utiliser. Ceux-ci géreront l'activation et la désactivation du plug-in, car les fonctions intégrées ne peuvent pas le faire avec des répertoires personnalisés.

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

Ensuite, il y a la fonction accrochée à load-plugins.php. Cela fait toutes sortes de choses amusantes.

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' ) );
    }
}

Passons en revue cette chose à la fois. la méthode get_plugins est une enveloppe autour d'une autre fonction. Il remplit l'attribut plugins avec des données.

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 est une copie de la fonction intégrée get_plugins sans les affaires codées en dur WP_CONTENT_DIR et plugins. Fondamentalement: obtenez le répertoire à partir du $wp_plugin_directories global, ouvrez-le, recherchez tous les fichiers du plugin. Stockez-les dans la cache pour plus tard.

<?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;
}

La prochaine étape consiste à activer et à désactiver les plugins. Pour ce faire, nous utilisons la méthode handle_actions. Ceci est, encore une fois, arraché de manière flagrante au sommet du fichier wp-admin/plugins.php principal.

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;
    }

}

Quelques fonctions personnalisées ici encore. cd_apd_activate_plugin (extrait de activate_plugin) et cd_apd_deactivate_plugins (extrait de deactivate_plugins). Les deux sont les mêmes que leurs fonctions "parent" respectives sans les répertoires codés en dur.

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;
}

Et la fonction de désactivation

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 );
}

Il y a aussi la fonction cd_apd_validate_plugin qui, bien sûr, est une arnaque de validate_plugin sans les fichiers codés en dur.

<?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;
}

D'accord, avec ça à l'écart. Nous pouvons en fait commencer à parler de list table display

Étape 1: ajoutez nos vues à la liste en haut du tableau. Ceci est fait en filtrant views_{$screen->id} dans notre fonction init.

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

Ensuite, la fonction accrochée passe en boucle à travers le $wp_plugin_directories. Si l'un des répertoires nouvellement enregistrés contient des plugins, nous l'inclurons dans l'affichage.

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;
}

La première chose que nous devons faire si nous visionnons une page de répertoire de plugin personnalisée est de filtrer à nouveau les vues. Nous devons nous débarrasser du nombre inactive car il ne sera pas précis. Une conséquence de l'absence de filtres là où nous en avons besoin. Raccroche à nouveau ...

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

Et un rapide non réglé ...

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

Ensuite, supprimons les plugins que vous auriez autrement vus dans la liste, et remplacez-les par nos plugins personnalisés. Accrocher dans all_plugins.

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

Puisque nous avons déjà configuré nos plugins et nos données (voir setup_plugins ci-dessus), la méthode filter_plugins vient (1) enregistre le nombre de tous les plugins pour plus tard et (2) remplace les plugins dans la liste.

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

Et maintenant, nous allons tuer les actions en bloc. Ceux-ci pourraient facilement être pris en charge, je suppose?

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' );
}

Les liens des actions de plug-in par défaut ne vont pas fonctionner pour nous. Nous devons donc créer nos propres actions (avec les actions personnalisées, etc.). Dans la fonction 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 );
}

Les seules choses qui changent ici sont (1) nous changeons les actions, (2) conservons le statut du plug-in et (3) modifions un peu les noms de 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;
}

Et enfin, nous avons juste besoin de mettre en file d'attente du JavaScript pour couronner le tout. Dans la fonction init à nouveau (tous ensemble cette fois).

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' ) );
}

Lors de la mise en file d'attente de notre serveur JS, nous utiliserons également wp_localize_script pour obtenir la valeur du nombre total de "tous les plugins".

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 )
        )
    );
}

Et bien sûr, le JS n'est que quelques astuces de Nice pour obtenir l'affichage correct des plugins active/inactive de la table de la liste. Nous allons également réintégrer le nombre correct de plug-ins dans le lien All.

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');
        }
    });
});

Envelopper

Le chargement réel de répertoires de plugins supplémentaires est assez peu excitant. Obtenir la table de liste pour afficher correctement est la partie la plus difficile. Je ne suis toujours pas complètement satisfait du résultat, mais peut-être que quelqu'un peut améliorer le code

27
chrisguitarguy

Personnellement, je n'ai aucun intérêt à modifier l'interface utilisateur, mais j'aimerais une structure de système de fichiers plus organisée, pour plusieurs raisons.

À cette fin, une autre approche consisterait à utiliser des liens symboliques.

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

Vous pouvez configurer vos plugins personnalisés dans plugins-custom, ce qui pourrait faire partie du référentiel de contrôle de version de votre projet.

Vous pouvez ensuite installer des dépendances tierces dans plugins-external (via Composer, ou les sous-modules Git, ou ce que vous préférez).

Vous pouvez ensuite avoir un simple script Bash ou une commande WP-CLI qui analyse les répertoires supplémentaires et crée un lien symbolique dans plugins pour chaque sous-dossier trouvé.

plugins serait toujours encombré, mais cela n'aurait pas d'importance, car il vous suffirait d'interagir avec plugins-custom et plugins-external.

Le redimensionnement en n répertoires supplémentaires suivrait le même processus que les deux premiers.

2
Ian Dunn