Opened 2 years ago
Last modified 3 days ago
#16953 assigned enhancement
Allow symlinked plugins
| Reported by: |
|
Owned by: | |
|---|---|---|---|
| Priority: | normal | Milestone: | Future Release |
| Component: | Plugins | Version: | |
| Severity: | normal | Keywords: | has-patch dev-feedback |
| Cc: | yincrash, paulschreiber@…, boonebgorges@…, djpaul@…, coenjacobs@…, lkraav, simon@…, junk@…, juzzin, mitcho@…, kenn@…, hertzog@…, kwisatz, info@…, kawauso, synapticism, markus.magnuson@…, cklosowski@…, mike@…, imtiedup@…, johnbeales@…, beaver6813, jason@…, david@…, cor@…, JeromeC, francesco.laffi@…, rodrigosprimo@… |
Description (last modified by scribu)
There are many scenarios where one would like to have a plugin's folder symlinked to another location.
A couple of these scenarios are described in #13550.
However, when using symlinks, code such as this fails:
plugins_url( 'script.js', __FILE__ );
This happens because __FILE__ resolves to the real path, which confuses plugin_basename().
The most simple and most flexible solution is to add a filter to plugin_basename(), leaving individual devs to handle symlinked paths, depending on their environment.
Attachments (8)
Change History (68)
- Keywords has-patch added
- Owner set to scribu
- Status changed from new to accepted
Test files coming up.
- Description modified (diff)
- Keywords commit added
- Milestone changed from Awaiting Review to 3.2
To test:
The archive already contains a symlink, so it probably won't work on Windows out of the box.
- Milestone changed from 3.2 to Future Release
Not a 3.2 feature moving to Future Release.
- Milestone Future Release deleted
- Resolution set to worksforme
- Status changed from accepted to closed
Well what'ya know... there's already a 'plugins_url' filter which can be used just as well.
- Milestone set to Future Release
- Resolution worksforme deleted
- Status changed from closed to reopened
Found a case that's not covered by the 'plugins_url' filter: register_*_hook().
Yes, very annoying, it boils down to lack of feature in PHP, see PHP bug 46260 (I actually believe best way to fix PHP bug is to create new constant like __FILELINK__ to keep backwards compatibility). But getting that fixed anytime soon is not reality.
@scribu, can you elaborate what kind of problems you encountered using plugins_url filter hack for symlinkked plugins? The problem on those register_*_hook() seems to be in plugin_basename().
Alternative for plugins_url filter that converts plugin realpath to WP plugin path is naturally to make all symlinked plugins to create their URL differently, e.g. using plugin WPMU_PLUGIN_URL and WP_PLUGIN_URL.
comment:10
scribu — 2 years ago
Well, anything that uses plugin_basename(__FILE__) won't work properly and can't be fixed by hooking into 'plugins_url'.
Changing each plugin to pass to plugin_basename() what it would expect is not a good solution.
Hence the proposed filter, which would allow to handle this from a single point.
comment:11
scribu — 2 years ago
- Keywords commit removed
An alternative solution would be to allow plugins from multiple directories, like we do for themes.
Fix for correctly extracting the local directory and filename regardless if the file comes from a symlink or not
comment:12
augustash — 22 months ago
- Resolution set to worksforme
- Status changed from reopened to closed
the above patch has been tested in our local and production environments. The patch is really simple as all that's needed is to use the basename() and dirname() functions to extract the local directory and filename from the passed in file.
dirname doc = http://www.php.net/manual/en/function.dirname.php
basename doc = http://www.php.net/manual/en/function.basename.php
or use pathinfo to get all the information = http://www.php.net/manual/en/function.pathinfo.php
comment:13
scribu — 22 months ago
- Resolution worksforme deleted
- Status changed from closed to reopened
The ticket will be closed automatically when a fix is commited.
What's up with WP_fix_for_plugin_basename_to_allow_symlinks_2011-07-28.2.patch?
comment:14
follow-up:
↓ 16
yincrash — 21 months ago
this patch is incorrect. it only works two levels deep. my issue is that that a lot of the jetpack modules are acting screwy when symlinked, and their JS and CSS files are 3/4 levels deep in the plugins folder.
comment:15
yincrash — 21 months ago
- Cc yincrash added
comment:16
in reply to:
↑ 14
augustash — 20 months ago
To clarify, the WP_fix_for_plugin_basename_to_allow_symlinks_2011-07-28.2.patch is a patch to the WP_fix_for_plugin_basename_to_allow_symlinks_2011-07-28.patch so apply the WP_fix_for_plugin_basename_to_allow_symlinks_2011-07-28.patch first then the WP_fix_for_plugin_basename_to_allow_symlinks_2011-07-28.2.patch.
Replying to yincrash:
this patch is incorrect. it only works two levels deep. my issue is that that a lot of the jetpack modules are acting screwy when symlinked, and their JS and CSS files are 3/4 levels deep in the plugins folder.
This patch is not incorrect. You're welcome to contribute to it if you have found scenarios that have nested paths, but I'll bet those scenarios are easily solved by using relative paths to the directory determined by this plugin basename patch.
comment:17
paulschreiber — 17 months ago
- Cc paulschreiber@… added
I keep getting bit by this and end up patching individual plugins (Raw HTML Pro, Advanced Custom Fields, More Fields, Post Page Association, etc.) They end up generating bad paths to CSS and JS — the <link> and <script> tags have the filesystem path embedded in them.
It would be nice if WordPress' plugins_url() did the right thing out of the box.
There are other complaints about symlinked plugins:
I urged you to seriously consider fixing this for 3.3.x or 3.4.
comment:18
boonebgorges — 14 months ago
- Cc boonebgorges@… added
comment:19
DJPaul — 14 months ago
- Cc djpaul@… added
comment:20
CoenJacobs — 13 months ago
- Cc coenjacobs@… added
comment:21
lkraav — 13 months ago
- Cc lkraav added
comment:22
gandhiano — 13 months ago
Had the same issue - symlinks are defintely important if one is managing a complex WP farm.
Applying the patches did solve the issue, so I propose that these are included upstream.
comment:23
scribu — 11 months ago
- Owner scribu deleted
- Status changed from reopened to assigned
comment:24
simonwheatley — 10 months ago
- Cc simon@… added
comment:25
leewillis77 — 10 months ago
- Cc junk@… added
comment:26
juzzin — 10 months ago
- Cc juzzin added
mitchoyoshitaka — 10 months ago
Alternative approach, which caches symlink targets and replaces them
- Cc mitcho@… added
Patching up plugin_basename() (or allowing for that patching) isn't the only issue here; symlinked files will show up in the plugins listing in wp-admin, but symlinked directories are not. I just attached a patch which also touches get_plugins() to support this.
My fix to plugin_basename() is also a little more robust, but perhaps more overhead than wanted. I hope this patch is helpful for others.
In the mean time, if this functionality should come as a plugin, scribu's pre_plugin_basename isn't the only hook needed; a filter in get_plugins() will also be necessary. If these filters are added, I'd be happy to release a "Symlink Plugins Support" plugin.
comment:28
kchrist — 9 months ago
- Cc kenn@… added
comment:29
rhertzog — 8 months ago
- Cc hertzog@… added
FTR, this feature is also needed for the setup picked by the official Debian package where we wanted to have packaged plugins in and manually installed plugins kept in two different directories (which is not really possible, so we put symlink to packaged plugins in the directory which is controlled by the local administrator). It would thus be nice to see this issue fixed.
See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=686228 for a report of this bug on the Debian side.
comment:30
kwisatz — 7 months ago
- Cc kwisatz added
Weirdly enough, while augustash's patch works for me (debian wp 3.4.2 package), mitchoyoshitaka's (included in there) does not.
comment:31
kchrist — 7 months ago
This is also an issue for WordPress sites deployed via Capistrano.
Capistrano keeps previous deployments of the site so you can easily roll back changes. So the actual filesystem path to the site might be:
/home/username/web/example.com/releases/20121018101521/public
But it's symlinked as:
/home/username/web/example.com/current/public
The latter is the path in my web server config. The current directory is a symlink to the latest deployment, which is actually the datestamped releases/20121018101521 directory.
As a result, the entire site is in a symlinked directory. This isn't an issue for most plugins but it's come up more than once in my experience running a handful of sites this way.
comment:32
follow-up:
↓ 34
scribu — 5 months ago
#22802 was marked as a duplicate.
comment:33
toscho — 5 months ago
- Cc info@… added
comment:34
in reply to:
↑ 32
MikeSchinkel — 5 months ago
comment:35
scribu — 5 months ago
Since #22802 was reopened anyway, let's keep this ticket focused on a solution that would enable symlinked plugins without plugin authors having to do anything special.
comment:36
kawauso — 4 months ago
- Cc kawauso added
comment:37
synapticism — 4 months ago
- Cc synapticism added
- Cc markus.magnuson@… added
comment:39
follow-up:
↓ 57
markus.magnuson — 4 months ago
I resolved all my plugin problems caused by symlinks in the wp-content path by using the realpath() function in my wp-config.php. I simply replace this:
define('WP_CONTENT_DIR',$_SERVER['DOCUMENT_ROOT'] . '/wp-content');
With this:
define('WP_CONTENT_DIR', realpath($_SERVER['DOCUMENT_ROOT'] . '/wp-content'));
Thought that might help anyone ending up in this ticket when troubleshooting.
comment:40
cklosows — 4 months ago
- Cc cklosowski@… added
comment:41
MikeSchinkel — 3 months ago
- Cc mike@… added
- Keywords dev-feedback added
- Version set to trunk
I've uploaded a path with another approach. The patch hooks 'pre_update_option_active_plugins' so anytime 'active_plugins' is saved it finds which active plugins have been symlinked and saves an array to a 'symlinked_plugins' key of wp_options. The array has an element for each of the symlinked plugins with the array key being realpath( $virtual_plugin_file ) and the value being virtual_plugin_file, i.e.
update_options( 'symlinked_plugins', array( '/Users/user/Plugins/my-plugin-1/my-plugin-1.php' => '/Users/user/Sites/test/wp-content/plugins/my-plugin-1/my-plugin-1.php', '/Users/user/Plugins/my-plugin-2/my-plugin-2.php' => '/Users/user/Sites/test/wp-content/plugins/my-plugin-2/my-plugin-2.php', ));
Then plugins_url() simply checks to see if the value of the $plugin parameter -- which is typically passed in as __FILE__ -- is a key in the array returned by get_option( 'symlinked_plugins' ) and if yes it assigns $plugin with that array element's value.
I think that's all it takes. I've been circling this problem for a while so maybe this is all we need to enable plugins_url() to work correctly with symlinked plugins. But I might have missed a requirement and if so will need others to point out what I've missed.
Also please consider this a starting point for discussion; I don't expect that it's completely ready for production, but crossing fingers it might be.
comment:42
follow-up:
↓ 43
scribu — 3 months ago
Re symlinked_plugins.diff: the check you added to plugin_url() should be moved to plugin_basename(), since that's the more general function (and also the one that doesn't have a filter).
comment:43
in reply to:
↑ 42
MikeSchinkel — 3 months ago
Replying to scribu:
Re symlinked_plugins.diff: the check you added to plugin_url() should be moved to plugin_basename(), since that's the more general function (and also the one that doesn't have a filter).
Originally I didn't put in plugin_basename() because it looked like that would not have worked but I just tested and you are correct, it works. I also tested it with a symlinked plugin where the virtual directory was different than the real directory and it still worked correctly, i.e. my-plugin-dev/my-plugin.php vs. my-plugin/my-plugin.php:
Virtual Path => /Users/my-user/Site/my-site/wp-content/plugins/my-plugin-dev/my-plugin.php Real Path => /Users/my-user/Plugins/my-plugin/my-plugin.php
I also added an enhancement that omits the 'symlinked_plugins' option if there are no symlinked plugins so that the options table won't won't be polluted with that entry when there are no plugins that have been symlinked.
Attached is a new patch with the check moved to plugins_url().
MikeSchinkel — 3 months ago
2nd attempt at patch to enable plugin_url() to support symlinked plugins.
comment:44
anointed — 3 months ago
- Cc imtiedup@… added
comment:45
johnnyb — 2 months ago
- Cc johnbeales@… added
comment:46
beaver6813 — 2 months ago
- Cc beaver6813 added
comment:47
lingfish — 7 weeks ago
- Cc jason@… added
comment:48
SergeyBiryukov — 7 weeks ago
- Version trunk deleted
comment:49
ddean — 4 weeks ago
- Cc david@… added
comment:50
anointed — 4 weeks ago
This has been working pretty well over the past few months.
Is there any chance of this making it into 3.6 so that I can update WordPress without fear of loosing all these code changes?
It has made a HUGE difference for me on the organization of my server files and makes maintaining dozens of sites on a single server so much easier. I no longer have to worry about any clients getting lazy and not updating their plugins and potentially leading to a security breach.
Has anyone at all come up with a problem that would cause this not to work properly in time for 3.6?
*If for some reason it can't be done in time, is it possible to turn this into an 'mu' plugin in the meantime?
comment:51
luv4wp — 4 weeks ago
Looks like MikeSchinkels code works for me.
Agree with everyone else here, please add this to the core. Putting it into 3.6 would be great so we don't have to wait 6 months.
comment:52
follow-up:
↓ 53
MikeSchinkel — 4 weeks ago
If adding my patch to the core is too much for 3.6, can you please consider adding a 'pre_plugin_basename' hook that would allow us to create an mu-plugin to do the rest? I've attached a patch for that, above.
comment:53
in reply to:
↑ 52
anointed — 4 weeks ago
Replying to MikeSchinkel:
If adding my patch to the core is too much for 3.6, can you please consider adding a 'pre_plugin_basename' hook that would allow us to create an mu-plugin to do the rest? I've attached a patch for that, above.
+1 if we can't have a core patch, this would work just fine for now!
comment:54
follow-up:
↓ 55
SergeyBiryukov — 4 weeks ago
16953.diff could go in before the beta, but we don't typically add new hooks after feature freeze.
comment:55
in reply to:
↑ 54
MikeSchinkel — 4 weeks ago
Replying to SergeyBiryukov:
16953.diff could go in before the beta, but we don't typically add new hooks after feature freeze.
Given how this ticket has been languishing for 2 years with lots of interest and having come full circle, it sure would be nice. :)
comment:56
corvannoorloos — 3 weeks ago
- Cc cor@… added
comment:57
in reply to:
↑ 39
aubreypwd — 13 days ago
I can confirm that the below solution worked for me in using:
define('WP_PLUGIN_DIR', realpath('/home/152858/domains/example.com/html/all/wp-content/plugins') );
define('WP_PLUGIN_URL', 'http://example.com/all/wp-content/plugins');
define('WP_THEMES_DIR', realpath('/home/152858/domains/example.com/html/all/wp-content/themes') );
define('WP_THEMES_URL', 'http://example.com/all/wp-content/themes');
define('WP_CONTENT_DIR', realpath('/home/152858/domains/example.com/html/all/wp-content') );
define('WP_CONTENT_URL', 'http://example.com/all/wp-content');
Replying to markus.magnuson:
I resolved all my plugin problems caused by symlinks in the wp-content path by using the realpath() function in my wp-config.php. I simply replace this:
define('WP_CONTENT_DIR',$_SERVER['DOCUMENT_ROOT'] . '/wp-content');With this:
define('WP_CONTENT_DIR', realpath($_SERVER['DOCUMENT_ROOT'] . '/wp-content'));Thought that might help anyone ending up in this ticket when troubleshooting.
comment:58
JeromeC — 12 days ago
- Cc JeromeC added
comment:59
francescolaffi — 5 days ago
- Cc francesco.laffi@… added
comment:60
rodrigosprimo — 3 days ago
- Cc rodrigosprimo@… added

'pre_plugin_basename' filter