WordPress.org

Make WordPress Core

Opened 4 years ago

Closed 4 years ago

Last modified 16 months ago

#14912 closed enhancement (wontfix)

Introduce register_update_hook()

Reported by: scribu Owned by:
Milestone: Priority: normal
Severity: normal Version:
Component: Upgrade/Install Keywords: has-patch
Focuses: Cc:

Description

Currently, plugin authors overload the activation hook with routines for upgrading from older versions.

We should have a dedicated hook for when plugins are upgraded.

The activation hook should only be fired when the plugin is activated by the user.

Attachments (4)

plugin-versions.diff (1.6 KB) - added by ericmann 4 years ago.
Moves all update handling to register_update_hook. Extends for multisite.
plugin-versions.2.diff (1.7 KB) - added by scribu 4 years ago.
Use is_plugin_active_for_network()
plugin-versions.3.diff (1.6 KB) - added by ericmann 4 years ago.
Uses is_multisite() so the hook works for themes.
plugin-versions.4.diff (1.5 KB) - added by scribu 4 years ago.
Only fire if is_admin(); updated docblock

Download all attachments as: .zip

Change History (44)

comment:2 nacin4 years ago

As said over wp-hackers by someone else: I don't think it is a good idea to assume that upgrades to plugins/themes are made through the WP admin interface.

Take, for example, SVN. Introducing a new hook here is only going to cause inconsistencies. At least activation and deactivation we can largely control. (Deactivation less so, as the files can vanish.) Schema upgrades are probably best handled on a case by case basis.

comment:3 ericmann4 years ago

WordPress could automatically verify plug-in versions on init. We already store a list of active plug-ins in the DB ... it would be fairly trivial to pass an expected plug-in version with the upgrade hook and check it against whatever version was "active" on the site. If the two version numbers are different, we can assume an update has happened (either though the UI, via SVN, or via FTP) and call the appropriate function.

comment:4 ericmann4 years ago

  • Cc eric@… added

comment:5 scribu4 years ago

Eric, that's a great idea. It combines the best-practice suggested on wp-hackers with a consistent approach.

comment:6 mikeschinkel4 years ago

  • Cc mikeschinkel@… added

comment:7 Denis-de-Bernardy4 years ago

+1 to nacin's comment. An upgrade hook is not going to work for users who upgrade through SVN or FTP.

Eric's idea is impractical: fetching the version of plugins on all page loads involves parsing their headers. That remained quite slow last I checked.

comment:8 nacin4 years ago

Suggesting wontfix here, and address the root problem: #14915

comment:9 follow-up: scribu4 years ago

Eric's idea is impractical: fetching the version of plugins on all page loads involves parsing their headers. That remained quite slow last I checked.

Not at all. Plugins that would call register_upgrade_hook() would have their current version stored in the DB, and then, only on each admin page load check that option, which would get autoloaded anyway.

It would just be a standardization of what plugin authors would have to do anyway.

comment:10 scribu4 years ago

#14915 is not the root problem.

comment:11 ericmann4 years ago

This patch does a couple of things. Firstly, it adds an option called active_plugin_versions that maps plugin file names to their current version. The key/value pair is added to the database when the plugin is first activated.

WordPress will check the plugin's version against its stored database value whenever register_update_hook is called. It's getting the expected (current) version number from the function call itself and the stored version number from get_option ... no need to re-parse plugin headers. If the expected version is higher than the value stored in the database, WordPress assumes an update and fires the update-plugin_{pluginname} action.

The function register_update_hook accepts three parameters - the plugin filename, a callback function, and the plugin version the system is expecting - this version should be the same as the current version of the plugin and is explicitly set by the plugin.

I've done some preliminary testing by tweaking the version number of Hello Dolly to see how things work. It seems to be working just fine so far.

Unfortunately, it only works for single sites at the moment ... I'm not up to speed with MS development yet, so I didn't even touch the active_sitewide_plugins settings, though I'm open to advice and guidance here.

comment:12 follow-up: scribu4 years ago

The function register_update_hook accepts three parameters - the plugin filename, a callback function, and the plugin version the system is expecting - this version should be the same as the current version of the plugin and is explicitly set by the plugin.

Is the third parameter really necessary?

comment:13 in reply to: ↑ 9 mikeschinkel4 years ago

Replying to scribu:

Not at all. Plugins that would call register_upgrade_hook() would have their current version stored in the DB, and then, only on each admin page load check that option, which would get autoloaded anyway.

Rather than just store versions, why not store more plugin metadata?

See: http://wordpress.stackexchange.com/questions/361/

comment:14 in reply to: ↑ 12 ericmann4 years ago

Replying to scribu:

Is the third parameter really necessary?

It's not a deal-breaker ... it was a preemptive compromise based on Denis' statement about the speed of parsing plugin header data. If we want, we could just scrape the plugin meta instead rather than passing the version number.

