您可以使用
register_theme_directory()
注册用于WP安装的添加其他主题目录。遗憾的是,核心并没有提供与插件相同的功能。我们已经有MU-Plugin,Drop-Ins,插件和主题。但是我们需要更多的资源来更好地组织文件。以下是要完成的任务:
为每个插件添加一个附加的插件目录
目录,需要一个新的“标签”,如下所示[1]
附加目录的功能与默认插件目录具有的功能相同。
其中有什么功能您?
最佳和最完整的答案将被授予赏金。
[1]新插件文件夹/目录的附加选项卡
#1 楼
好吧,我将对此采取行动。我在使用过程中遇到的一些局限性:WP_List_Table的子类中没有很多过滤器,至少没有我们需要的过滤器。
由于缺少过滤器,我们无法真正在顶部维护准确的
插件类型列表。
我们还必须使用一些很棒的(阅读:肮脏的)JavaScript hack
将插件显示为活动。
我将管理区代码包装在一个类中,因此我的函数名称没有前缀。您可以在此处查看所有这些代码。请贡献!
中央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
并浏览活动的插件,并加载它们。管理区域
让我们设置功能在班级内部。
<?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_DIR
和plugins
业务。基本上:从全局$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;
}
好了,道路。我们实际上可以开始谈论列表表的显示了。
步骤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
方法仅(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)稍微更改现时名称。
最后,我们只需要加入一些JavaScript即可。再次在
init
函数中(这次全部在一起)。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;
}
排队我们的JS时,我们还将使用
wp_localize_script
来获取“所有插件”总数的值计数。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只是一些不错的技巧,可以使列表表活动/不活动插件正确显示。我们还将所有插件的正确计数重新粘贴到
All
链接中。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 )
)
);
}
总结
实际加载其他插件目录非常令人兴奋。使列表正确显示是最困难的部分。我仍然不完全满意结果,但是也许有人可以改善代码
评论
令人印象深刻!真的很好周末我会花一些时间研究您的代码。注意:有一个函数__return_empty_array()。
– fuxia♦
2012年9月9日7:05
谢谢!始终欢迎反馈。合并了__return_empty_array函数!
–chrisguitarguy
2012年9月9日在7:14
您应该收集所有地方的列表,在这些地方,简单的核心过滤器可以为您节省一个单独的功能。然后…提交Trac票。
– fuxia♦
2012年9月9日在7:18
真的很棒如果我们可以将其作为主题库中的库,那就更好了(请参阅我在Github上的评论:github.com/chrisguitarguy/WP-Plugin-Directories/issues/4)
– julien_c
2012年3月28日上午10:30
+1不能相信我会错过这个答案-太好了!周末我将详细检查您的代码:)。 @Julien_c-为什么要在主题中使用它?
–斯蒂芬·哈里斯(Stephen Harris)
2012年4月14日上午9:48
#2 楼
我个人对修改UI没有任何兴趣,但是出于某些原因,我希望有一个更有条理的文件系统布局。为此,另一种方法是使用符号链接。 br />
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
中设置自定义插件,该插件可以是项目的版本控制存储库的一部分。那么您可以安装3rd-方依赖关系进入
plugins-external
(通过Composer或Git子模块,或您喜欢的任何方式)。那么您可以使用简单的Bash脚本或WP-CLI命令来扫描其他目录,并在其中创建符号链接
plugins
仍然很混乱,但这并不重要,因为您只需要与plugins
和plugins-custom
进行交互。plugins-external
额外的目录将遵循与前两个相同的过程。#3 楼
或者,您也可以使用COMPOSER,并将自定义目录路径设置为指向wp-content文件夹。如果不是您的问题的直接答案,这是一种新的思考wordpress的方法,请在您陷入困境之前继续尝试作曲。评论
很久以前就搬到Composer。请查询该问题的日期。除此之外:这并不是真正的答案。也许显示如何实际设置?
– kaiser
16年11月16日在12:07
评论
由于目录结构与目录常量紧密相关,因此我怀疑在文件系统级别执行此操作是否可行(没有采用核心技术)。在扩展级别上,管理员中组织的虚拟层可能更容易实现。@Rarst不应阻止您添加想法:)
这将是一个很棒的功能。
功能听起来不错。只需反向工程核心,弄清楚应该如何完成(WP方式),然后向开发人员提交补丁...您需要查看register_theme_directory()-search_theme_directories()-get_raw_theme_root()-get_theme_roots ()-get_theme()-get_themes()
伙计们:提交什么?仅供参考:这是一个问题,不是完整的代码的答案:)仅供参考:在Trac上有一张新票,用于将get_themes()重写为类。