0

I'm developing a plugin that may also be used as a library in themes or plugins.

I need to enqueue a custom script, but I cannot know the absolute path to the script, as the final location of the library may vary. I don't want the other developers to have to manually define a constant with the path to the library/script either, as that's extra work for them and I want the library to be as minimalistic as possible

I also want to consider that this plugin/library might be symlinked, which means that I may not be able to know if it's located in the plugins directory or the themes directory.

This is where it becomes the trickiest, because PHP's magic constants FILE and DIR resolve the symlinks and I'm unable to figure out where the library is being used.

This is what I currently have. Is there any way to improve this?

static function enqueue_admin_scripts( string $hook_suffix ): void {
    if ( self::get_page_hook_suffix() !== $hook_suffix ) {
        return;
    }

    if ( str_contains( __DIR__, ABSPATH ) ) {
        // Enqueue script taking into account that MY_PLUGIN may be either a plugin or a library used in another plugin or theme.
        $utils_path = trailingslashit( str_replace( ABSPATH, '/', __DIR__ ) ) . 'utils.js';
    } else {
        // The plugin has been symlinked and the previous enqueue method won't resolve.
        $utils_path = plugin_dir_url( __FILE__ ) . 'utils.js';

        // When using the MY_PLUGIN as a library, MY_PLUGIN_DIR must be defined in your plugin or theme using the right path.
        if ( MY_PLUGIN_DIR !== dirname( __DIR__, 2 ) ) {
            $utils_path = trailingslashit( MY_PLUGIN_DIR ) . 'includes/Settings/utils.js';
        } else {
            if ( ! defined( 'MY_PLUGIN_DISABLE_LOG' ) || ! MY_PLUGIN_DISABLE_LOG ) {
                error_log( "WARNING: MY_PLUGIN has been symlinked. The script utils.js might not be loading correctly. If it is not loading correctly and you are using MY_PLUGIN in your theme or plugin, please define the constant MY_PLUGIN_DIR with the correct path to MY_PLUGIN. To disable this warning, use define( 'MY_PLUGIN_DISABLE_LOG', true ) in your functions.php or your plugin main file." );
            }
        }
    }
    wp_enqueue_script( 'my-plugin-utils', $utils_path );
}

I would like to be able to simplify this code as well as to offer a bulletproof solution without requiring a constant to be defined.

4
  • 1
    I don't see the necessity of guessing the script path.. Can't you just put the utils script in a folder like js in your plugin folder, then add a constant like define( 'MY_PLUGIN_FILE', __FILE__ ) in the main plugin file, and (for example) use plugins_url( 'js/utils.js', MY_PLUGIN_FILE ) when enqueueing the script?
    – Sally CJ
    Commented Jan 23 at 10:43
  • @SallyCJ In a normal environment yes, you could do something like that. But if the plugin directory happens to be a symlink the path won't resolve. Commented Jan 23 at 15:25
  • 1
    I've never came across such situations, but readlink() and is_link() should help.
    – Sally CJ
    Commented Jan 23 at 16:12
  • @SallyCJ Unfortunately those functions don't seem to provide the path I need, but the opposite one. They return the real path, which in this case wouldn't match the path within WordPress if the real directory was elsewhere. Not having a way to get the unresolved paths for symlinks is a PHP flaw that has never been fixed in over 17 years. Commented Jan 24 at 7:04

1 Answer 1

1

I think this can help you. I did something similar in one of the plugins I'm developing.

static function enqueue_admin_scripts( string $hook_suffix ): void {
    if ( self::get_page_hook_suffix() !== $hook_suffix ) {
        return;
    }

    $utils_path = self::get_utils_script_path();
    if ( ! empty( $utils_path ) ) {
        wp_enqueue_script( 'my-plugin-utils', $utils_path );
    }
}

static function get_utils_script_path(): string {
    $relative_path = '/path/to/utils.js'; // Adjust the relative path accordingly

    // Check if the file exists in the child theme
    $child_theme_path = get_stylesheet_directory() . $relative_path;
    if ( file_exists( $child_theme_path ) ) {
        return get_stylesheet_directory_uri() . $relative_path;
    }

    // Check if the file exists in the parent theme
    $parent_theme_path = get_template_directory() . $relative_path;
    if ( file_exists( $parent_theme_path ) ) {
        return get_template_directory_uri() . $relative_path;
    }

    // Check if the file exists in the plugin
    $plugin_path = plugin_dir_path( __FILE__ ) . $relative_path;
    if ( file_exists( $plugin_path ) ) {
        return plugins_url( $relative_path, __FILE__ );
    }

    // Return an empty string if the file does not exist in any of the locations
    return '';
}

static function determine_context(): ?string {
    // Implement logic to determine if the context is a plugin or theme
    // You can check the backtrace or look for specific functions or classes
    // Return 'plugin', 'theme', or null
}
2
  • Not quite, as this solution doesn't seem to cover the case in which the plugin is included as another plugin/theme library. Checking for file existence could help a bit though. I'm thinking that maybe in this edge case it could be better to use something like wp_add_inline_script() to add the contents of the file (for which I know the relative path) to another default WordPress script that I'm already enqueueing. Probably not the cleanest solution, although way shorter. I might just echo the script on the page using some hook at the cost of losing the ability to dequeue it if necessary. Commented Jan 23 at 15:34
  • 1
    I've given you some foundation, so you can build slowly. You will hardly make the script you need just like that without making some overkill. The only thing is to create your own index of plugins and then check if the plugin is active and has that script -> use it. Or look into function wp_scripts() and try loop inside all scripts. Commented Jan 23 at 21:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.