任务
您可以使用注册添加其他主题目录register_theme_directory()
用于WP安装。遗憾的是,core并没有为插件提供相同的功能。我们已经有了MU插件、插件、插件和主题。但我们需要更多的资源来实现更好的文件组织。以下是要完成的任务列表:
- 为每个插件目录添加一个额外的插件目录,需要一个新的“选项卡”,如图所示。额外的目录将具有与默认插件目录相同的功能,其中为您提供了什么最佳和最完整的答案将获得悬赏。
新插件文件夹/目录的附加选项卡
register_theme_directory()
用于WP安装。遗憾的是,core并没有为插件提供相同的功能。我们已经有了MU插件、插件、插件和主题。但我们需要更多的资源来实现更好的文件组织。新插件文件夹/目录的附加选项卡
好吧,我试试看。我在这一过程中遇到了一些限制:
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_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;
}
好吧,别挡道了。我们可以开始讨论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&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;
}
最后,我们只需要将一些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我个人对修改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-custom
和plugins-external
.
缩放到n
额外的目录将遵循与前两个相同的过程。
或者您也可以使用COMPOSER,将自定义目录路径设置为指向wp content文件夹。如果这不是一个直接的答案,你的问题是一种新的思维方式wordpress,请在它吞噬你之前转到composer。
我正在制作一个插件,它需要一个可以从外部访问的页面,非常像一个API,并且有这样的url,http://xxxxx/custom_method?parameter=xxxxx&something=xxxx有没有干净的方法可以做到这一点?提前谢谢。