WordPress.org

Make WordPress Core

Opened 4 years ago

Closed 4 years ago

Last modified 4 weeks ago

#11308 closed enhancement (worksforme)

Handling plugin dependencies

Reported by: apeatling Owned by: westi
Milestone: Priority: normal
Severity: normal Version:
Component: Plugins Keywords: plugins
Focuses: Cc:

Description

I wanted to start a ticket for this so I have my ideas down for discussion.

BuddyPress would benefit greatly if WordPress could natively handle plugin dependancies, but I can also see this being a useful feature for plugins that want to use the existing WP plugin API for extensions.

As it stands, users who install plugins designed for BuddyPress have to manually deactivate each plugin before upgrading or deactivating BuddyPress. They also need to make sure BuddyPress is active before installing a dependant plugin (many people don't and see errors).

So, I think two things should be handled:

  1. Making sure the plugin that is being depended on is active before activating the dependant plugin.
  1. Making sure when the plugin being depended on is deactivated, all dependant plugins are first deactivated.

This could be kept very simple for plugin authors. They could add something like this to their plugin comment header:

Requires: plugin-name [, plugin-name ... ]

I'll have a crack at a patch for this sometime in the new year, happy to debate this thoroughly before then.

Change History (27)

comment:1 apeatling4 years ago

Apparently I also like to switch between the British and American spelling of "dependent". Figures.

comment:2 filosofo4 years ago

I think the advantages of something like this---in contrast to the "dependent" plugin's doing its own checking for needed functionality, defined classes, etc.,---are outweighed by the problems it introduces.

  • The proposed system is (by necessity) too binary: a plugin is either a requirement or it isn't. In contrast, plugins checking for their own needed functionality can disable / enable features as needed, granularly.
  • There's the difficult problem of name collisions. The "Requires:" line really needs to use GUIDs.
  • The dilemma of versions with the parent plugin: if the dependency-check isn't aware of versions, it's not that helpful. If it is, it becomes much more complicated.
  • It adds extra code and responsibility to WP to solve problems introduced by a minority of plugins, when those problems should and can be solved by the plugins themselves.

As it stands, users who install plugins designed for BuddyPress have to manually deactivate each plugin before upgrading or deactivating BuddyPress. They also need to make sure BuddyPress is active before installing a dependant plugin (many people don't and see errors).

This is just sloppy plugin design. At the very least, the plugin authors could wrap everything in a class_exists conditional for one of the core BP classes.

My Counter-Proposal

Instead of trying to sort out dependencies on the WP side of things, let's provide better tools for dependent plugins to use. Some ideas:

  • Make it even simpler to add warning messages, complete with styling, to the admin, so plugins can say with one function call things like "you must have plugin X activated to use the full features of this...".
  • Allow plugins to set more easily the CSS classes on their plugin's plugin-page row, and have a class that has to do with partial functionality. That will allow plugin authors another way to give a visual cue to users that their plugin needs something done before it will work.
  • Encourage "parent"-like plugins, such as BuddyPress, to provide action hooks and the like, to which dependent plugins can attach feature-activating callbacks (e.g. maybe do_action('buddypress_init'); ?).

comment:3 apeatling4 years ago

I agree with what you are saying, many of the problems can be fixed by plugin developers themselves. I still feel this is a good idea on its most simple level. I don't think it should be concerned with version numbers or guids, it will just look to see if a matching plugin exists and loads it first. The rest is up to the user.

There is still the problem of load order which can't be fixed by any of your suggestions. If I want to make sure that BuddyPress is loaded before my plugin, but only if BuddyPress is activated, how can I do that? Unless I'm missing something, currently the only way to be sure of this is to name my plugin file alphabetically after 'b' so it is loaded after.

If the "Requires:" line is not an ideal solution, It would be nice to at least be able to do something simple like this:

<?php if ( is_plugin_active( PLUGIN ) ) { require_once( PLUGIN ); } ?>

comment:4 apeatling4 years ago

So it's possible to do this at the top of every BuddyPress plugin:

/* Make sure BuddyPress is loaded */
require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
if ( is_plugin_active( 'buddypress/bp-loader.php' ) )
	require_once ( WP_PLUGIN_DIR . '/buddypress/bp-loader.php' );
else
	// Display an error message

But this is going to be slow. If I have twenty BP plugin active, then that's twenty calls to require_once() which is not ideal.

There must be a better way to handle this?

comment:5 apeatling4 years ago

Perhaps a possible alternate approach would be to provide a load priority option for plugins, in the same way that you can define a priority on add_action and add_filter calls?

That way I could set BuddyPress as a higher priority than the default, which would ensure that it is loaded before any dependent plugins.

comment:6 westi4 years ago

I'm quite strongly against the idea of plugin dependencies.

I would much prefer for plugins to be written using hooks and actions in the same way that WordPress is.

Progressive enhancement if another plugin is present is a much cleaner user experience and displaying a nice message which says hey this plugin won't work completely without this other plugin is the best solution.

comment:7 follow-up: markjaquith4 years ago

Plugin:

function my_plugin_prefix_init() {
    do_action( 'my_plugin_prefix_init' );
}
add_action( 'plugins_loaded', 'my_plugin_prefix_init' );

Dependent plugin:

function dependent_prefix_init() {
    // add all bootstrap stuff here
    // no code should run "live"
}
add_action( 'my_plugin_prefix_init', 'dependent_prefix_init' );

comment:8 nacin4 years ago

  • Milestone 3.0 deleted
  • Resolution set to worksforme
  • Status changed from new to closed

comment:9 scribu4 years ago

Duplicate (with proposed patch): #13296

comment:10 in reply to: ↑ 7 scribu4 years ago

Replying to markjaquith:

I was about to claim that you can't use register_activation_hook() with that approach. Then I figured this should work:

Dependent plugin:

function dependent_plugin_activation() {
    if ( !did_action('my_plugin_prefix_init') )
        return;

    // activation code
}
register_activation_hook(__FILE__, 'dependent_plugin_activation');

comment:11 azaozz4 years ago

I'm with @westi and @markjaquith on this. Our hooks API is flexible enough to handle this case perfectly. The patch in #13296 just adds a lot of code without offering significant improvements.

Note that a priority can be set in the main plugin to run the custom action code first on "plugins_loaded" avoiding any possible collisions with the dependent plugins using the same hook:

function my_plugin_prefix_init() {
    do_action( 'my_plugin_prefix_init' );
}
add_action( 'plugins_loaded', 'my_plugin_prefix_init', -1 );

comment:12 johnjamesjacoby4 years ago

As it sits today, we have plugin authors add this little bit in their code:

if ( defined( 'BP_VERSION' ) )
	your_plugin_prefix_init();
else
	add_action( 'bp_init', 'your_plugin_prefix_init' );

...and then wrapping pretty much the entire plugin inside that your_plugin_prefix_init() call.

There's two problems:
1.) Not every plugin author does this.
2.) If a plugin called "AAA BuddyPress" loads before BuddyPress does, without the above code, it expects for BP functions to be available and white screens the entire install.