comment:15 follow-up: scribu4 years ago

I see. It's probably better to specify an explicit version anyway, since not every update will have a schema change.

More observations on the patch:

  • the callback should receive the previous version as a parameter
  • there's no point in using add_action() / do_action() in the same function

comment:16 in reply to: ↑ 15 ericmann4 years ago

Replying to scribu:

More observations on the patch:

  • the callback should receive the previous version as a parameter
  • there's no point in using add_action() / do_action() in the same function

Great suggestions; I've incorporated them both. Though I'm still wondering how best to apply this patch to multisite/network-wide plugins. I want to keep things as consistent as possible, but without direct experience in site-wide options, I'm not quite sure where to begin ...

comment:17 scribu4 years ago

For network plugins, you can use add_site_option() etc. to store a separate array of version and then you would have to run the upgrade procedure for each site. Similar to #14170

A more pressing problem is that in your current patch, you tie the version update to the activation procedure, so we're back where we started. Upgrades shouldn't be tied to activation at all.

The way I see it, the active version should be added and updated from register_update_hook().

ericmann4 years ago

Moves all update handling to register_update_hook. Extends for multisite.

comment:18 ericmann4 years ago

Replying to scribu:

Upgrades shouldn't be tied to activation at all.

The way I see it, the active version should be added and updated from register_update_hook().

I agree ... and I've changed the newest patch to do just that. register_update_hook will do all the work, checking the plugin's current version against its stored value, running the callback function if necessary, and updating the stored plugin version afterwards. I've also changed it so we're passing both the current version and the stored version to the callback function. I can think of a few times where that would have helped a few of my clients when updating plugins across multiple versions.

I also took a stab at extending this for multisite. If we're working with a multisite installation, I use get_site_option and update_site_option instead of their standard single-installation counterparts. This makes sense to me, so correct me if the implementation seems to be in error.

scribu4 years ago

Use is_plugin_active_for_network()

comment:19 scribu4 years ago

plugin-versions.2.diff:

I replaced the is_multisite() check with is_plugin_active_for_network() as that's what we're interested in.

I think passing only the stored version to the callback is enough, since it will already know what the latest version is.

I'm also passing wether the current plugin is network activated, so that the callback can handle that on it's own.

ericmann4 years ago

Uses is_multisite() so the hook works for themes.

comment:20 ericmann4 years ago

I like the changes. Had I realized is_multisite() was used in is_plugin_active_for_network() I might have done it this way. Though the is_plugin_active checks seem a bit redundant to me. The code in the plugin calling register_update_hook will only get called if the plugin is active, correct?

Does keying on is_plugin_active mean we can't use this hook for themes as well? More theme developers are wrapping custom plugin functionality into their functions.php files, so I could see several situations where a theme author would want to use this hook as well. If I'm right that the code will only be executed if the plugin is active, could we assume is_plugin_active will return true and replace that check with is_multisite() instead? (ala plugin-versions.3.diff)

comment:21 scribu4 years ago

It's not the same thing. is_plugin_active_for_network() checks if a plugin is network-activated.

Themes can't be network activated and plugin_basename() probably doesn't work either.

So, I think we should make a separate function for themes (in a separate ticket).

comment:22 ericmann4 years ago

Good to know ... let's go with plugin-versions.2.diff then. Can we remove the other two patches to eliminate confusion?

comment:23 follow-ups: scribu4 years ago

Uploaded files can't be removed. Anyway, in plugin-versions.4.diff I've added an is_admin() at the beginning, to keep the front-end light and free of potential weirdness.

comment:24 in reply to: ↑ 23 ericmann4 years ago

  • Keywords has-patch added; needs-patch removed

Replying to scribu:

keep the front-end light and free of potential weirdness.

+1 To that.

scribu4 years ago

Only fire if is_admin(); updated docblock

comment:25 in reply to: ↑ 23 ; follow-up: batmoo4 years ago

  • Cc batmoo@… added

This is awesome guys!

Replying to scribu:

I've added an is_admin() at the beginning, to keep the front-end light and free of potential weirdness.

The one potential problem with only firing the update hook on is_admin is that the front-end is going to have updated plugin code which likely expects updated data, and that's more likely to cause weirdness.

For example, if I go from 0.1 to 0.2 and my update hook changes the name of an option from 'my_option' to 'myplugin_my_option', my code is going to reference the option as 'myplugin_my_option'. If a user visits my site before the update hook fires, my plugin is probably not going to work. But then again, this might just be a coding practice issue.

And from the looks of it, the current patch fires update_option/update_site_option on every page load for all update hooks registered. It might be better be to fire those only if the version is different.

comment:26 Denis-de-Bernardy4 years ago

Expanding on that last remark, there should probably be two procedures. One that tells the plugin to run the upgrade code on all page loads, regardless of where we are on the site. The other that tells the plugin to make these changes persistent, which runs in the admin area only, preferably only when the admin is logged in, and which updates the stored plugin version. This, to avoid hammering the DB on large sites.

The two functions need to be well documented, too, so they're not misused. Most plugin devs don't realize that non-atomic upgrade code can wreck their data when it's run concurrently on multiple page loads.

comment:27 in reply to: ↑ 25 ; follow-up: scribu4 years ago

Replying to batmoo:

Replying to scribu:

I've added an is_admin() at the beginning, to keep the front-end light and free of potential weirdness.

The one potential problem with only firing the update hook on is_admin is that the front-end is going to have updated plugin code which likely expects updated data, and that's more likely to cause weirdness.

Considering that 99.9% of plugin updates will be done through the admin, I don't think this is a problem.

And from the looks of it, the current patch fires update_option/update_site_option on every page load for all update hooks registered. It might be better be to fire those only if the version is different.

update_option() and update_site_option() do an UPDATE only if the new value is different from the old value, so it's the same thing.

comment:28 azizur4 years ago

  • Cc azizur added

comment:29 scribu4 years ago

  • Summary changed from Introduce register_upgrade_hook() to Introduce register_update_hook()

comment:30 solarissmoke4 years ago

  • Cc solaris.smoke@… added

comment:31 in reply to: ↑ 27 ; follow-up: StephenCronin4 years ago

  • Cc StephenCronin added

Replying to scribu:

Considering that 99.9% of plugin updates will be done through the admin, I don't think this is a problem.

Isn't that one of the arguments for this in the first place? That the existing activation hook shouldn't be used, because it only works when people upgrade plugins through admin. Ie it won't work when plugins are update via FTP, etc

Now, we're replacing that with something that won't work in the same cases (ie when a plugin is updated via FTP)?

Am I missing something here?

comment:32 in reply to: ↑ 31 ; follow-up: nacin4 years ago

Replying to StephenCronin:

Now, we're replacing that with something that won't work in the same cases (ie when a plugin is updated via FTP)?

It will work when a plugin is updated through FTP. It just won't fire until the admin area is accessed. I consider that an extreme edge case at that point. And FWIW, WordPress doesn't run the database upgrade for a new version until the admin area is accessed, either, so it's consistent with that.

comment:33 GamajoTech4 years ago

  • Cc gary@… added

comment:34 in reply to: ↑ 32 StephenCronin4 years ago

Replying to nacin:

It will work when a plugin is updated through FTP. It just won't fire until the admin area is accessed. I consider that an extreme edge case at that point. And FWIW, WordPress doesn't run the database upgrade for a new version until the admin area is accessed, either, so it's consistent with that.

hmm can't argue with it being consistent with the core db upgrade, and for most plugin updates it won't be an issue, but there will occasionally be plugin updates where something needs to be updated immediately. In those cases, the plugin may behave inconsistently until the user accesses the admin area (typically in my case that could be up to a week!).

It seems that the only bulletproof solution is for the plugins to handle this themselves.

If that's the case, perhaps we should scrap this new function and just educate plugin authors about handling it themselves.

comment:35 scribu4 years ago

hmm can't argue with it being consistent with the core db upgrade, and for most plugin updates it won't be an issue, but there will occasionally be plugin updates where something needs to be updated immediately. In those cases, the plugin may behave inconsistently until the user accesses the admin area (typically in my case that could be up to a week!).

No matter how the plugin does it, it won't be able to sense how often you login to the admin. So you would have to modify it either way.

We can educate plugin authors that the update hook will only run in the admin and they can decide for themselves if they should use register_update_hook() or a custom solution.

comment:36 scribu4 years ago

  • Milestone changed from Future Release to 3.1

I'm going to commit this for 3.1 and leave the automated network activation handling for a future release.

comment:37 Viper007Bond4 years ago

I still think this should be wontfix. Let plugin authors take care of this themselves.

comment:38 westi4 years ago

  • Milestone changed from 3.1 to Future Release

1 We are past the freeze for 3.1 and shouldn't be adding any more new features.
2 This is more code than a plugin needs to implement the solution itself and will run more often than it would need to in a plugin - yes you have an is_admin() check but plugins doing this themselves would be able to hook into an admin focused action hook thereby ensuring it was an admin page load.
3 If plugins behave inconsistently until the upgrade code has run then they are poorly coded :-)

I'm in the WONTFIX camp but this is definetly too late for 3.1

comment:39 scribu4 years ago

  • Milestone Future Release deleted
  • Resolution set to wontfix
  • Status changed from new to closed

Oh well, I guess this is what plugin frameworks are for :)

comment:40 desrosj16 months ago

  • Cc desrosj@… added
Note: See TracTickets for help on using tickets.