From dd1a3e3b521017e23737692ec653e78a4349c979 Mon Sep 17 00:00:00 2001 From: Kim Brown <50059399+kbrown9@users.noreply.github.com> Date: Mon, 30 Mar 2020 16:31:22 -0400 Subject: [PATCH] Autoloader: refactor functions.php (#15111) * Autoloader: refactor functions.php into classes * Autoloader: update unit tests Update the unit tests to use the new Classes_Handler and Files_Handler classes. * Autoloader: update is_current_plugin_active() Update is_current_plugin_active() to check both active and activating plugins when determining whether the current plugin is active. The active and activating plugins can be obtained using the Plugins_Handler::get_active_plugins() method. * Added newly generated files to gitignore. * Autoloader: improve Plugins_Handler Improve the Plugins_Handler class by: - Giving some methods more descriptive names. - Adding a $jetpack_autoloader_activating_plugins global variable that contains the plugins that are activating via a non-request method. - Refactoring a few methods to improve clarity. * Autoloader: update the autoloader chain after autoloader reset Add a new function, Autoloader_Handler::update_autoloader_chain(), which handles updates of the autoloader chain as follows: - Registers the latest autoloader function. - Moves the v1 autoloader function to the end of the chain. - Removes any other v2 autoloader functions. This is needed for situations where a plugin activates using a non-request method, so the activating plugin was not known when the autoloader first ran. If the activating plugin has an autoloader with a later version, that autoloader will be registered and the previously registered autoloader function should be removed. Finally, use the Autoloader_Handler::update_autoloader_chain() method in set_up_autoloader(). * Autoloader: update update_autoloader_chain() In Autoloader_Handler::update_autoloader_chain(), check whether the autoloader object is a string. If not, just continue. We're only looking for Jetpack autoloader functions, and they're registered as strings. Co-authored-by: Igor Zinovyev --- .gitignore | 4 + packages/autoloader/src/AutoloadGenerator.php | 14 +- .../src/class-autoloader-handler.php | 120 +++++++ .../autoloader/src/class-classes-handler.php | 86 +++++ .../autoloader/src/class-files-handler.php | 100 ++++++ .../autoloader/src/class-plugins-handler.php | 146 ++++++++ packages/autoloader/src/functions.php | 315 ++---------------- packages/autoloader/tests/php/bootstrap.php | 3 + .../autoloader/tests/php/test_Autoloader.php | 16 +- .../autoloader/tests/php/test_file_loader.php | 28 +- 10 files changed, 532 insertions(+), 300 deletions(-) create mode 100644 packages/autoloader/src/class-autoloader-handler.php create mode 100644 packages/autoloader/src/class-classes-handler.php create mode 100644 packages/autoloader/src/class-files-handler.php create mode 100644 packages/autoloader/src/class-plugins-handler.php diff --git a/.gitignore b/.gitignore index 0c437a5e7b159..96c24465c55df 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,7 @@ _inc/jetpack-strings.php /vendor/autoload.php /vendor/autoload_packages.php /vendor/autoload_functions.php +/vendor/class-autoloader-handler.php +/vendor/class-classes-handler.php +/vendor/class-files-handler.php +/vendor/class-plugins-handler.php diff --git a/packages/autoloader/src/AutoloadGenerator.php b/packages/autoloader/src/AutoloadGenerator.php index e9d2ad65618fc..18310a22cbf5f 100644 --- a/packages/autoloader/src/AutoloadGenerator.php +++ b/packages/autoloader/src/AutoloadGenerator.php @@ -103,6 +103,18 @@ public function dump( file_put_contents( $vendorPath . '/autoload_functions.php', $this->getAutoloadPackageFile( 'functions.php', $suffix ) ); $this->io->writeError( 'Generated ' . $vendorPath . '/autoload_functions.php', true ); + + file_put_contents( $vendorPath . '/class-autoloader-handler.php', $this->getAutoloadPackageFile( 'class-autoloader-handler.php', $suffix ) ); + $this->io->writeError( 'Generated ' . $vendorPath . '/class-autoloader-handler.php', true ); + + file_put_contents( $vendorPath . '/class-classes-handler.php', $this->getAutoloadPackageFile( 'class-classes-handler.php', $suffix ) ); + $this->io->writeError( 'Generated ' . $vendorPath . '/class-classes-handler.php', true ); + + file_put_contents( $vendorPath . '/class-files-handler.php', $this->getAutoloadPackageFile( 'class-files-handler.php', $suffix ) ); + $this->io->writeError( 'Generated ' . $vendorPath . '/class-files-handler.php', true ); + + file_put_contents( $vendorPath . '/class-plugins-handler.php', $this->getAutoloadPackageFile( 'class-plugins-handler.php', $suffix ) ); + $this->io->writeError( 'Generated ' . $vendorPath . '/class-plugins-handler.php', true ); } /** @@ -330,7 +342,7 @@ private function getAutoloadFilesPackagesFile( $filesMap ) { */ private function getAutoloadPackageFile( $filename, $suffix ) { $header = self::COMMENT; - $header .= PHP_EOL . PHP_EOL; + $header .= PHP_EOL; $header .= 'namespace Automattic\Jetpack\Autoloader\jp' . $suffix . ';'; $header .= PHP_EOL . PHP_EOL; diff --git a/packages/autoloader/src/class-autoloader-handler.php b/packages/autoloader/src/class-autoloader-handler.php new file mode 100644 index 0000000000000..5fde0720b9841 --- /dev/null +++ b/packages/autoloader/src/class-autoloader-handler.php @@ -0,0 +1,120 @@ +plugins_handler = $plugins_handler; + } + + /** + * Finds the latest installed autoloader. + */ + public function find_latest_autoloader() { + global $jetpack_autoloader_latest_version; + + $current_autoloader_path = trailingslashit( dirname( __FILE__ ) ) . 'autoload_packages.php'; + + $selected_autoloader_version = null; + $selected_autoloader_path = null; + + $active_plugins = $this->plugins_handler->get_all_active_plugins(); + + foreach ( $active_plugins as $plugin ) { + $plugin_path = plugin_dir_path( trailingslashit( WP_PLUGIN_DIR ) . $plugin ); + $classmap_path = trailingslashit( $plugin_path ) . 'vendor/composer/jetpack_autoload_classmap.php'; + + if ( file_exists( $classmap_path ) ) { + $packages = require $classmap_path; + + $compare_version = $packages['Automattic\\Jetpack\\Autoloader\\AutoloadGenerator']['version']; + $compare_path = trailingslashit( $plugin_path ) . 'vendor/autoload_packages.php'; + + // TODO: This comparison needs to properly handle dev versions. + if ( version_compare( $selected_autoloader_version, $compare_version, '<' ) ) { + $selected_autoloader_version = $compare_version; + $selected_autoloader_path = $compare_path; + } + } + } + + $jetpack_autoloader_latest_version = $selected_autoloader_version; + if ( $current_autoloader_path !== $selected_autoloader_path ) { + require $selected_autoloader_path; + } + } + + /** + * Get this autoloader's package version. + * + * @return String The autoloader's package version. + */ + public function get_current_autoloader_version() { + $classmap_file = trailingslashit( dirname( __FILE__ ) ) . 'composer/jetpack_autoload_classmap.php'; + $autoloader_packages = require $classmap_file; + + return $autoloader_packages['Automattic\\Jetpack\\Autoloader\\AutoloadGenerator']['version']; + } + + + /** + * Updates the spl autoloader chain: + * - Registers this namespace's autoloader function. + * - If a v1 autoloader function is registered, moves it to the end of the chain. + * - Removes any other v2 autoloader functions that have already been registered. This + * can occur when the autoloader is being reset by an activating plugin. + */ + public function update_autoloader_chain() { + spl_autoload_register( __NAMESPACE__ . '\autoloader' ); + + $autoload_chain = spl_autoload_functions(); + + foreach ( $autoload_chain as $autoloader ) { + if ( ! is_string( $autoloader ) ) { + /* + * The Jetpack Autoloader functions are registered as strings, so + * just continue if $autoloader isn't a string. + */ + continue; + } + + if ( self::V1_AUTOLOADER_NAME === $autoloader ) { + // Move the v1.* autoloader function to the end of the spl autoloader chain. + spl_autoload_unregister( $autoloader ); + spl_autoload_register( $autoloader ); + + } elseif ( + self::V2_AUTOLOADER_BASE === substr( $autoloader, 0, strlen( self::V2_AUTOLOADER_BASE ) ) + && __NAMESPACE__ !== substr( $autoloader, 0, strlen( __NAMESPACE__ ) ) + ) { + // Unregister any other v2.* autoloader functions if they're in the chain. + spl_autoload_unregister( $autoloader ); + } + } + } +} diff --git a/packages/autoloader/src/class-classes-handler.php b/packages/autoloader/src/class-classes-handler.php new file mode 100644 index 0000000000000..84329f50e18d6 --- /dev/null +++ b/packages/autoloader/src/class-classes-handler.php @@ -0,0 +1,86 @@ +plugins_handler = $plugins_handler; + } + + /** + * Adds the version of a package to the $jetpack_packages_classmap global + * array so that the autoloader is able to find it. + * + * @param string $class_name Name of the class that you want to autoload. + * @param string $version Version of the class. + * @param string $path Absolute path to the class so that we can load it. + */ + public function enqueue_package_class( $class_name, $version, $path ) { + global $jetpack_packages_classmap; + + if ( ! isset( $jetpack_packages_classmap[ $class_name ] ) ) { + $jetpack_packages_classmap[ $class_name ] = array( + 'version' => $version, + 'path' => $path, + ); + + return; + } + // If we have a @dev version set always use that one! + if ( 'dev-' === substr( $jetpack_packages_classmap[ $class_name ]['version'], 0, 4 ) ) { + return; + } + + // Always favour the @dev version. Since that version is the same as bleeding edge. + // We need to make sure that we don't do this in production! + if ( 'dev-' === substr( $version, 0, 4 ) ) { + $jetpack_packages_classmap[ $class_name ] = array( + 'version' => $version, + 'path' => $path, + ); + + return; + } + // Set the latest version! + if ( version_compare( $jetpack_packages_classmap[ $class_name ]['version'], $version, '<' ) ) { + $jetpack_packages_classmap[ $class_name ] = array( + 'version' => $version, + 'path' => $path, + ); + } + } + + /** + * Initializes the classmap. + */ + public function set_class_paths() { + $paths = $this->plugins_handler->get_active_plugins_paths(); + + foreach ( $paths as $path ) { + if ( is_readable( $path['class'] ) ) { + $class_map = require $path['class']; + + if ( is_array( $class_map ) ) { + foreach ( $class_map as $class_name => $class_info ) { + $this->enqueue_package_class( $class_name, $class_info['version'], $class_info['path'] ); + } + } + } + } + } +} diff --git a/packages/autoloader/src/class-files-handler.php b/packages/autoloader/src/class-files-handler.php new file mode 100644 index 0000000000000..43548d65f1a0b --- /dev/null +++ b/packages/autoloader/src/class-files-handler.php @@ -0,0 +1,100 @@ +plugins_handler = $plugins_handler; + } + + /** + * Adds the version of a package file to the $jetpack_packages_filemap global + * array so that we can load the most recent version. + * + * @param string $file_identifier Unique id to file assigned by composer based on package name and filename. + * @param string $version Version of the file. + * @param string $path Absolute path to the file so that we can load it. + */ + public function enqueue_package_file( $file_identifier, $version, $path ) { + global $jetpack_packages_filemap; + + if ( ! isset( $jetpack_packages_filemap[ $file_identifier ] ) ) { + $jetpack_packages_filemap[ $file_identifier ] = array( + 'version' => $version, + 'path' => $path, + ); + + return; + } + // If we have a @dev version set always use that one! + if ( 'dev-' === substr( $jetpack_packages_filemap[ $file_identifier ]['version'], 0, 4 ) ) { + return; + } + + // Always favour the @dev version. Since that version is the same as bleeding edge. + // We need to make sure that we don't do this in production! + if ( 'dev-' === substr( $version, 0, 4 ) ) { + $jetpack_packages_filemap[ $file_identifier ] = array( + 'version' => $version, + 'path' => $path, + ); + + return; + } + // Set the latest version! + if ( version_compare( $jetpack_packages_filemap[ $file_identifier ]['version'], $version, '<' ) ) { + $jetpack_packages_filemap[ $file_identifier ] = array( + 'version' => $version, + 'path' => $path, + ); + } + } + + /** + * Initializes the filemap. + */ + public function set_file_paths() { + $paths = $this->plugins_handler->get_active_plugins_paths(); + + foreach ( $paths as $path ) { + if ( is_readable( $path['file'] ) ) { + $file_map = require $path['file']; + + if ( is_array( $file_map ) ) { + foreach ( $file_map as $file_identifier => $file_data ) { + $this->enqueue_package_file( $file_identifier, $file_data['version'], $file_data['path'] ); + } + } + } + } + } + + /** + * Include latest version of all enqueued files. + */ + public function file_loader() { + global $jetpack_packages_filemap; + foreach ( $jetpack_packages_filemap as $file_identifier => $file_data ) { + if ( empty( $GLOBALS['__composer_autoload_files'][ $file_identifier ] ) ) { + require_once $file_data['path']; + + $GLOBALS['__composer_autoload_files'][ $file_identifier ] = true; + } + } + } +} diff --git a/packages/autoloader/src/class-plugins-handler.php b/packages/autoloader/src/class-plugins-handler.php new file mode 100644 index 0000000000000..52c802528465e --- /dev/null +++ b/packages/autoloader/src/class-plugins-handler.php @@ -0,0 +1,146 @@ +get_all_activating_plugins() ) ); + + return $plugins; + } + + /** + * Creates an array containing the paths to the classmap and filemap for the given plugin. + * The classmap and filemap filenames are the names of the files generated by Jetpack + * Autoloader with versions >=2.0. + * + * @param String $plugin The plugin string. + * @return Array An array containing the paths to the plugin's classmap and filemap. + */ + public function create_map_path_array( $plugin ) { + $plugin_path = plugin_dir_path( trailingslashit( WP_PLUGIN_DIR ) . $plugin ); + + return array( + 'class' => trailingslashit( $plugin_path ) . 'vendor/composer/jetpack_autoload_classmap.php', + 'file' => trailingslashit( $plugin_path ) . 'vendor/composer/jetpack_autoload_filemap.php', + ); + } + + /** + * Returns an array containing the paths to the classmap and filemap for the active plugins. + */ + public function get_active_plugins_paths() { + $active_plugins = $this->get_all_active_plugins(); + return array_map( array( $this, 'create_map_path_array' ), $active_plugins ); + } + + /** + * Checks whether the autoloader should be reset. The autoloader should be reset + * when a plugin is activating via a method other than a request, for example + * using WP-CLI. When this occurs, the activating plugin was not known when + * the autoloader selected the package versions for the classmap and filemap + * globals, so the autoloader must reselect the versions. + * + * If the current plugin is not already known, this method will add it to the + * $jetpack_autoloader_activating_plugins global. + * + * @return Boolean True if the autoloder must be reset, else false. + */ + public function should_autoloader_reset() { + global $jetpack_autoloader_activating_plugins; + + $plugins = $this->get_all_active_plugins(); + $current_plugin = $this->get_current_plugin(); + $plugin_unknown = ! in_array( $current_plugin, $plugins, true ); + + if ( $plugin_unknown ) { + // If the current plugin isn't known, add it to the activating plugins list. + $jetpack_autoloader_activating_plugins[] = $current_plugin; + } + + return $plugin_unknown; + } + + /** + * Returns the names of activating plugins if the plugins are activating via a request. + * + * @return Array The array of the activating plugins or empty array. + */ + private function get_plugins_activating_via_request() { + + // phpcs:disable WordPress.Security.NonceVerification.Recommended + + $action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : false; + $plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : false; + $nonce = isset( $_REQUEST['_wpnonce'] ) ? $_REQUEST['_wpnonce'] : false; + + /** + * Note: we're not actually checking the nonce here becase it's too early + * in the execution. The pluggable functions are not yet loaded to give + * plugins a chance to plug their versions. Therefore we're doing the bare + * minimum: checking whether the nonce exists and it's in the right place. + * The request will fail later if the nonce doesn't pass the check. + */ + + // In case of a single plugin activation there will be a plugin slug. + if ( 'activate' === $action && ! empty( $nonce ) ) { + return array( wp_unslash( $plugin ) ); + } + + $plugins = isset( $_REQUEST['checked'] ) ? $_REQUEST['checked'] : array(); + + // In case of bulk activation there will be an array of plugins. + if ( 'activate-selected' === $action && ! empty( $nonce ) ) { + return array_map( 'wp_unslash', $plugins ); + } + + // phpcs:enable WordPress.Security.NonceVerification.Recommended + + return array(); + } + + /** + * Returns an array of the names of all known activating plugins. This includes + * plugins activating via a request and plugins that are activating via other + * methods. + * + * @return Array The array of all activating plugins or empty array. + */ + private function get_all_activating_plugins() { + global $jetpack_autoloader_activating_plugins; + + $activating_plugins = $this->get_plugins_activating_via_request(); + return array_unique( array_merge( $activating_plugins, $jetpack_autoloader_activating_plugins ) ); + } + + /** + * Returns the name of the current plugin. + * + * @return String The name of the current plugin. + */ + public function get_current_plugin() { + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $dir = explode( '/', plugin_basename( __FILE__ ) )[0]; + $file = array_keys( get_plugins( "/$dir" ) )[0]; + return "$dir/$file"; + } +} diff --git a/packages/autoloader/src/functions.php b/packages/autoloader/src/functions.php index 014bfeb17793b..cb6379b0f5969 100644 --- a/packages/autoloader/src/functions.php +++ b/packages/autoloader/src/functions.php @@ -3,6 +3,7 @@ global $jetpack_packages_classmap; global $jetpack_packages_filemap; +global $jetpack_autoloader_activating_plugins; if ( ! is_array( $jetpack_packages_classmap ) ) { $jetpack_packages_classmap = array(); @@ -12,104 +13,8 @@ $jetpack_packages_filemap = array(); } -/** - * Adds the version of a package to the $jetpack_packages global array so that - * the autoloader is able to find it. - * - * @param string $class_name Name of the class that you want to autoload. - * @param string $version Version of the class. - * @param string $path Absolute path to the class so that we can load it. - */ -function enqueue_package_class( $class_name, $version, $path ) { - global $jetpack_packages_classmap; - - if ( ! isset( $jetpack_packages_classmap[ $class_name ] ) ) { - $jetpack_packages_classmap[ $class_name ] = array( - 'version' => $version, - 'path' => $path, - ); - - return; - } - // If we have a @dev version set always use that one! - if ( 'dev-' === substr( $jetpack_packages_classmap[ $class_name ]['version'], 0, 4 ) ) { - return; - } - - // Always favour the @dev version. Since that version is the same as bleeding edge. - // We need to make sure that we don't do this in production! - if ( 'dev-' === substr( $version, 0, 4 ) ) { - $jetpack_packages_classmap[ $class_name ] = array( - 'version' => $version, - 'path' => $path, - ); - - return; - } - // Set the latest version! - if ( version_compare( $jetpack_packages_classmap[ $class_name ]['version'], $version, '<' ) ) { - $jetpack_packages_classmap[ $class_name ] = array( - 'version' => $version, - 'path' => $path, - ); - } -} - -/** - * Adds the version of a package file to the $jetpack_packages_filemap global array so that - * we can load the most recent version after 'plugins_loaded'. - * - * @param string $file_identifier Unique id to file assigned by composer based on package name and filename. - * @param string $version Version of the file. - * @param string $path Absolute path to the file so that we can load it. - */ -function enqueue_package_file( $file_identifier, $version, $path ) { - global $jetpack_packages_filemap; - - if ( ! isset( $jetpack_packages_filemap[ $file_identifier ] ) ) { - $jetpack_packages_filemap[ $file_identifier ] = array( - 'version' => $version, - 'path' => $path, - ); - - return; - } - // If we have a @dev version set always use that one! - if ( 'dev-' === substr( $jetpack_packages_filemap[ $file_identifier ]['version'], 0, 4 ) ) { - return; - } - - // Always favour the @dev version. Since that version is the same as bleeding edge. - // We need to make sure that we don't do this in production! - if ( 'dev-' === substr( $version, 0, 4 ) ) { - $jetpack_packages_filemap[ $file_identifier ] = array( - 'version' => $version, - 'path' => $path, - ); - - return; - } - // Set the latest version! - if ( version_compare( $jetpack_packages_filemap[ $file_identifier ]['version'], $version, '<' ) ) { - $jetpack_packages_filemap[ $file_identifier ] = array( - 'version' => $version, - 'path' => $path, - ); - } -} - -/** - * Include latest version of all enqueued files. Should be called after all plugins are loaded. - */ -function file_loader() { - global $jetpack_packages_filemap; - foreach ( $jetpack_packages_filemap as $file_identifier => $file_data ) { - if ( empty( $GLOBALS['__composer_autoload_files'][ $file_identifier ] ) ) { - require_once $file_data['path']; - - $GLOBALS['__composer_autoload_files'][ $file_identifier ] = true; - } - } +if ( ! is_array( $jetpack_autoloader_activating_plugins ) ) { + $jetpack_autoloader_activating_plugins = array(); } /** @@ -132,207 +37,55 @@ function autoloader( $class_name ) { /** * Used for running the code that initializes class and file maps. - */ -function enqueue_files() { - $active_plugins = get_active_plugins(); - $paths = array_map( __NAMESPACE__ . '\create_map_path_array', $active_plugins ); - - foreach ( $paths as $path ) { - if ( is_readable( $path['class'] ) ) { - $class_map = require $path['class']; - - if ( is_array( $class_map ) ) { - foreach ( $class_map as $class_name => $class_info ) { - enqueue_package_class( $class_name, $class_info['version'], $class_info['path'] ); - } - } - } - - if ( is_readable( $path['file'] ) ) { - $file_map = require $path['file']; - - if ( is_array( $file_map ) ) { - foreach ( $file_map as $file_identifier => $file_data ) { - enqueue_package_file( $file_identifier, $file_data['version'], $file_data['path'] ); - } - } - } - } - - file_loader(); -} - -/** - * Returns an array containing the active plugins. If plugin is activating, it - * is included in the array. * - * @return Array An array of plugin names as strings. + * @param Plugins_Handler $plugins_handler The Plugins_Handler object. */ -function get_active_plugins() { - $active_plugins = array_merge( - is_multisite() - ? array_keys( get_site_option( 'active_sitewide_plugins', array() ) ) - : array(), - (array) get_option( 'active_plugins', array() ) - ); - $current_plugin = get_current_plugin(); +function enqueue_files( $plugins_handler ) { + require_once __DIR__ . '/class-classes-handler.php'; + require_once __DIR__ . '/class-files-handler.php'; - if ( ! in_array( $current_plugin, $active_plugins, true ) ) { - // The current plugin isn't active, so it must be activating. Add it to the list. - $active_plugins[] = $current_plugin; - } + $classes_handler = new Classes_Handler( $plugins_handler ); + $classes_handler->set_class_paths(); - // If the activating plugin is not the only activating plugin, we need to add others too. - $active_plugins = array_unique( array_merge( $active_plugins, get_activating_plugins() ) ); + $files_handler = new Files_Handler( $plugins_handler ); + $files_handler->set_file_paths(); - return $active_plugins; + $files_handler->file_loader(); } /** - * Creates an array containing the paths to the classmap and filemap for the given plugin. - * The filenames are the names of the files generated by the Jetpack Autoloader version >2.0. - * - * @param String $plugin The plugin string. - * @return Array An array containing the paths to the plugin's classmap and filemap. - */ -function create_map_path_array( $plugin ) { - $plugin_path = plugin_dir_path( trailingslashit( WP_PLUGIN_DIR ) . $plugin ); - - return array( - 'class' => trailingslashit( $plugin_path ) . 'vendor/composer/jetpack_autoload_classmap.php', - 'file' => trailingslashit( $plugin_path ) . 'vendor/composer/jetpack_autoload_filemap.php', - ); -} - -/** - * Checks whether the current plugin is active. - * - * @return Boolean True if the current plugin is active, else false. - */ -function is_current_plugin_active() { - $active_plugins = (array) get_option( 'active_plugins', array() ); - $current_plugin = get_current_plugin(); - - return in_array( $current_plugin, $active_plugins, true ); -} - -/** - * Returns the name of activating plugin if a plugin is activating via a request. - * - * @return Array The array of the activating plugins or empty array. - */ -function get_activating_plugins() { - - // phpcs:disable WordPress.Security.NonceVerification.Recommended - - $action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : false; - $plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : false; - $nonce = isset( $_REQUEST['_wpnonce'] ) ? $_REQUEST['_wpnonce'] : false; - - /** - * Note: we're not actually checking the nonce here becase it's too early - * in the execution. The pluggable functions are not yet loaded to give - * plugins a chance to plug their versions. Therefore we're doing the bare - * minimum: checking whether the nonce exists and it's in the right place. - * The request will fail later if the nonce doesn't pass the check. - */ - - // In case of a single plugin activation there will be a plugin slug. - if ( 'activate' === $action && ! empty( $nonce ) ) { - return array( wp_unslash( $plugin ) ); - } - - $plugins = isset( $_REQUEST['checked'] ) ? $_REQUEST['checked'] : array(); - - // In case of bulk activation there will be an array of plugins. - if ( 'activate-selected' === $action && ! empty( $nonce ) ) { - return array_map( 'wp_unslash', $plugins ); - } - - // phpcs:enable WordPress.Security.NonceVerification.Recommended - - return array(); -} - -/** - * Returns the name of the current plugin. - * - * @return String The name of the current plugin. - */ -function get_current_plugin() { - if ( ! function_exists( 'get_plugins' ) ) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } - - $dir = explode( '/', plugin_basename( __FILE__ ) )[0]; - $file = array_keys( get_plugins( "/$dir" ) )[0]; - return "$dir/$file"; -} - -/** - * Find the latest installed autoloader and set up the classmap and filemap. + * Finds the latest installed autoloader. If this is the latest autoloader, sets + * up the classmap and filemap. */ function set_up_autoloader() { - global $latest_autoloader_version; + global $jetpack_autoloader_latest_version; global $jetpack_packages_classmap; - if ( ! is_current_plugin_active() ) { - // The current plugin is activating, so reset the autoloader. - $latest_autoloader_version = null; - $jetpack_packages_classmap = array(); - } + require_once __DIR__ . '/class-plugins-handler.php'; + require_once __DIR__ . '/class-autoloader-handler.php'; - $classmap_file = trailingslashit( dirname( __FILE__ ) ) . 'composer/jetpack_autoload_classmap.php'; - $autoloader_packages = require $classmap_file; + $plugins_handler = new Plugins_Handler(); + $autoloader_handler = new Autoloader_Handler( $plugins_handler ); - $current_autoloader_version = $autoloader_packages['Automattic\\Jetpack\\Autoloader\\AutoloadGenerator']['version']; - $current_autoloader_path = trailingslashit( dirname( __FILE__ ) ) . 'autoload_packages.php'; + if ( $plugins_handler->should_autoloader_reset() ) { + /* + * The autoloader must be reset when an activating plugin that was + * previously unknown is detected. + */ + $jetpack_autoloader_latest_version = null; + $jetpack_packages_classmap = array(); + } // Find the latest autoloader. - if ( ! $latest_autoloader_version ) { - $autoloader_version = $current_autoloader_version; - $autoloader_path = $current_autoloader_path; - $current_plugin = get_current_plugin(); - - $active_plugins = get_active_plugins(); - - foreach ( $active_plugins as $plugin ) { - if ( $current_plugin === $plugin ) { - continue; - } - - $plugin_path = plugin_dir_path( trailingslashit( WP_PLUGIN_DIR ) . $plugin ); - $classmap_path = trailingslashit( $plugin_path ) . 'vendor/composer/jetpack_autoload_classmap.php'; - if ( file_exists( $classmap_path ) ) { - $packages = require $classmap_path; - - $current_version = $packages['Automattic\\Jetpack\\Autoloader\\AutoloadGenerator']['version']; - - // TODO: This comparison needs to properly handle dev versions. - if ( version_compare( $autoloader_version, $current_version, '<' ) ) { - $autoloader_version = $current_version; - $autoloader_path = trailingslashit( $plugin_path ) . 'vendor/autoload_packages.php'; - } - } - } - - $latest_autoloader_version = $autoloader_version; - if ( $current_autoloader_path !== $autoloader_path ) { - require $autoloader_path; - } + if ( ! $jetpack_autoloader_latest_version ) { + $autoloader_handler->find_latest_autoloader(); } - // This is the latest autoloader, so generate the classmap and filemap and register the autoloader function. - if ( empty( $jetpack_packages_classmap ) && $current_autoloader_version === $latest_autoloader_version ) { - enqueue_files(); - - spl_autoload_register( __NAMESPACE__ . '\autoloader' ); + $current_autoloader_version = $autoloader_handler->get_current_autoloader_version(); - $autoload_chain = spl_autoload_functions(); - if ( in_array( 'Automattic\Jetpack\Autoloader\autoloader', $autoload_chain, true ) ) { - // Move the old autoloader function to the end of the spl autoloader chaain. - spl_autoload_unregister( 'Automattic\Jetpack\Autoloader\autoloader' ); - spl_autoload_register( 'Automattic\Jetpack\Autoloader\autoloader' ); - } + // This is the latest autoloader, so generate the classmap and filemap and register the autoloader function. + if ( empty( $jetpack_packages_classmap ) && $current_autoloader_version === $jetpack_autoloader_latest_version ) { + enqueue_files( $plugins_handler ); + $autoloader_handler->update_autoloader_chain(); } } diff --git a/packages/autoloader/tests/php/bootstrap.php b/packages/autoloader/tests/php/bootstrap.php index 8b7cdb15b5d76..15c2aa5dc260d 100644 --- a/packages/autoloader/tests/php/bootstrap.php +++ b/packages/autoloader/tests/php/bootstrap.php @@ -23,3 +23,6 @@ function trailingslashit( $string ) { */ require_once __DIR__ . '/../../vendor/autoload.php'; require_once __DIR__ . '/../../src/functions.php'; +require_once __DIR__ . '/../../src/class-plugins-handler.php'; +require_once __DIR__ . '/../../src/class-classes-handler.php'; +require_once __DIR__ . '/../../src/class-files-handler.php'; diff --git a/packages/autoloader/tests/php/test_Autoloader.php b/packages/autoloader/tests/php/test_Autoloader.php index 464a5a66fa72c..d62e4318cf987 100644 --- a/packages/autoloader/tests/php/test_Autoloader.php +++ b/packages/autoloader/tests/php/test_Autoloader.php @@ -18,7 +18,7 @@ class WP_Test_Autoloader extends TestCase { */ public function setup() { parent::setup(); - + $this->classes_handler = new Classes_Handler( new Plugins_Handler() ); spl_autoload_register( 'autoloader' ); } @@ -26,7 +26,7 @@ public function setup() { * Tests whether enqueueing adds a class to the global array. */ public function test_enqueueing_adds_to_the_global_array() { - enqueue_package_class( 'className', '1', 'path_to_class' ); + $this->classes_handler->enqueue_package_class( 'className', '1', 'path_to_class' ); global $jetpack_packages_classmap; $this->assertTrue( isset( $jetpack_packages_classmap['className'] ) ); @@ -38,8 +38,8 @@ public function test_enqueueing_adds_to_the_global_array() { * Tests whether enqueueing adds the latest class version to the global array. */ public function test_enqueueing_adds_the_latest_version_to_the_global_array() { - enqueue_package_class( 'className', '1', 'path_to_class' ); - enqueue_package_class( 'className', '2', 'path_to_class_v2' ); + $this->classes_handler->enqueue_package_class( 'className', '1', 'path_to_class' ); + $this->classes_handler->enqueue_package_class( 'className', '2', 'path_to_class_v2' ); global $jetpack_packages_classmap; $this->assertTrue( isset( $jetpack_packages_classmap['className'] ) ); @@ -53,9 +53,9 @@ public function test_enqueueing_adds_the_latest_version_to_the_global_array() { */ public function test_enqueueing_always_adds_the_dev_version_to_the_global_array() { - enqueue_package_class( 'className', '1', 'path_to_class' ); - enqueue_package_class( 'className', 'dev-howdy', 'path_to_class_dev' ); - enqueue_package_class( 'className', '2', 'path_to_class_v2' ); + $this->classes_handler->enqueue_package_class( 'className', '1', 'path_to_class' ); + $this->classes_handler->enqueue_package_class( 'className', 'dev-howdy', 'path_to_class_dev' ); + $this->classes_handler->enqueue_package_class( 'className', '2', 'path_to_class_v2' ); global $jetpack_packages_classmap; $this->assertTrue( isset( $jetpack_packages_classmap['className'] ) ); @@ -67,7 +67,7 @@ public function test_enqueueing_always_adds_the_dev_version_to_the_global_array( * Tests whether enqueueing works with autoloading. */ public function test_enqueue_class_to_autoload_works_as_expected() { - enqueue_package_class( 'Jetpack\TestCase_ABC\className_ABC', '1', dirname( __FILE__ ) . '/path_to_class.php' ); + $this->classes_handler->enqueue_package_class( 'Jetpack\TestCase_ABC\className_ABC', '1', dirname( __FILE__ ) . '/path_to_class.php' ); $class = new ClassName_ABC(); diff --git a/packages/autoloader/tests/php/test_file_loader.php b/packages/autoloader/tests/php/test_file_loader.php index c4a1960fa2a84..49ffeaa0db4b2 100644 --- a/packages/autoloader/tests/php/test_file_loader.php +++ b/packages/autoloader/tests/php/test_file_loader.php @@ -12,11 +12,19 @@ */ class WP_Test_File_Loader extends TestCase { + /** + * Setup runs before each test. + */ + public function setUp() { + parent::setUp(); + $this->files_handler = new Files_Handler( new Plugins_Handler() ); + } + /** * Tests whether enqueueing adds a file to the global array. */ public function test_enqueueing_adds_to_the_global_array() { - enqueue_package_file( 'file_id_10', '1', 'path_to_file.php' ); + $this->files_handler->enqueue_package_file( 'file_id_10', '1', 'path_to_file.php' ); global $jetpack_packages_filemap; $this->assertTrue( isset( $jetpack_packages_filemap['file_id_10'] ) ); @@ -28,8 +36,8 @@ public function test_enqueueing_adds_to_the_global_array() { * Tests whether enqueueing adds the latest file version to the global array. */ public function test_enqueueing_adds_the_latest_version_to_the_global_array() { - enqueue_package_file( 'file_id', '1', 'path_to_file' ); - enqueue_package_file( 'file_id', '2', 'path_to_file_v2' ); + $this->files_handler->enqueue_package_file( 'file_id', '1', 'path_to_file' ); + $this->files_handler->enqueue_package_file( 'file_id', '2', 'path_to_file_v2' ); global $jetpack_packages_filemap; $this->assertTrue( isset( $jetpack_packages_filemap['file_id'] ) ); @@ -42,9 +50,9 @@ public function test_enqueueing_adds_the_latest_version_to_the_global_array() { */ public function test_enqueueing_always_adds_the_dev_version_to_the_global_array() { - enqueue_package_file( 'file_id', '1', 'path_to_file' ); - enqueue_package_file( 'file_id', 'dev-howdy', 'path_to_file_dev' ); - enqueue_package_file( 'file_id', '2', 'path_to_file_v2' ); + $this->files_handler->enqueue_package_file( 'file_id', '1', 'path_to_file' ); + $this->files_handler->enqueue_package_file( 'file_id', 'dev-howdy', 'path_to_file_dev' ); + $this->files_handler->enqueue_package_file( 'file_id', '2', 'path_to_file_v2' ); global $jetpack_packages_filemap; $this->assertTrue( isset( $jetpack_packages_filemap['file_id'] ) ); @@ -57,15 +65,15 @@ public function test_enqueueing_always_adds_the_dev_version_to_the_global_array( */ public function test_enqueued_file_is_actually_loaded() { - enqueue_package_file( 'file_id', '1', __DIR__ . '/path_to_file.php' ); + $this->files_handler->enqueue_package_file( 'file_id', '1', __DIR__ . '/path_to_file.php' ); - file_loader(); + $this->files_handler->file_loader(); $this->assertTrue( function_exists( 'if_i_exist_then_this_test_passed' ) ); $this->assertTrue( if_i_exist_then_this_test_passed() ); - enqueue_package_file( 'file_id', '2', __DIR__ . '/bogus_path_to_file.php' ); + $this->files_handler->enqueue_package_file( 'file_id', '2', __DIR__ . '/bogus_path_to_file.php' ); - file_loader(); // file_loader should not include same file twice. + $this->files_handler->file_loader(); // file_loader should not include same file twice. $this->assertTrue( if_i_exist_then_this_test_passed() ); }