I get that this is a developer education issue, but this is also something that other plugins could eventually use not unlike how jQuery plugins need to make sure jQuery is loaded first.

It would be nice if there was some core WP way for plugins to register themselves as ready and available in a reliable way. I went the css/script route because in many ways plugins are extensions of the WP core much like CSS and JS are extensions to HTML markup.

The benefit of plugin dependencies would allow for plugins to "light up" with special features when other plugins are installed.

At the very least, it would be nice if there was a dedicated WP function to check if a plugin is active and completely loaded without error, without doing file_exists checks and site_option checks.

Maybe plugins could "register" themselves with WP into an array at the end of their load cycle? It would be an optional function for plugins that want to tell WP "hey, I'm here, I'm ready, and I'm friendly!" so that other plugins can call:

if ( !is_plugin_loaded( 'plugin_name' ) ) return false;

comment:13 nacin4 years ago

Plugins should already be running nearly all of their code off hooks anyway.

It *is* a plugin developer education issue, which is why changing the process isn't going to help. All we're doing is introducing a whole new dependency network. We can't get many plugin developers to use the enqueue script API, or even recognize that we package our own jQuery, so I fail to see why introducing a new API when the existing system does the job in any way helps with the education component.

On plugin activation, a BuddyPress plugin can check to see if BuddyPress is installed. (A simple did_action will work.)

Additionally, on plugins_loaded or init (all plugins should be waiting until then anyway, so I fail to see why they cannot be told to certainly wait until then if they need the BP API), a BuddyPress plugin can check to see if BuddyPress isn't installed, and noop or deactivate. Otherwise, they can attach all of their hooks and run their initialization code at that point.

A plugin could likewise wait until init to see if other plugins are installed (I find did_action to be both effective and lightweight for this), and then progressively enhance.

I think that covers all the bases. A flexible base framework can be written that BP developers can rely on and start with.

comment:14 follow-up: shidouhikari4 years ago

  • Cc shidouhikari added

Interesting, I liked all presented ideas!

My contribution, is the need of framework plugins. Drupal has a bunch of them, like CCK and Tokens. A framework plugins doesn't have end-user code, it "only" has code that remains available and will be used by other plugins.

I don't remember if Drupal's module dependance system checks version, but it lets a module sets itself as depending on another module. If the parent module isn't activated, the dependant one is listed, but it's activation link isn't provided, first the parent module must be activated. And when both are activated, the parent one can't be deactivated, first all its dependants must be. For each module, it also lists their dependants and dependances, so that users can see what may be blocking an action.

I like Wordpress' dependance feature, that's used for scripts in exemple. Plugins are loaded in the order present in 'active_plugins' option, it's an array and when a plugin is activated it goes to the end of the list... plugins aren't loaded in alphabetical order.

The problem for framework plugins, is that both ones MAY be activated, but dependant one may be after parent one in the array. If the dependant plugin extends a framework class, and when it's loaded that class isn't loaded yet, it may break. Action hooks is a good idea, but I think that having a core feature to control loading order, in the same way scripts inclusion is done, would be nice.

