debugging register_activation_hook()

I feel a bit dense for not noticing this sooner, but I finally realized that my plugins directory on my computer is a symbolic link to another location as pointed out by Kunal Bhalla in symlinks with WP plugins. Since __FILE__ resolves symbolic links register_activation_hook() is attempting to load a file outside of the WordPress plugins path.

If you look at the register_activation_hook() function in plugin.php you’ll see the magic seems to happen in plugin_basename():

function register_activation_hook($file, $function) {
    $file = plugin_basename($file);
    add_action('activate_' . $file, $function);
}

Further examination shows that plugin_basename() is essentially performing pattern replacement in attempt to turn a system path into my_plugin/my_plugin.php; however, since str_replace() is using WP_PLUGIN_DIR as the subject, any system path outside of that the WordPress plugin directory will not produce a match to replace against:

function plugin_basename($file) {
    $file = str_replace('\\',"https://wordpress.stackexchange.com/",$file); // sanitize for Win32 installs
    $file = preg_replace('|/+|',"https://wordpress.stackexchange.com/", $file); // remove any duplicate slash
    $plugin_dir = str_replace('\\',"https://wordpress.stackexchange.com/",WP_PLUGIN_DIR); // sanitize for Win32 installs
    $plugin_dir = preg_replace('|/+|',"https://wordpress.stackexchange.com/", $plugin_dir); // remove any duplicate slash
    $mu_plugin_dir = str_replace('\\',"https://wordpress.stackexchange.com/",WPMU_PLUGIN_DIR); // sanitize for Win32 installs
    $mu_plugin_dir = preg_replace('|/+|',"https://wordpress.stackexchange.com/", $mu_plugin_dir); // remove any duplicate slash
    $file = preg_replace('#^' . preg_quote($plugin_dir, '#') . "https://wordpress.stackexchange.com/"^' . preg_quote($mu_plugin_dir, '#') . '/#','',$file); // get relative path from plugins dir
    $file = trim($file, "https://wordpress.stackexchange.com/");
    return $file;
}

This method seems a little short-sited, and I’ve found several solutions ranging from defining additional constants in wp-config.php to hard-coding the relative path of your plugin. Instead I think a better stop-gap method might be to use the following pattern which pre-defines the path plugin_basename() is attempting to extract. This can be used in place of __FILE__ as the first parameter of register_activation_hook().

basename( dirname( __FILE__ ) )."https://wordpress.stackexchange.com/".basename( __FILE__ );

This method will only work if your plugin has it’s own folder in the wp-content/plugins directory and is called from the base plugin file. Both of these conditions seem to be generally in control of the plugin developer, though I’m curious if anyone has some opposing thoughts.


Additional Links: