[wp-trac] [WordPress Trac] #22802: Empower Plugin Developers to make Symlink Compatible Plugins

WordPress Trac noreply at wordpress.org
Fri Dec 7 05:43:27 UTC 2012

#22802: Empower Plugin Developers to make Symlink Compatible Plugins
 Reporter:  MikeSchinkel                        |       Type:  defect (bug)
   Status:  new                                 |   Priority:  normal
Milestone:  Awaiting Review                     |  Component:  Plugins
  Version:  3.4.2                               |   Severity:  normal
 Keywords:  has-patch dev-feedback 2nd-opinion  |
 Currently it's effectively impossible to use symlinked plugins
 '''''reliably''''' in a WordPress installation. For those not familiar
 with the problems with symlinks, let's assume we have a structure like the
 following where two sites are on the same server, and then there is a
 plugins directory that is on peer with the site directories:

 /home/myacct/mysite1/  <-- Site #1 goes here
 /home/myacct/mysite2/  <-- Site #2 goes here
 /home/myacct/plugins/  <-- All the shared plugins go here
 Now let's assume one of those plugins use `plugins_url( 'js/myajax.js',
 __FILE__ )` to get a URL for this external file.  But the problem is that
 PHP resolves symlinks before returning the value of `__FILE__` but it does
 not provide any way to translate the value from __FILE__ back into the
 symlinked virtual directory. So when you need a URL that looks like this:
 You instead get this URL because of the symlink:

 This problem has been discussed on tickets #16953 and #13550 and in both
 cases the discussion apparently stalled because there wasn't a good viable
 way to automatically support plugins that have been symlinked into the
 expected directories.

 But what CAN be done is for the plugin developer to be proactive and
 handle write their plugin to be symlinkable.
 [http://alexking.org/blog/2011/12/15/wordpress-plugins-and-symlinks Alex
 King proposed a solution] as
 directories/15204#15204 did Jan Fabry] which captures the file path value
 from the global variables `$plugin`, `$mu_plugin` or `$network_plugin`,
 whichever is applicable.

 Unfortunately that works in many cases, but not all. Specifically those
 fail during plugin activation and plugin deactivation as you can see in
 [https://gist.github.com/4230921 the code I've extracted from a working
 class file]. This class file shows how frought with peril it is to try to
 make this work in all applicable use-cases ''(For those interested
 base.php#L148 here is the real file] that uses this code from its GitHub

 Forunately there is a very simple modification to WordPress core that will
 empower plugin developers to make their plugins symlinkable by simply
 replacing `__FILE__` with `$GLOBALS['wp_plugin_file']`; easy peasy. Rather
 than WordPress core call `include_once()` every place where it needs to
 load a plugin WordPress core could call `wp_load_plugin()` instead. Here's
 how simple `wp_load_plugin()` can be:

 function wp_load_plugin( $plugin_file ) {
   global $wp_plugin_file;
   include_once( $wp_plugin_file = $plugin_file );
   unset( $wp_plugin_file );

 Simple, bulletproof, and here's [https://gist.github.com/4230972 what
 plugin code could look like] with this change (compared to
 [https://gist.github.com/4230921 this].)

 Note I decided to use a ''(disappearing)'' global variable because that
 would be the most performant method of capturing the plugin file value vs.
 assigning to a static object properties or similar, and because I think it
 will be the easiest syntax for people to use, i.e.:

 global wp_plugin_file;
 $url = plugins_url( 'js/myajax.js', $wp_plugin_file );
 And being ''"disappearing"'' the code releases `$wp_plugin_file` from
 memory immediately after loading the plugin so chance of future conflict
 with anything else in WordPress is extremely unlikely.

 I've attached a patch with includes this `wp_load_plugin()` function. The
 patch also calls `wp_load_plugin()` in all the places I've identified that
 load plugins. The version in the patch is actually a bit more complicated;
 it has logic to calculate and assign a `$wp_plugin_slug` too, because the
 plugins slug is needed at times and it would be really convenient if it
 could be made available during plugin load too ''(but of course adding
 `$wp_plugin_slug` is not nearly as important as adding `$wp_plugin_file`):

 function wp_load_plugin( $plugin_file, $plugin_type = 'plugin' ) {
   global $wp_plugin_file, $wp_plugin_type, $wp_plugin_slug;
   $wp_plugin_type = $plugin_type;
   $dir = preg_quote( 'plugin' == $plugin_type ? WP_PLUGIN_DIR :
   $wp_plugin_slug = preg_replace( "#^{$dir}/(.+)$#", '$1', str_replace(
 '\\', '/', $plugin_file ) );
   include_once( $wp_plugin_file = $plugin_file );
   unset( $wp_plugin_file, $wp_plugin_type, $wp_plugin_slug );

 In summary this addresses symlinking by enabling a new best practice that,
 when following, would empower plugin developers to build symlinkable
 plugins without going to extremely fragile lengths and that would work
 reliably in all cases.

 BTW, if you've never had this problem try using some of your own plugins
 in a symlinked directory first before jumping to conclusions about what's
 required; it wasn't obvious to me how difficult this problem is to address
 until after I needed to.

Ticket URL: <http://core.trac.wordpress.org/ticket/22802>
WordPress Trac <http://core.trac.wordpress.org/>
WordPress blogging software

More information about the wp-trac mailing list