The problem now is that a plugin reference another plugin is indeed tricky. In the active_plugins's array, they are referenced by plugin_folder/plugin_file.php. That's good because no other plugin can have the same folder/filename at the same time, but it's also bad because this "ID" isn't static. Plugins folders may be renamed by users, and the "ID" isn't set by (and thefore controlled) plugin author, so dependant plugins authors also can't control them.

In the end, MHO is that filoso's suggestion is very nice, let plugins have more control over plugins list UI and set colors and texts saying like "I'm activated, but I don't work", "I'm activated, but only partial features are available", and also add texts that let them say their dependances, and on their parent plugins list themselves and dependants. This way we could make it all clear, and with a standard interface.

And then, maybe build a new code to let plugins load. For exemple, when plugins loading loop is running, create a temp array with all plugins filename, and when they are loaded, before 'plugins_loaded' action is run, run dynamic actions based on plugins filenames (for exemple "$plugin-loaded").

Yes, I know a plugin can add an action itself, but if core does it by default, any plugin could use the feature without relying on its parent cooperating with it.

In the end, we can right now develop plugins that depend on other plugins, but what we're talking here is define a standard for this situation. Imagine 10 plugins depending on BuddyPress, each of them handling the depandance by themselves using different methods... It'd be a mess. If we want Community Plugins to prosperate, we must standardize plugins dependances.

Imagine if BuddyPress is deactivated and then we have 10 orphaned plugins, all in the same situation, but 6 of them add text on top of page, 3 of these 6 add the text to ALL admin pages, 1 of them add a message on frontend footer, 2 other plugins change their color (using 2 different colors) in their row in plugins list, and 1 of them simply deactivate itself.

Personally, I think dependant plugins don't need to be deactivated when parent one isn't available. Leave them activated waiting for parent to come back, just disable their features and make it clear to site admin, that they're activated but not running, and that's happening because a parent plugin isn't available.

comment:15 in reply to: ↑ 14 scribu4 years ago

With WP 3.0, you can instruct the users to put the framework plugin in the MU folder. It will be loaded before any other regular plugin.

As there aren't that many framework plugins out there, I think it's premature to consider adding further admin UI support for them.

comment:16 shidouhikari4 years ago

Yeah I'll do that when I release my framework, but still users won't do it, and built-in install also doesn't support it.

And like I said, core must support it *before* it's used, or frameworks won't be popular at all. WP community is used to see plugins as complete solution packages, since there's no native support and no standard for plugins dependances, authors simply aren't encouraged to do so.

As I said, it'd be great to have Community Plugins providing frameworks that other plugins can enhance, as jquery does for exemple. Core plugin adds features and a basic UI, then another plugin adds AJAX UI, and other one enhances 2 or 3 features.

For now, each dependant plugin could add an action to plugins_loaded -10, and in this action test if framework is available, and then include its files, or add another action reporting to user that the framework isn't available and the consequences. Then, when plugins_loaded 10 comes up they will all have finished loading. And in init they start interacting and hooking.

comment:17 mikeschinkel4 years ago

  • Cc mikeschinkel@… added

comment:18 scribu3 years ago

I've just released the first version of a meta-plugin for handling plugin dependencies:

http://scribu.net/wordpress/plugin-dependencies/

comment:19 johnjamesjacoby3 years ago

Just got back from gsoc summit but planning on looking at this tomorrow.

comment:20 Philipp15b3 years ago

  • Cc hebipp1@… added

comment:21 ZaneMatthew2 years ago

  • Cc zanematthew@… added

comment:22 boonebgorges2 years ago

  • Cc boonebgorges@… added

comment:23 stephenh19882 years ago

  • Cc stephen@… added

comment:24 lkraav2 years ago

  • Cc lkraav added

comment:25 lkraav2 years ago

scribu, your plugin doesn't handle the actual load order does it? if i need to do

class MyClass extends SomeUpstreamClass

but my plugin is alphabetically in front of SomeUpstreamPlugin, then SomeUpstreamClass won't exist.

Something called Plugin Organizer is out there, but it seems too bloated for this simple task.

So it seems comment:7 is the best approach today, albeit requiring cooperation from SomeUpstreamPlugin?

Version 0, edited 2 years ago by lkraav (next)

comment:26 scribu2 years ago

The Plugin Dependencies plugin doesn't alter the load order, so yes, you still need to use hooks.

The 'plugins_loaded' action should handle 90% of the cases and doesn't require cooperation from the parent plugin.

comment:27 spacedmonkey4 weeks ago

I have come up against this issue a lot while developing in WordPress. Plugins that load in libraries, such as meta box generator. I have also had issues with buddypress plugins, although this issue is getting better. Could the plugin load order be fixed by implementeing a similar system to child themes, making child plugins. This would allow you to create plugin that is linked to another. You could also make it not active if the parent plugin is not present or deactivated.

Note: See TracTickets for help on using tickets.