添加多个插件目录

时间:2012-02-23 作者:kaiser

任务

您可以使用注册添加其他主题目录register_theme_directory() 用于WP安装。遗憾的是,core并没有为插件提供相同的功能。我们已经有了MU插件、插件、插件和主题。但我们需要更多的资源来实现更好的文件组织。

以下是要完成的任务列表:

  • 为每个插件目录添加一个额外的插件目录,需要一个新的“选项卡”,如图所示。额外的目录将具有与默认插件目录相同的功能,其中为您提供了什么最佳和最完整的答案将获得悬赏。

    新插件文件夹/目录的附加选项卡

3 个回复
最合适的回答,由SO网友:chrisguitarguy 整理而成

好吧,我试试看。我在这一过程中遇到了一些限制:

WP\\u List\\u表的子类中没有太多的过滤器,至少没有我们需要它们的地方。

由于缺少过滤器,我们无法真正将插件类型的准确列表保持在顶部。

我们还必须使用一些可怕的JavaScript黑客来显示插件的活动状态。

我将管理区号包装在一个类中,因此我的函数名没有前缀。您可以看到所有这些代码here. 请投稿!

Central API

这只是一个简单的函数,它设置了一个全局变量,该变量将在关联数组中包含我们的插件目录。这个$key 将在内部用于获取插件等。$dir 是完整路径或与wp-content 目录$label 将在管理区域显示(例如,可翻译字符串)。

<?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
    );
}
然后,当然,我们需要加载插件。钩入plugins_loaded 很晚了,检查活动插件,加载每个插件。

Admin Area

让我们在类中设置我们的功能。

<?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
我们要加入plugins_loaded 非常早,设置我们将要使用的允许的“操作”。这些将处理插件的激活和停用,因为内置函数无法处理自定义目录。

function setup_actions()
{
    $tmp = array(
        \'custom_activate\',
        \'custom_deactivate\'
    );
    $this->actions = apply_filters( \'custom_plugin_actions\', $tmp );
}
然后是连接到load-plugins.php. 这可以做各种有趣的事情。

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\' ) );
    }
}
让我们一次只做一件事。这个get_plugins 方法,是另一个函数的包装器。它填充属性plugins 使用数据。

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 是内置的撕裂get_plugins 不带硬编码的功能WP_CONTENT_DIRplugins 商业基本上:从$wp_plugin_directories 全局,打开它,找到所有插件文件。将它们存储在缓存中以备将来使用。

<?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;
}
接下来是实际激活和停用插件的麻烦事。为此,我们使用handle_actions 方法这又是从核心顶部公然撕下的wp-admin/plugins.php 文件

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

}
这里又有几个自定义函数。cd_apd_activate_plugin (摘自activate_plugin) 和cd_apd_deactivate_plugins (摘自deactivate_plugins). 两者都与各自的“父”函数相同,没有硬编码目录。

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;
}
和停用功能

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 );
}
还有cd_apd_validate_plugin 函数,当然是对validate_plugin 没有硬编码的垃圾。

<?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;
}
好吧,别挡道了。我们可以开始讨论list table display

步骤1:将我们的视图添加到表顶部的列表中。这是通过过滤完成的views_{$screen->id} 在我们的init 作用

add_filter( \'views_\' . $screen->id, array( &$this, \'views\' ) );
然后,实际的钩子函数只是在$wp_plugin_directories. 如果其中一个新注册的目录有插件,我们将在屏幕上显示它。

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;
}
如果我们碰巧看到一个自定义插件目录页面,我们需要做的第一件事就是再次过滤视图。我们需要摆脱inactive 数一数,因为它不会准确。这是因为在我们需要的地方没有过滤器。再次上钩。。。

if( $this->get_plugin_status() )
{
    add_filter( \'views_\' . $screen->id, array( &$this, \'views_again\' ) );
}
和快速取消设置。。。

function views_again( $views )
{
    if( isset( $views[\'inactive\'] ) ) unset( $views[\'inactive\'] );
    return $views;
}
接下来,让我们去掉列表中的插件,用我们的自定义插件替换它们。钩入all_plugins.

if( $this->get_plugin_status() )
{
    add_filter( \'views_\' . $screen->id, array( &$this, \'views_again\' ) );
    add_filter( \'all_plugins\', array( &$this, \'filter_plugins\' ) );
}
因为我们已经设置了插件和数据(请参见setup_plugins ,则filter_plugins 方法just(1)保存所有插件的计数以备将来使用,并且(2)替换列表中的插件。

function filter_plugins( $plugins )
{
    if( $key = $this->get_plugin_status() )
    {
        $this->all_count = count( $plugins );
        $plugins = $this->plugins[$key];
    }
    return $plugins;
}
现在我们将取消批量操作。我想这些很容易得到支持吧?

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\' );
}
默认的插件操作链接对我们不起作用。所以,我们需要建立自己的(使用自定义操作等)。在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 );
}
这里唯一需要更改的是(1)我们正在更改操作,(2)保持插件状态,以及(3)稍微更改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;
}
最后,我们只需要将一些JavaScript排队以结束它。在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 );
    add_action( \'admin_enqueue_scripts\', array( &$this, \'scripts\' ) );
}
在将我们的JS排队时,我们还将使用wp_localize_script 获取“所有插件”总数的值。

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 )
        )
    );
}
当然,JS只是一些很好的技巧,可以让列表表的活动/非活动插件正确显示。我们还将把所有插件的正确计数粘贴回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\');
        }
    });
});

Wrap Up

额外插件目录的实际加载非常乏味。让列表正确显示是更困难的部分。我仍然不完全满意结果,但也许有人可以improve the code

SO网友:Ian Dunn

我个人对修改UI没有任何兴趣,但出于几个原因,我希望文件系统布局更有条理。

为此,另一种方法是使用符号链接。

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
您可以在中设置自定义插件plugins-custom, 它可能是项目版本控制存储库的一部分。

然后您可以将第三方依赖项安装到plugins-external (通过Composer、Git子模块或任何您喜欢的方式)。

然后,您可以使用一个简单的Bash脚本或WP-CLI命令来扫描其他目录,并在中创建符号链接plugins 对于找到的每个子文件夹。

plugins 仍然会很混乱,但这并不重要,因为你只需要与plugins-customplugins-external.

缩放到n 额外的目录将遵循与前两个相同的过程。

SO网友:Franzscisco Mai

或者您也可以使用COMPOSER,将自定义目录路径设置为指向wp content文件夹。如果这不是一个直接的答案,你的问题是一种新的思维方式wordpress,请在它吞噬你之前转到composer。

结束