initial commit

This commit is contained in:
2021-12-10 12:03:04 +00:00
commit c46c7ddbf0
3643 changed files with 582794 additions and 0 deletions

View File

@ -0,0 +1,105 @@
<?php // phpcs:ignore WordPress.Files.FileName
/**
* Autoloader file writer.
*
* @package automattic/jetpack-autoloader
*/
// phpcs:disable WordPress.Files.FileName.InvalidClassFileName
// phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// phpcs:disable WordPress.NamingConventions.ValidVariableName.InterpolatedVariableNotSnakeCase
// phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
// phpcs:disable WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase
// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_var_export
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_fopen
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_fwrite
namespace Automattic\Jetpack\Autoloader;
/**
* Class AutoloadFileWriter.
*/
class AutoloadFileWriter {
/**
* The file comment to use.
*/
const COMMENT = <<<AUTOLOADER_COMMENT
/**
* This file was automatically generated by automattic/jetpack-autoloader.
*
* @package automattic/jetpack-autoloader
*/
AUTOLOADER_COMMENT;
/**
* Copies autoloader files and replaces any placeholders in them.
*
* @param IOInterface|null $io An IO for writing to.
* @param string $outDir The directory to place the autoloader files in.
* @param string $suffix The suffix to use in the autoloader's namespace.
*/
public static function copyAutoloaderFiles( $io, $outDir, $suffix ) {
$renameList = array(
'autoload.php' => '../autoload_packages.php',
);
$ignoreList = array(
'AutoloadGenerator.php',
'AutoloadProcessor.php',
'CustomAutoloaderPlugin.php',
'ManifestGenerator.php',
'AutoloadFileWriter.php',
);
// Copy all of the autoloader files.
$files = scandir( __DIR__ );
foreach ( $files as $file ) {
// Only PHP files will be copied.
if ( substr( $file, -4 ) !== '.php' ) {
continue;
}
if ( in_array( $file, $ignoreList, true ) ) {
continue;
}
$newFile = isset( $renameList[ $file ] ) ? $renameList[ $file ] : $file;
$content = self::prepareAutoloaderFile( $file, $suffix );
$written = file_put_contents( $outDir . '/' . $newFile, $content );
if ( $io ) {
if ( $written ) {
$io->writeError( " <info>Generated: $newFile</info>" );
} else {
$io->writeError( " <error>Error: $newFile</error>" );
}
}
}
}
/**
* Prepares an autoloader file to be written to the destination.
*
* @param String $filename a file to prepare.
* @param String $suffix Unique suffix used in the namespace.
*
* @return string
*/
private static function prepareAutoloaderFile( $filename, $suffix ) {
$header = self::COMMENT;
$header .= PHP_EOL;
$header .= 'namespace Automattic\Jetpack\Autoloader\jp' . $suffix . ';';
$header .= PHP_EOL . PHP_EOL;
$sourceLoader = fopen( __DIR__ . '/' . $filename, 'r' );
$file_contents = stream_get_contents( $sourceLoader );
return str_replace(
'/* HEADER */',
$header,
$file_contents
);
}
}

View File

@ -0,0 +1,393 @@
<?php // phpcs:ignore WordPress.Files.FileName
/**
* Autoloader Generator.
*
* @package automattic/jetpack-autoloader
*/
// phpcs:disable PHPCompatibility.Keywords.NewKeywords.t_useFound
// phpcs:disable PHPCompatibility.LanguageConstructs.NewLanguageConstructs.t_ns_separatorFound
// phpcs:disable PHPCompatibility.FunctionDeclarations.NewClosure.Found
// phpcs:disable PHPCompatibility.Keywords.NewKeywords.t_namespaceFound
// phpcs:disable PHPCompatibility.Keywords.NewKeywords.t_dirFound
// phpcs:disable WordPress.Files.FileName.InvalidClassFileName
// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_var_export
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_fopen
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_fwrite
// phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// phpcs:disable WordPress.NamingConventions.ValidVariableName.InterpolatedVariableNotSnakeCase
// phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
// phpcs:disable WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase
namespace Automattic\Jetpack\Autoloader;
use Composer\Autoload\ClassMapGenerator;
use Composer\Composer;
use Composer\Config;
use Composer\Installer\InstallationManager;
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Util\Filesystem;
use Composer\Util\PackageSorter;
/**
* Class AutoloadGenerator.
*/
class AutoloadGenerator {
/**
* The filesystem utility.
*
* @var Filesystem
*/
private $filesystem;
/**
* Instantiate an AutoloadGenerator object.
*
* @param IOInterface $io IO object.
*/
public function __construct( IOInterface $io = null ) {
$this->io = $io;
$this->filesystem = new Filesystem();
}
/**
* Dump the Jetpack autoloader files.
*
* @param Composer $composer The Composer object.
* @param Config $config Config object.
* @param InstalledRepositoryInterface $localRepo Installed Repository object.
* @param PackageInterface $mainPackage Main Package object.
* @param InstallationManager $installationManager Manager for installing packages.
* @param string $targetDir Path to the current target directory.
* @param bool $scanPsrPackages Whether or not PSR packages should be converted to a classmap.
* @param string $suffix The autoloader suffix.
*/
public function dump(
Composer $composer,
Config $config,
InstalledRepositoryInterface $localRepo,
PackageInterface $mainPackage,
InstallationManager $installationManager,
$targetDir,
$scanPsrPackages = false,
$suffix = null
) {
$this->filesystem->ensureDirectoryExists( $config->get( 'vendor-dir' ) );
$packageMap = $composer->getAutoloadGenerator()->buildPackageMap( $installationManager, $mainPackage, $localRepo->getCanonicalPackages() );
$autoloads = $this->parseAutoloads( $packageMap, $mainPackage );
// Convert the autoloads into a format that the manifest generator can consume more easily.
$basePath = $this->filesystem->normalizePath( realpath( getcwd() ) );
$vendorPath = $this->filesystem->normalizePath( realpath( $config->get( 'vendor-dir' ) ) );
$processedAutoloads = $this->processAutoloads( $autoloads, $scanPsrPackages, $vendorPath, $basePath );
unset( $packageMap, $autoloads );
// Make sure none of the legacy files remain that can lead to problems with the autoloader.
$this->removeLegacyFiles( $vendorPath );
// Write all of the files now that we're done.
$this->writeAutoloaderFiles( $vendorPath . '/jetpack-autoloader/', $suffix );
$this->writeManifests( $vendorPath . '/' . $targetDir, $processedAutoloads );
if ( ! $scanPsrPackages ) {
$this->io->writeError( '<warning>You are generating an unoptimized autoloader. If this is a production build, consider using the -o option.</warning>' );
}
}
/**
* Compiles an ordered list of namespace => path mappings
*
* @param array $packageMap Array of array(package, installDir-relative-to-composer.json).
* @param PackageInterface $mainPackage Main package instance.
*
* @return array The list of path mappings.
*/
public function parseAutoloads( array $packageMap, PackageInterface $mainPackage ) {
$rootPackageMap = array_shift( $packageMap );
$sortedPackageMap = $this->sortPackageMap( $packageMap );
$sortedPackageMap[] = $rootPackageMap;
array_unshift( $packageMap, $rootPackageMap );
$psr0 = $this->parseAutoloadsType( $packageMap, 'psr-0', $mainPackage );
$psr4 = $this->parseAutoloadsType( $packageMap, 'psr-4', $mainPackage );
$classmap = $this->parseAutoloadsType( array_reverse( $sortedPackageMap ), 'classmap', $mainPackage );
$files = $this->parseAutoloadsType( $sortedPackageMap, 'files', $mainPackage );
krsort( $psr0 );
krsort( $psr4 );
return array(
'psr-0' => $psr0,
'psr-4' => $psr4,
'classmap' => $classmap,
'files' => $files,
);
}
/**
* Sorts packages by dependency weight
*
* Packages of equal weight retain the original order
*
* @param array $packageMap The package map.
*
* @return array
*/
protected function sortPackageMap( array $packageMap ) {
$packages = array();
$paths = array();
foreach ( $packageMap as $item ) {
list( $package, $path ) = $item;
$name = $package->getName();
$packages[ $name ] = $package;
$paths[ $name ] = $path;
}
$sortedPackages = PackageSorter::sortPackages( $packages );
$sortedPackageMap = array();
foreach ( $sortedPackages as $package ) {
$name = $package->getName();
$sortedPackageMap[] = array( $packages[ $name ], $paths[ $name ] );
}
return $sortedPackageMap;
}
/**
* Returns the file identifier.
*
* @param PackageInterface $package The package instance.
* @param string $path The path.
*/
protected function getFileIdentifier( PackageInterface $package, $path ) {
return md5( $package->getName() . ':' . $path );
}
/**
* Returns the path code for the given path.
*
* @param Filesystem $filesystem The filesystem instance.
* @param string $basePath The base path.
* @param string $vendorPath The vendor path.
* @param string $path The path.
*
* @return string The path code.
*/
protected function getPathCode( Filesystem $filesystem, $basePath, $vendorPath, $path ) {
if ( ! $filesystem->isAbsolutePath( $path ) ) {
$path = $basePath . '/' . $path;
}
$path = $filesystem->normalizePath( $path );
$baseDir = '';
if ( 0 === strpos( $path . '/', $vendorPath . '/' ) ) {
$path = substr( $path, strlen( $vendorPath ) );
$baseDir = '$vendorDir';
if ( false !== $path ) {
$baseDir .= ' . ';
}
} else {
$path = $filesystem->normalizePath( $filesystem->findShortestPath( $basePath, $path, true ) );
if ( ! $filesystem->isAbsolutePath( $path ) ) {
$baseDir = '$baseDir . ';
$path = '/' . $path;
}
}
if ( strpos( $path, '.phar' ) !== false ) {
$baseDir = "'phar://' . " . $baseDir;
}
return $baseDir . ( ( false !== $path ) ? var_export( $path, true ) : '' );
}
/**
* This function differs from the composer parseAutoloadsType in that beside returning the path.
* It also return the path and the version of a package.
*
* Supports PSR-4, PSR-0, and classmap parsing.
*
* @param array $packageMap Map of all the packages.
* @param string $type Type of autoloader to use.
* @param PackageInterface $mainPackage Instance of the Package Object.
*
* @return array
*/
protected function parseAutoloadsType( array $packageMap, $type, PackageInterface $mainPackage ) {
$autoloads = array();
foreach ( $packageMap as $item ) {
list($package, $installPath) = $item;
$autoload = $package->getAutoload();
if ( $package === $mainPackage ) {
$autoload = array_merge_recursive( $autoload, $package->getDevAutoload() );
}
if ( null !== $package->getTargetDir() && $package !== $mainPackage ) {
$installPath = substr( $installPath, 0, -strlen( '/' . $package->getTargetDir() ) );
}
if ( in_array( $type, array( 'psr-4', 'psr-0' ), true ) && isset( $autoload[ $type ] ) && is_array( $autoload[ $type ] ) ) {
foreach ( $autoload[ $type ] as $namespace => $paths ) {
$paths = is_array( $paths ) ? $paths : array( $paths );
foreach ( $paths as $path ) {
$relativePath = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path;
$autoloads[ $namespace ][] = array(
'path' => $relativePath,
'version' => $package->getVersion(), // Version of the class comes from the package - should we try to parse it?
);
}
}
}
if ( 'classmap' === $type && isset( $autoload['classmap'] ) && is_array( $autoload['classmap'] ) ) {
foreach ( $autoload['classmap'] as $paths ) {
$paths = is_array( $paths ) ? $paths : array( $paths );
foreach ( $paths as $path ) {
$relativePath = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path;
$autoloads[] = array(
'path' => $relativePath,
'version' => $package->getVersion(), // Version of the class comes from the package - should we try to parse it?
);
}
}
}
if ( 'files' === $type && isset( $autoload['files'] ) && is_array( $autoload['files'] ) ) {
foreach ( $autoload['files'] as $paths ) {
$paths = is_array( $paths ) ? $paths : array( $paths );
foreach ( $paths as $path ) {
$relativePath = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path;
$autoloads[ $this->getFileIdentifier( $package, $path ) ] = array(
'path' => $relativePath,
'version' => $package->getVersion(), // Version of the file comes from the package - should we try to parse it?
);
}
}
}
}
return $autoloads;
}
/**
* Given Composer's autoloads this will convert them to a version that we can use to generate the manifests.
*
* When the $scanPsrPackages argument is true, PSR-4 namespaces are converted to classmaps. When $scanPsrPackages
* is false, PSR-4 namespaces are not converted to classmaps.
*
* PSR-0 namespaces are always converted to classmaps.
*
* @param array $autoloads The autoloads we want to process.
* @param bool $scanPsrPackages Whether or not PSR-4 packages should be converted to a classmap.
* @param string $vendorPath The path to the vendor directory.
* @param string $basePath The path to the current directory.
*
* @return array $processedAutoloads
*/
private function processAutoloads( $autoloads, $scanPsrPackages, $vendorPath, $basePath ) {
$processor = new AutoloadProcessor(
function ( $path, $excludedClasses, $namespace ) use ( $basePath ) {
$dir = $this->filesystem->normalizePath(
$this->filesystem->isAbsolutePath( $path ) ? $path : $basePath . '/' . $path
);
return ClassMapGenerator::createMap(
$dir,
$excludedClasses,
null, // Don't pass the IOInterface since the normal autoload generation will have reported already.
empty( $namespace ) ? null : $namespace
);
},
function ( $path ) use ( $basePath, $vendorPath ) {
return $this->getPathCode( $this->filesystem, $basePath, $vendorPath, $path );
}
);
return array(
'psr-4' => $processor->processPsr4Packages( $autoloads, $scanPsrPackages ),
'classmap' => $processor->processClassmap( $autoloads, $scanPsrPackages ),
'files' => $processor->processFiles( $autoloads ),
);
}
/**
* Removes all of the legacy autoloader files so they don't cause any problems.
*
* @param string $outDir The directory legacy files are written to.
*/
private function removeLegacyFiles( $outDir ) {
$files = array(
'autoload_functions.php',
'class-autoloader-handler.php',
'class-classes-handler.php',
'class-files-handler.php',
'class-plugins-handler.php',
'class-version-selector.php',
);
foreach ( $files as $file ) {
$this->filesystem->remove( $outDir . '/' . $file );
}
}
/**
* Writes all of the autoloader files to disk.
*
* @param string $outDir The directory to write to.
* @param string $suffix The unique autoloader suffix.
*/
private function writeAutoloaderFiles( $outDir, $suffix ) {
$this->io->writeError( "<info>Generating jetpack autoloader ($outDir)</info>" );
// We will remove all autoloader files to generate this again.
$this->filesystem->emptyDirectory( $outDir );
// Write the autoloader files.
AutoloadFileWriter::copyAutoloaderFiles( $this->io, $outDir, $suffix );
}
/**
* Writes all of the manifest files to disk.
*
* @param string $outDir The directory to write to.
* @param array $processedAutoloads The processed autoloads.
*/
private function writeManifests( $outDir, $processedAutoloads ) {
$this->io->writeError( "<info>Generating jetpack autoloader manifests ($outDir)</info>" );
$manifestFiles = array(
'classmap' => 'jetpack_autoload_classmap.php',
'psr-4' => 'jetpack_autoload_psr4.php',
'files' => 'jetpack_autoload_filemap.php',
);
foreach ( $manifestFiles as $key => $file ) {
// Make sure the file doesn't exist so it isn't there if we don't write it.
$this->filesystem->remove( $outDir . '/' . $file );
if ( empty( $processedAutoloads[ $key ] ) ) {
continue;
}
$content = ManifestGenerator::buildManifest( $key, $file, $processedAutoloads[ $key ] );
if ( empty( $content ) ) {
continue;
}
if ( file_put_contents( $outDir . '/' . $file, $content ) ) {
$this->io->writeError( " <info>Generated: $file</info>" );
} else {
$this->io->writeError( " <error>Error: $file</error>" );
}
}
}
}

View File

@ -0,0 +1,180 @@
<?php // phpcs:ignore WordPress.Files.FileName
/**
* Autoload Processor.
*
* @package automattic/jetpack-autoloader
*/
// phpcs:disable WordPress.Files.FileName.InvalidClassFileName
// phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// phpcs:disable WordPress.NamingConventions.ValidVariableName.InterpolatedVariableNotSnakeCase
// phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
// phpcs:disable WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase
namespace Automattic\Jetpack\Autoloader;
/**
* Class AutoloadProcessor.
*/
class AutoloadProcessor {
/**
* A callable for scanning a directory for all of its classes.
*
* @var callable
*/
private $classmapScanner;
/**
* A callable for transforming a path into one to be used in code.
*
* @var callable
*/
private $pathCodeTransformer;
/**
* The constructor.
*
* @param callable $classmapScanner A callable for scanning a directory for all of its classes.
* @param callable $pathCodeTransformer A callable for transforming a path into one to be used in code.
*/
public function __construct( $classmapScanner, $pathCodeTransformer ) {
$this->classmapScanner = $classmapScanner;
$this->pathCodeTransformer = $pathCodeTransformer;
}
/**
* Processes the classmap autoloads into a relative path format including the version for each file.
*
* @param array $autoloads The autoloads we are processing.
* @param bool $scanPsrPackages Whether or not PSR packages should be converted to a classmap.
*
* @return array $processed
*/
public function processClassmap( $autoloads, $scanPsrPackages ) {
// We can't scan PSR packages if we don't actually have any.
if ( empty( $autoloads['psr-4'] ) ) {
$scanPsrPackages = false;
}
if ( empty( $autoloads['classmap'] ) && ! $scanPsrPackages ) {
return null;
}
$excludedClasses = null;
if ( ! empty( $autoloads['exclude-from-classmap'] ) ) {
$excludedClasses = '{(' . implode( '|', $autoloads['exclude-from-classmap'] ) . ')}';
}
$processed = array();
if ( $scanPsrPackages ) {
foreach ( $autoloads['psr-4'] as $namespace => $sources ) {
$namespace = empty( $namespace ) ? null : $namespace;
foreach ( $sources as $source ) {
$classmap = call_user_func( $this->classmapScanner, $source['path'], $excludedClasses, $namespace );
foreach ( $classmap as $class => $path ) {
$processed[ $class ] = array(
'version' => $source['version'],
'path' => call_user_func( $this->pathCodeTransformer, $path ),
);
}
}
}
}
/*
* PSR-0 namespaces are converted to classmaps for both optimized and unoptimized autoloaders because any new
* development should use classmap or PSR-4 autoloading.
*/
if ( ! empty( $autoloads['psr-0'] ) ) {
foreach ( $autoloads['psr-0'] as $namespace => $sources ) {
$namespace = empty( $namespace ) ? null : $namespace;
foreach ( $sources as $source ) {
$classmap = call_user_func( $this->classmapScanner, $source['path'], $excludedClasses, $namespace );
foreach ( $classmap as $class => $path ) {
$processed[ $class ] = array(
'version' => $source['version'],
'path' => call_user_func( $this->pathCodeTransformer, $path ),
);
}
}
}
}
if ( ! empty( $autoloads['classmap'] ) ) {
foreach ( $autoloads['classmap'] as $package ) {
$classmap = call_user_func( $this->classmapScanner, $package['path'], $excludedClasses, null );
foreach ( $classmap as $class => $path ) {
$processed[ $class ] = array(
'version' => $package['version'],
'path' => call_user_func( $this->pathCodeTransformer, $path ),
);
}
}
}
return $processed;
}
/**
* Processes the PSR-4 autoloads into a relative path format including the version for each file.
*
* @param array $autoloads The autoloads we are processing.
* @param bool $scanPsrPackages Whether or not PSR packages should be converted to a classmap.
*
* @return array $processed
*/
public function processPsr4Packages( $autoloads, $scanPsrPackages ) {
if ( $scanPsrPackages || empty( $autoloads['psr-4'] ) ) {
return null;
}
$processed = array();
foreach ( $autoloads['psr-4'] as $namespace => $packages ) {
$namespace = empty( $namespace ) ? null : $namespace;
$paths = array();
foreach ( $packages as $package ) {
$paths[] = call_user_func( $this->pathCodeTransformer, $package['path'] );
}
$processed[ $namespace ] = array(
'version' => $package['version'],
'path' => $paths,
);
}
return $processed;
}
/**
* Processes the file autoloads into a relative format including the version for each file.
*
* @param array $autoloads The autoloads we are processing.
*
* @return array|null $processed
*/
public function processFiles( $autoloads ) {
if ( empty( $autoloads['files'] ) ) {
return null;
}
$processed = array();
foreach ( $autoloads['files'] as $file_id => $package ) {
$processed[ $file_id ] = array(
'version' => $package['version'],
'path' => call_user_func( $this->pathCodeTransformer, $package['path'] ),
);
}
return $processed;
}
}

View File

@ -0,0 +1,198 @@
<?php //phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase
/**
* Custom Autoloader Composer Plugin, hooks into composer events to generate the custom autoloader.
*
* @package automattic/jetpack-autoloader
*/
// phpcs:disable PHPCompatibility.Keywords.NewKeywords.t_useFound
// phpcs:disable PHPCompatibility.LanguageConstructs.NewLanguageConstructs.t_ns_separatorFound
// phpcs:disable PHPCompatibility.Keywords.NewKeywords.t_namespaceFound
// phpcs:disable WordPress.Files.FileName.NotHyphenatedLowercase
// phpcs:disable WordPress.Files.FileName.InvalidClassFileName
// phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
namespace Automattic\Jetpack\Autoloader;
use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
use Composer\Script\Event;
use Composer\Script\ScriptEvents;
/**
* Class CustomAutoloaderPlugin.
*
* @package automattic/jetpack-autoloader
*/
class CustomAutoloaderPlugin implements PluginInterface, EventSubscriberInterface {
/**
* IO object.
*
* @var IOInterface IO object.
*/
private $io;
/**
* Composer object.
*
* @var Composer Composer object.
*/
private $composer;
/**
* Do nothing.
*
* @param Composer $composer Composer object.
* @param IOInterface $io IO object.
*/
public function activate( Composer $composer, IOInterface $io ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$this->composer = $composer;
$this->io = $io;
}
/**
* Do nothing.
* phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
*
* @param Composer $composer Composer object.
* @param IOInterface $io IO object.
*/
public function deactivate( Composer $composer, IOInterface $io ) {
/*
* Intentionally left empty. This is a PluginInterface method.
* phpcs:enable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
*/
}
/**
* Do nothing.
* phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
*
* @param Composer $composer Composer object.
* @param IOInterface $io IO object.
*/
public function uninstall( Composer $composer, IOInterface $io ) {
/*
* Intentionally left empty. This is a PluginInterface method.
* phpcs:enable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
*/
}
/**
* Tell composer to listen for events and do something with them.
*
* @return array List of subscribed events.
*/
public static function getSubscribedEvents() {
return array(
ScriptEvents::POST_AUTOLOAD_DUMP => 'postAutoloadDump',
);
}
/**
* Generate the custom autolaoder.
*
* @param Event $event Script event object.
*/
public function postAutoloadDump( Event $event ) {
// When the autoloader is not required by the root package we don't want to execute it.
// This prevents unwanted transitive execution that generates unused autoloaders or
// at worst throws fatal executions.
if ( ! $this->isRequiredByRoot() ) {
return;
}
$config = $this->composer->getConfig();
if ( 'vendor' !== $config->raw()['config']['vendor-dir'] ) {
$this->io->writeError( "\n<error>An error occurred while generating the autoloader files:", true );
$this->io->writeError( 'The project\'s composer.json or composer environment set a non-default vendor directory.', true );
$this->io->writeError( 'The default composer vendor directory must be used.</error>', true );
exit();
}
$installationManager = $this->composer->getInstallationManager();
$repoManager = $this->composer->getRepositoryManager();
$localRepo = $repoManager->getLocalRepository();
$package = $this->composer->getPackage();
$optimize = $event->getFlags()['optimize'];
$suffix = $this->determineSuffix();
$generator = new AutoloadGenerator( $this->io );
$generator->dump( $this->composer, $config, $localRepo, $package, $installationManager, 'composer', $optimize, $suffix );
$this->generated = true;
}
/**
* Determine the suffix for the autoloader class.
*
* Reuses an existing suffix from vendor/autoload_packages.php or vendor/autoload.php if possible.
*
* @return string Suffix.
*/
private function determineSuffix() {
$config = $this->composer->getConfig();
$vendorPath = $config->get( 'vendor-dir' );
// Command line.
$suffix = $config->get( 'autoloader-suffix' );
if ( $suffix ) {
return $suffix;
}
// Reuse our own suffix, if any.
if ( is_readable( $vendorPath . '/autoload_packages.php' ) ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
$content = file_get_contents( $vendorPath . '/autoload_packages.php' );
if ( preg_match( '/^namespace Automattic\\\\Jetpack\\\\Autoloader\\\\jp([^;\s]+);/m', $content, $match ) ) {
return $match[1];
}
}
// Reuse Composer's suffix, if any.
if ( is_readable( $vendorPath . '/autoload.php' ) ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
$content = file_get_contents( $vendorPath . '/autoload.php' );
if ( preg_match( '{ComposerAutoloaderInit([^:\s]+)::}', $content, $match ) ) {
return $match[1];
}
}
// Generate a random suffix.
return md5( uniqid( '', true ) );
}
/**
* Checks to see whether or not the root package is the one that required the autoloader.
*
* @return bool
*/
private function isRequiredByRoot() {
$package = $this->composer->getPackage();
$requires = $package->getRequires();
if ( ! is_array( $requires ) ) {
$requires = array();
}
$devRequires = $package->getDevRequires();
if ( ! is_array( $devRequires ) ) {
$devRequires = array();
}
$requires = array_merge( $requires, $devRequires );
if ( empty( $requires ) ) {
$this->io->writeError( "\n<error>The package is not required and this should never happen?</error>", true );
exit();
}
foreach ( $requires as $require ) {
if ( 'automattic/jetpack-autoloader' === $require->getTarget() ) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,121 @@
<?php // phpcs:ignore WordPress.Files.FileName
/**
* Manifest Generator.
*
* @package automattic/jetpack-autoloader
*/
// phpcs:disable WordPress.Files.FileName.InvalidClassFileName
// phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// phpcs:disable WordPress.NamingConventions.ValidVariableName.InterpolatedVariableNotSnakeCase
// phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
// phpcs:disable WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase
// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_var_export
namespace Automattic\Jetpack\Autoloader;
/**
* Class ManifestGenerator.
*/
class ManifestGenerator {
/**
* Builds a manifest file for the given autoloader type.
*
* @param string $autoloaderType The type of autoloader to build a manifest for.
* @param string $fileName The filename of the manifest.
* @param array $content The manifest content to generate using.
*
* @return string|null $manifestFile
* @throws \InvalidArgumentException When an invalid autoloader type is given.
*/
public static function buildManifest( $autoloaderType, $fileName, $content ) {
if ( empty( $content ) ) {
return null;
}
switch ( $autoloaderType ) {
case 'classmap':
case 'files':
return self::buildStandardManifest( $fileName, $content );
case 'psr-4':
return self::buildPsr4Manifest( $fileName, $content );
}
throw new \InvalidArgumentException( 'An invalid manifest type of ' . $autoloaderType . ' was passed!' );
}
/**
* Builds the contents for the standard manifest file.
*
* @param string $fileName The filename we are building.
* @param array $manifestData The formatted data for the manifest.
*
* @return string|null $manifestFile
*/
private static function buildStandardManifest( $fileName, $manifestData ) {
$fileContent = PHP_EOL;
foreach ( $manifestData as $key => $data ) {
$key = var_export( $key, true );
$versionCode = var_export( $data['version'], true );
$fileContent .= <<<MANIFEST_CODE
$key => array(
'version' => $versionCode,
'path' => {$data['path']}
),
MANIFEST_CODE;
$fileContent .= PHP_EOL;
}
return self::buildFile( $fileName, $fileContent );
}
/**
* Builds the contents for the PSR-4 manifest file.
*
* @param string $fileName The filename we are building.
* @param array $namespaces The formatted PSR-4 data for the manifest.
*
* @return string|null $manifestFile
*/
private static function buildPsr4Manifest( $fileName, $namespaces ) {
$fileContent = PHP_EOL;
foreach ( $namespaces as $namespace => $data ) {
$namespaceCode = var_export( $namespace, true );
$versionCode = var_export( $data['version'], true );
$pathCode = 'array( ' . implode( ', ', $data['path'] ) . ' )';
$fileContent .= <<<MANIFEST_CODE
$namespaceCode => array(
'version' => $versionCode,
'path' => $pathCode
),
MANIFEST_CODE;
$fileContent .= PHP_EOL;
}
return self::buildFile( $fileName, $fileContent );
}
/**
* Generate the PHP that will be used in the file.
*
* @param string $fileName The filename we are building.
* @param string $content The content to be written into the file.
*
* @return string $fileContent
*/
private static function buildFile( $fileName, $content ) {
return <<<INCLUDE_FILE
<?php
// This file `$fileName` was auto generated by automattic/jetpack-autoloader.
\$vendorDir = dirname(__DIR__);
\$baseDir = dirname(\$vendorDir);
return array($content);
INCLUDE_FILE;
}
}

View File

@ -0,0 +1,5 @@
<?php
/* HEADER */ // phpcs:ignore
require_once __DIR__ . '/jetpack-autoloader/class-autoloader.php';
Autoloader::init();

View File

@ -0,0 +1,139 @@
<?php
/* HEADER */ // phpcs:ignore
use Automattic\Jetpack\Autoloader\AutoloadGenerator;
/**
* This class selects the package version for the autoloader.
*/
class Autoloader_Handler {
/**
* The PHP_Autoloader instance.
*
* @var PHP_Autoloader
*/
private $php_autoloader;
/**
* The Hook_Manager instance.
*
* @var Hook_Manager
*/
private $hook_manager;
/**
* The Manifest_Reader instance.
*
* @var Manifest_Reader
*/
private $manifest_reader;
/**
* The Version_Selector instance.
*
* @var Version_Selector
*/
private $version_selector;
/**
* The constructor.
*
* @param PHP_Autoloader $php_autoloader The PHP_Autoloader instance.
* @param Hook_Manager $hook_manager The Hook_Manager instance.
* @param Manifest_Reader $manifest_reader The Manifest_Reader instance.
* @param Version_Selector $version_selector The Version_Selector instance.
*/
public function __construct( $php_autoloader, $hook_manager, $manifest_reader, $version_selector ) {
$this->php_autoloader = $php_autoloader;
$this->hook_manager = $hook_manager;
$this->manifest_reader = $manifest_reader;
$this->version_selector = $version_selector;
}
/**
* Checks to see whether or not an autoloader is currently in the process of initializing.
*
* @return bool
*/
public function is_initializing() {
// If no version has been set it means that no autoloader has started initializing yet.
global $jetpack_autoloader_latest_version;
if ( ! isset( $jetpack_autoloader_latest_version ) ) {
return false;
}
// When the version is set but the classmap is not it ALWAYS means that this is the
// latest autoloader and is being included by an older one.
global $jetpack_packages_classmap;
if ( empty( $jetpack_packages_classmap ) ) {
return true;
}
// Version 2.4.0 added a new global and altered the reset semantics. We need to check
// the other global as well since it may also point at initialization.
// Note: We don't need to check for the class first because every autoloader that
// will set the latest version global requires this class in the classmap.
$replacing_version = $jetpack_packages_classmap[ AutoloadGenerator::class ]['version'];
if ( $this->version_selector->is_dev_version( $replacing_version ) || version_compare( $replacing_version, '2.4.0.0', '>=' ) ) {
global $jetpack_autoloader_loader;
if ( ! isset( $jetpack_autoloader_loader ) ) {
return true;
}
}
return false;
}
/**
* Activates an autoloader using the given plugins and activates it.
*
* @param string[] $plugins The plugins to initialize the autoloader for.
*/
public function activate_autoloader( $plugins ) {
global $jetpack_packages_psr4;
$jetpack_packages_psr4 = array();
$this->manifest_reader->read_manifests( $plugins, 'vendor/composer/jetpack_autoload_psr4.php', $jetpack_packages_psr4 );
global $jetpack_packages_classmap;
$jetpack_packages_classmap = array();
$this->manifest_reader->read_manifests( $plugins, 'vendor/composer/jetpack_autoload_classmap.php', $jetpack_packages_classmap );
global $jetpack_packages_filemap;
$jetpack_packages_filemap = array();
$this->manifest_reader->read_manifests( $plugins, 'vendor/composer/jetpack_autoload_filemap.php', $jetpack_packages_filemap );
$loader = new Version_Loader(
$this->version_selector,
$jetpack_packages_classmap,
$jetpack_packages_psr4,
$jetpack_packages_filemap
);
$this->php_autoloader->register_autoloader( $loader );
// Now that the autoloader is active we can load the filemap.
$loader->load_filemap();
}
/**
* Resets the active autoloader and all related global state.
*/
public function reset_autoloader() {
$this->php_autoloader->unregister_autoloader();
$this->hook_manager->reset();
// Clear all of the autoloader globals so that older autoloaders don't do anything strange.
global $jetpack_autoloader_latest_version;
$jetpack_autoloader_latest_version = null;
global $jetpack_packages_classmap;
$jetpack_packages_classmap = array(); // Must be array to avoid exceptions in old autoloaders!
global $jetpack_packages_psr4;
$jetpack_packages_psr4 = array(); // Must be array to avoid exceptions in old autoloaders!
global $jetpack_packages_filemap;
$jetpack_packages_filemap = array(); // Must be array to avoid exceptions in old autoloaders!
}
}

View File

@ -0,0 +1,82 @@
<?php
/* HEADER */ // phpcs:ignore
use Automattic\Jetpack\Autoloader\AutoloadGenerator;
/**
* This class locates autoloaders.
*/
class Autoloader_Locator {
/**
* The object for comparing autoloader versions.
*
* @var Version_Selector
*/
private $version_selector;
/**
* The constructor.
*
* @param Version_Selector $version_selector The version selector object.
*/
public function __construct( $version_selector ) {
$this->version_selector = $version_selector;
}
/**
* Finds the path to the plugin with the latest autoloader.
*
* @param array $plugin_paths An array of plugin paths.
* @param string $latest_version The latest version reference.
*
* @return string|null
*/
public function find_latest_autoloader( $plugin_paths, &$latest_version ) {
$latest_plugin = null;
foreach ( $plugin_paths as $plugin_path ) {
$version = $this->get_autoloader_version( $plugin_path );
if ( ! $this->version_selector->is_version_update_required( $latest_version, $version ) ) {
continue;
}
$latest_version = $version;
$latest_plugin = $plugin_path;
}
return $latest_plugin;
}
/**
* Gets the path to the autoloader.
*
* @param string $plugin_path The path to the plugin.
*
* @return string
*/
public function get_autoloader_path( $plugin_path ) {
return trailingslashit( $plugin_path ) . 'vendor/autoload_packages.php';
}
/**
* Gets the version for the autoloader.
*
* @param string $plugin_path The path to the plugin.
*
* @return string|null
*/
public function get_autoloader_version( $plugin_path ) {
$classmap = trailingslashit( $plugin_path ) . 'vendor/composer/jetpack_autoload_classmap.php';
if ( ! file_exists( $classmap ) ) {
return null;
}
$classmap = require $classmap;
if ( isset( $classmap[ AutoloadGenerator::class ] ) ) {
return $classmap[ AutoloadGenerator::class ]['version'];
}
return null;
}
}

View File

@ -0,0 +1,82 @@
<?php
/* HEADER */ // phpcs:ignore
/**
* This class handles management of the actual PHP autoloader.
*/
class Autoloader {
/**
* Checks to see whether or not the autoloader should be initialized and then initializes it if so.
*
* @param Container|null $container The container we want to use for autoloader initialization. If none is given
* then a container will be created automatically.
*/
public static function init( $container = null ) {
// The container holds and manages the lifecycle of our dependencies
// to make them easier to work with and increase flexibility.
if ( ! isset( $container ) ) {
require_once __DIR__ . '/class-container.php';
$container = new Container();
}
// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @var Autoloader_Handler $autoloader_handler */
$autoloader_handler = $container->get( Autoloader_Handler::class );
// If the autoloader is already initializing it means that it has included us as the latest.
$was_included_by_autoloader = $autoloader_handler->is_initializing();
/** @var Plugin_Locator $plugin_locator */
$plugin_locator = $container->get( Plugin_Locator::class );
/** @var Plugins_Handler $plugins_handler */
$plugins_handler = $container->get( Plugins_Handler::class );
// The current plugin is the one that we are attempting to initialize here.
$current_plugin = $plugin_locator->find_current_plugin();
// The active plugins are those that we were able to discover on the site. This list will not
// include mu-plugins, those activated by code, or those who are hidden by filtering. We also
// want to take care to not consider the current plugin unknown if it was included by an
// autoloader. This avoids the case where a plugin will be marked "active" while deactivated
// due to it having the latest autoloader.
$active_plugins = $plugins_handler->get_active_plugins( true, ! $was_included_by_autoloader );
// The cached plugins are all of those that were active or discovered by the autoloader during a previous request.
// Note that it's possible this list will include plugins that have since been deactivated, but after a request
// the cache should be updated and the deactivated plugins will be removed.
$cached_plugins = $plugins_handler->get_cached_plugins();
// We combine the active list and cached list to preemptively load classes for plugins that are
// presently unknown but will be loaded during the request. While this may result in us considering packages in
// deactivated plugins there shouldn't be any problems as a result and the eventual consistency is sufficient.
$all_plugins = array_merge( $active_plugins, $cached_plugins );
// In particular we also include the current plugin to address the case where it is the latest autoloader
// but also unknown (and not cached). We don't want it in the active list because we don't know that it
// is active but we need it in the all plugins list so that it is considered by the autoloader.
$all_plugins[] = $current_plugin;
// We require uniqueness in the array to avoid processing the same plugin more than once.
$all_plugins = array_values( array_unique( $all_plugins ) );
/** @var Latest_Autoloader_Guard $guard */
$guard = $container->get( Latest_Autoloader_Guard::class );
if ( $guard->should_stop_init( $current_plugin, $all_plugins, $was_included_by_autoloader ) ) {
return;
}
// Initialize the autoloader using the handler now that we're ready.
$autoloader_handler->activate_autoloader( $all_plugins );
/** @var Hook_Manager $hook_manager */
$hook_manager = $container->get( Hook_Manager::class );
// Register a shutdown handler to clean up the autoloader.
$hook_manager->add_action( 'shutdown', new Shutdown_Handler( $plugins_handler, $cached_plugins, $was_included_by_autoloader ) );
// phpcs:enable Generic.Commenting.DocComment.MissingShort
}
}

View File

@ -0,0 +1,142 @@
<?php
/* HEADER */ // phpcs:ignore
/**
* This class manages the files and dependencies of the autoloader.
*/
class Container {
/**
* Since each autoloader's class files exist within their own namespace we need a map to
* convert between the local class and a shared key. Note that no version checking is
* performed on these dependencies and the first autoloader to register will be the
* one that is utilized.
*/
const SHARED_DEPENDENCY_KEYS = array(
Hook_Manager::class => 'Hook_Manager',
);
/**
* A map of all the dependencies we've registered with the container and created.
*
* @var array
*/
protected $dependencies;
/**
* The constructor.
*/
public function __construct() {
$this->dependencies = array();
$this->register_shared_dependencies();
$this->register_dependencies();
$this->initialize_globals();
}
/**
* Gets a dependency out of the container.
*
* @param string $class The class to fetch.
*
* @return mixed
* @throws \InvalidArgumentException When a class that isn't registered with the container is fetched.
*/
public function get( $class ) {
if ( ! isset( $this->dependencies[ $class ] ) ) {
throw new \InvalidArgumentException( "Class '$class' is not registered with the container." );
}
return $this->dependencies[ $class ];
}
/**
* Registers all of the dependencies that are shared between all instances of the autoloader.
*/
private function register_shared_dependencies() {
global $jetpack_autoloader_container_shared;
if ( ! isset( $jetpack_autoloader_container_shared ) ) {
$jetpack_autoloader_container_shared = array();
}
$key = self::SHARED_DEPENDENCY_KEYS[ Hook_Manager::class ];
if ( ! isset( $jetpack_autoloader_container_shared[ $key ] ) ) {
require_once __DIR__ . '/class-hook-manager.php';
$jetpack_autoloader_container_shared[ $key ] = new Hook_Manager();
}
$this->dependencies[ Hook_Manager::class ] = &$jetpack_autoloader_container_shared[ $key ];
}
/**
* Registers all of the dependencies with the container.
*/
private function register_dependencies() {
require_once __DIR__ . '/class-path-processor.php';
$this->dependencies[ Path_Processor::class ] = new Path_Processor();
require_once __DIR__ . '/class-plugin-locator.php';
$this->dependencies[ Plugin_Locator::class ] = new Plugin_Locator(
$this->get( Path_Processor::class )
);
require_once __DIR__ . '/class-version-selector.php';
$this->dependencies[ Version_Selector::class ] = new Version_Selector();
require_once __DIR__ . '/class-autoloader-locator.php';
$this->dependencies[ Autoloader_Locator::class ] = new Autoloader_Locator(
$this->get( Version_Selector::class )
);
require_once __DIR__ . '/class-php-autoloader.php';
$this->dependencies[ PHP_Autoloader::class ] = new PHP_Autoloader();
require_once __DIR__ . '/class-manifest-reader.php';
$this->dependencies[ Manifest_Reader::class ] = new Manifest_Reader(
$this->get( Version_Selector::class )
);
require_once __DIR__ . '/class-plugins-handler.php';
$this->dependencies[ Plugins_Handler::class ] = new Plugins_Handler(
$this->get( Plugin_Locator::class ),
$this->get( Path_Processor::class )
);
require_once __DIR__ . '/class-autoloader-handler.php';
$this->dependencies[ Autoloader_Handler::class ] = new Autoloader_Handler(
$this->get( PHP_Autoloader::class ),
$this->get( Hook_Manager::class ),
$this->get( Manifest_Reader::class ),
$this->get( Version_Selector::class )
);
require_once __DIR__ . '/class-latest-autoloader-guard.php';
$this->dependencies[ Latest_Autoloader_Guard::class ] = new Latest_Autoloader_Guard(
$this->get( Plugins_Handler::class ),
$this->get( Autoloader_Handler::class ),
$this->get( Autoloader_Locator::class )
);
// Register any classes that we will use elsewhere.
require_once __DIR__ . '/class-version-loader.php';
require_once __DIR__ . '/class-shutdown-handler.php';
}
/**
* Initializes any of the globals needed by the autoloader.
*/
private function initialize_globals() {
/*
* This global was retired in version 2.9. The value is set to 'false' to maintain
* compatibility with older versions of the autoloader.
*/
global $jetpack_autoloader_including_latest;
$jetpack_autoloader_including_latest = false;
// Not all plugins can be found using the locator. In cases where a plugin loads the autoloader
// but was not discoverable, we will record them in this array to track them as "active".
global $jetpack_autoloader_activating_plugins_paths;
if ( ! isset( $jetpack_autoloader_activating_plugins_paths ) ) {
$jetpack_autoloader_activating_plugins_paths = array();
}
}
}

View File

@ -0,0 +1,68 @@
<?php
/* HEADER */ // phpcs:ignore
/**
* Allows the latest autoloader to register hooks that can be removed when the autoloader is reset.
*/
class Hook_Manager {
/**
* An array containing all of the hooks that we've registered.
*
* @var array
*/
private $registered_hooks;
/**
* The constructor.
*/
public function __construct() {
$this->registered_hooks = array();
}
/**
* Adds an action to WordPress and registers it internally.
*
* @param string $tag The name of the action which is hooked.
* @param callable $callable The function to call.
* @param int $priority Used to specify the priority of the action.
* @param int $accepted_args Used to specify the number of arguments the callable accepts.
*/
public function add_action( $tag, $callable, $priority = 10, $accepted_args = 1 ) {
$this->registered_hooks[ $tag ][] = array(
'priority' => $priority,
'callable' => $callable,
);
add_action( $tag, $callable, $priority, $accepted_args );
}
/**
* Adds a filter to WordPress and registers it internally.
*
* @param string $tag The name of the filter which is hooked.
* @param callable $callable The function to call.
* @param int $priority Used to specify the priority of the filter.
* @param int $accepted_args Used to specify the number of arguments the callable accepts.
*/
public function add_filter( $tag, $callable, $priority = 10, $accepted_args = 1 ) {
$this->registered_hooks[ $tag ][] = array(
'priority' => $priority,
'callable' => $callable,
);
add_filter( $tag, $callable, $priority, $accepted_args );
}
/**
* Removes all of the registered hooks.
*/
public function reset() {
foreach ( $this->registered_hooks as $tag => $hooks ) {
foreach ( $hooks as $hook ) {
remove_filter( $tag, $hook['callable'], $hook['priority'] );
}
}
$this->registered_hooks = array();
}
}

View File

@ -0,0 +1,78 @@
<?php
/* HEADER */ // phpcs:ignore
/**
* This class ensures that we're only executing the latest autoloader.
*/
class Latest_Autoloader_Guard {
/**
* The Plugins_Handler instance.
*
* @var Plugins_Handler
*/
private $plugins_handler;
/**
* The Autoloader_Handler instance.
*
* @var Autoloader_Handler
*/
private $autoloader_handler;
/**
* The Autoloader_locator instance.
*
* @var Autoloader_Locator
*/
private $autoloader_locator;
/**
* The constructor.
*
* @param Plugins_Handler $plugins_handler The Plugins_Handler instance.
* @param Autoloader_Handler $autoloader_handler The Autoloader_Handler instance.
* @param Autoloader_Locator $autoloader_locator The Autoloader_Locator instance.
*/
public function __construct( $plugins_handler, $autoloader_handler, $autoloader_locator ) {
$this->plugins_handler = $plugins_handler;
$this->autoloader_handler = $autoloader_handler;
$this->autoloader_locator = $autoloader_locator;
}
/**
* Indicates whether or not the autoloader should be initialized. Note that this function
* has the side-effect of actually loading the latest autoloader in the event that this
* is not it.
*
* @param string $current_plugin The current plugin we're checking.
* @param string[] $plugins The active plugins to check for autoloaders in.
* @param bool $was_included_by_autoloader Indicates whether or not this autoloader was included by another.
*
* @return bool True if we should stop initialization, otherwise false.
*/
public function should_stop_init( $current_plugin, $plugins, $was_included_by_autoloader ) {
global $jetpack_autoloader_latest_version;
// We need to reset the autoloader when the plugins change because
// that means the autoloader was generated with a different list.
if ( $this->plugins_handler->have_plugins_changed( $plugins ) ) {
$this->autoloader_handler->reset_autoloader();
}
// When the latest autoloader has already been found we don't need to search for it again.
// We should take care however because this will also trigger if the autoloader has been
// included by an older one.
if ( isset( $jetpack_autoloader_latest_version ) && ! $was_included_by_autoloader ) {
return true;
}
$latest_plugin = $this->autoloader_locator->find_latest_autoloader( $plugins, $jetpack_autoloader_latest_version );
if ( isset( $latest_plugin ) && $latest_plugin !== $current_plugin ) {
require $this->autoloader_locator->get_autoloader_path( $latest_plugin );
return true;
}
return false;
}
}

View File

@ -0,0 +1,91 @@
<?php
/* HEADER */ // phpcs:ignore
/**
* This class reads autoloader manifest files.
*/
class Manifest_Reader {
/**
* The Version_Selector object.
*
* @var Version_Selector
*/
private $version_selector;
/**
* The constructor.
*
* @param Version_Selector $version_selector The Version_Selector object.
*/
public function __construct( $version_selector ) {
$this->version_selector = $version_selector;
}
/**
* Reads all of the manifests in the given plugin paths.
*
* @param array $plugin_paths The paths to the plugins we're loading the manifest in.
* @param string $manifest_path The path that we're loading the manifest from in each plugin.
* @param array $path_map The path map to add the contents of the manifests to.
*
* @return array $path_map The path map we've built using the manifests in each plugin.
*/
public function read_manifests( $plugin_paths, $manifest_path, &$path_map ) {
$file_paths = array_map(
function ( $path ) use ( $manifest_path ) {
return trailingslashit( $path ) . $manifest_path;
},
$plugin_paths
);
foreach ( $file_paths as $path ) {
$this->register_manifest( $path, $path_map );
}
return $path_map;
}
/**
* Registers a plugin's manifest file with the path map.
*
* @param string $manifest_path The absolute path to the manifest that we're loading.
* @param array $path_map The path map to add the contents of the manifest to.
*/
protected function register_manifest( $manifest_path, &$path_map ) {
if ( ! is_readable( $manifest_path ) ) {
return;
}
$manifest = require $manifest_path;
if ( ! is_array( $manifest ) ) {
return;
}
foreach ( $manifest as $key => $data ) {
$this->register_record( $key, $data, $path_map );
}
}
/**
* Registers an entry from the manifest in the path map.
*
* @param string $key The identifier for the entry we're registering.
* @param array $data The data for the entry we're registering.
* @param array $path_map The path map to add the contents of the manifest to.
*/
protected function register_record( $key, $data, &$path_map ) {
if ( isset( $path_map[ $key ]['version'] ) ) {
$selected_version = $path_map[ $key ]['version'];
} else {
$selected_version = null;
}
if ( $this->version_selector->is_version_update_required( $selected_version, $data['version'] ) ) {
$path_map[ $key ] = array(
'version' => $data['version'],
'path' => $data['path'],
);
}
}
}

View File

@ -0,0 +1,186 @@
<?php
/* HEADER */ // phpcs:ignore
/**
* This class handles dealing with paths for the autoloader.
*/
class Path_Processor {
/**
* Given a path this will replace any of the path constants with a token to represent it.
*
* @param string $path The path we want to process.
*
* @return string The tokenized path.
*/
public function tokenize_path_constants( $path ) {
$path = wp_normalize_path( $path );
$constants = $this->get_normalized_constants();
foreach ( $constants as $constant => $constant_path ) {
$len = strlen( $constant_path );
if ( substr( $path, 0, $len ) !== $constant_path ) {
continue;
}
return substr_replace( $path, '{{' . $constant . '}}', 0, $len );
}
return $path;
}
/**
* Given a path this will replace any of the path constant tokens with the expanded path.
*
* @param string $tokenized_path The path we want to process.
*
* @return string The expanded path.
*/
public function untokenize_path_constants( $tokenized_path ) {
$tokenized_path = wp_normalize_path( $tokenized_path );
$constants = $this->get_normalized_constants();
foreach ( $constants as $constant => $constant_path ) {
$constant = '{{' . $constant . '}}';
$len = strlen( $constant );
if ( substr( $tokenized_path, 0, $len ) !== $constant ) {
continue;
}
return $this->get_real_path( substr_replace( $tokenized_path, $constant_path, 0, $len ) );
}
return $tokenized_path;
}
/**
* Given a file and an array of places it might be, this will find the absolute path and return it.
*
* @param string $file The plugin or theme file to resolve.
* @param array $directories_to_check The directories we should check for the file if it isn't an absolute path.
*
* @return string|false Returns the absolute path to the directory, otherwise false.
*/
public function find_directory_with_autoloader( $file, $directories_to_check ) {
$file = wp_normalize_path( $file );
if ( ! $this->is_absolute_path( $file ) ) {
$file = $this->find_absolute_plugin_path( $file, $directories_to_check );
if ( ! isset( $file ) ) {
return false;
}
}
// We need the real path for consistency with __DIR__ paths.
$file = $this->get_real_path( $file );
// phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
$directory = @is_file( $file ) ? dirname( $file ) : $file;
if ( ! @is_file( $directory . '/vendor/composer/jetpack_autoload_classmap.php' ) ) {
return false;
}
// phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged
return $directory;
}
/**
* Fetches an array of normalized paths keyed by the constant they came from.
*
* @return string[] The normalized paths keyed by the constant.
*/
private function get_normalized_constants() {
$raw_constants = array(
// Order the constants from most-specific to least-specific.
'WP_PLUGIN_DIR',
'WPMU_PLUGIN_DIR',
'WP_CONTENT_DIR',
'ABSPATH',
);
$constants = array();
foreach ( $raw_constants as $raw ) {
if ( ! defined( $raw ) ) {
continue;
}
$path = wp_normalize_path( constant( $raw ) );
if ( isset( $path ) ) {
$constants[ $raw ] = $path;
}
}
return $constants;
}
/**
* Indicates whether or not a path is absolute.
*
* @param string $path The path to check.
*
* @return bool True if the path is absolute, otherwise false.
*/
private function is_absolute_path( $path ) {
if ( 0 === strlen( $path ) || '.' === $path[0] ) {
return false;
}
// Absolute paths on Windows may begin with a drive letter.
if ( preg_match( '/^[a-zA-Z]:[\/\\\\]/', $path ) ) {
return true;
}
// A path starting with / or \ is absolute; anything else is relative.
return ( '/' === $path[0] || '\\' === $path[0] );
}
/**
* Given a file and a list of directories to check, this method will try to figure out
* the absolute path to the file in question.
*
* @param string $normalized_path The normalized path to the plugin or theme file to resolve.
* @param array $directories_to_check The directories we should check for the file if it isn't an absolute path.
*
* @return string|null The absolute path to the plugin file, otherwise null.
*/
private function find_absolute_plugin_path( $normalized_path, $directories_to_check ) {
// We're only able to find the absolute path for plugin/theme PHP files.
if ( ! is_string( $normalized_path ) || '.php' !== substr( $normalized_path, -4 ) ) {
return null;
}
foreach ( $directories_to_check as $directory ) {
$normalized_check = wp_normalize_path( trailingslashit( $directory ) ) . $normalized_path;
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
if ( @is_file( $normalized_check ) ) {
return $normalized_check;
}
}
return null;
}
/**
* Given a path this will figure out the real path that we should be using.
*
* @param string $path The path to resolve.
*
* @return string The resolved path.
*/
private function get_real_path( $path ) {
// We want to resolve symbolic links for consistency with __DIR__ paths.
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
$real_path = @realpath( $path );
if ( false === $real_path ) {
// Let the autoloader deal with paths that don't exist.
$real_path = $path;
}
// Using realpath will make it platform-specific so we must normalize it after.
if ( $path !== $real_path ) {
$real_path = wp_normalize_path( $real_path );
}
return $real_path;
}
}

View File

@ -0,0 +1,82 @@
<?php
/* HEADER */ // phpcs:ignore
/**
* This class handles management of the actual PHP autoloader.
*/
class PHP_Autoloader {
/**
* Registers the autoloader with PHP so that it can begin autoloading classes.
*
* @param Version_Loader $version_loader The class loader to use in the autoloader.
*/
public function register_autoloader( $version_loader ) {
// Make sure no other autoloaders are registered.
$this->unregister_autoloader();
// Set the global so that it can be used to load classes.
global $jetpack_autoloader_loader;
$jetpack_autoloader_loader = $version_loader;
// Ensure that the autoloader is first to avoid contention with others.
spl_autoload_register( array( self::class, 'load_class' ), true, true );
}
/**
* Unregisters the active autoloader so that it will no longer autoload classes.
*/
public function unregister_autoloader() {
// Remove any v2 autoloader that we've already registered.
$autoload_chain = spl_autoload_functions();
foreach ( $autoload_chain as $autoloader ) {
// We can identify a v2 autoloader using the namespace.
$namespace_check = null;
// Functions are recorded as strings.
if ( is_string( $autoloader ) ) {
$namespace_check = $autoloader;
} elseif ( is_array( $autoloader ) && is_string( $autoloader[0] ) ) {
// Static method calls have the class as the first array element.
$namespace_check = $autoloader[0];
} else {
// Since the autoloader has only ever been a function or a static method we don't currently need to check anything else.
continue;
}
// Check for the namespace without the generated suffix.
if ( 'Automattic\\Jetpack\\Autoloader\\jp' === substr( $namespace_check, 0, 32 ) ) {
spl_autoload_unregister( $autoloader );
}
}
// Clear the global now that the autoloader has been unregistered.
global $jetpack_autoloader_loader;
$jetpack_autoloader_loader = null;
}
/**
* Loads a class file if one could be found.
*
* Note: This function is static so that the autoloader can be easily unregistered. If
* it was a class method we would have to unwrap the object to check the namespace.
*
* @param string $class_name The name of the class to autoload.
*
* @return bool Indicates whether or not a class file was loaded.
*/
public static function load_class( $class_name ) {
global $jetpack_autoloader_loader;
if ( ! isset( $jetpack_autoloader_loader ) ) {
return;
}
$file = $jetpack_autoloader_loader->find_class_file( $class_name );
if ( ! isset( $file ) ) {
return false;
}
require $file;
return true;
}
}

View File

@ -0,0 +1,145 @@
<?php
/* HEADER */ // phpcs:ignore
/**
* This class scans the WordPress installation to find active plugins.
*/
class Plugin_Locator {
/**
* The path processor for finding plugin paths.
*
* @var Path_Processor
*/
private $path_processor;
/**
* The constructor.
*
* @param Path_Processor $path_processor The Path_Processor instance.
*/
public function __construct( $path_processor ) {
$this->path_processor = $path_processor;
}
/**
* Finds the path to the current plugin.
*
* @return string $path The path to the current plugin.
*
* @throws \RuntimeException If the current plugin does not have an autoloader.
*/
public function find_current_plugin() {
// Escape from `vendor/__DIR__` to root plugin directory.
$plugin_directory = dirname( dirname( __DIR__ ) );
// Use the path processor to ensure that this is an autoloader we're referencing.
$path = $this->path_processor->find_directory_with_autoloader( $plugin_directory, array() );
if ( false === $path ) {
throw new \RuntimeException( 'Failed to locate plugin ' . $plugin_directory );
}
return $path;
}
/**
* Checks a given option for plugin paths.
*
* @param string $option_name The option that we want to check for plugin information.
* @param bool $site_option Indicates whether or not we want to check the site option.
*
* @return array $plugin_paths The list of absolute paths we've found.
*/
public function find_using_option( $option_name, $site_option = false ) {
$raw = $site_option ? get_site_option( $option_name ) : get_option( $option_name );
if ( false === $raw ) {
return array();
}
return $this->convert_plugins_to_paths( $raw );
}
/**
* Checks for plugins in the `action` request parameter.
*
* @param string[] $allowed_actions The actions that we're allowed to return plugins for.
*
* @return array $plugin_paths The list of absolute paths we've found.
*/
public function find_using_request_action( $allowed_actions ) {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
/**
* Note: we're not actually checking the nonce here because 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.
*/
if ( empty( $_REQUEST['_wpnonce'] ) ) {
return array();
}
$action = isset( $_REQUEST['action'] ) ? wp_unslash( $_REQUEST['action'] ) : false;
if ( ! in_array( $action, $allowed_actions, true ) ) {
return array();
}
$plugin_slugs = array();
switch ( $action ) {
case 'activate':
case 'deactivate':
if ( empty( $_REQUEST['plugin'] ) ) {
break;
}
$plugin_slugs[] = wp_unslash( $_REQUEST['plugin'] );
break;
case 'activate-selected':
case 'deactivate-selected':
if ( empty( $_REQUEST['checked'] ) ) {
break;
}
$plugin_slugs = wp_unslash( $_REQUEST['checked'] );
break;
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
return $this->convert_plugins_to_paths( $plugin_slugs );
}
/**
* Given an array of plugin slugs or paths, this will convert them to absolute paths and filter
* out the plugins that are not directory plugins. Note that array keys will also be included
* if they are plugin paths!
*
* @param string[] $plugins Plugin paths or slugs to filter.
*
* @return string[]
*/
private function convert_plugins_to_paths( $plugins ) {
if ( ! is_array( $plugins ) || empty( $plugins ) ) {
return array();
}
// We're going to look for plugins in the standard directories.
$path_constants = array( WP_PLUGIN_DIR, WPMU_PLUGIN_DIR );
$plugin_paths = array();
foreach ( $plugins as $key => $value ) {
$path = $this->path_processor->find_directory_with_autoloader( $key, $path_constants );
if ( $path ) {
$plugin_paths[] = $path;
}
$path = $this->path_processor->find_directory_with_autoloader( $value, $path_constants );
if ( $path ) {
$plugin_paths[] = $path;
}
}
return $plugin_paths;
}
}

View File

@ -0,0 +1,156 @@
<?php
/* HEADER */ // phpcs:ignore
/**
* This class handles locating and caching all of the active plugins.
*/
class Plugins_Handler {
/**
* The transient key for plugin paths.
*/
const TRANSIENT_KEY = 'jetpack_autoloader_plugin_paths';
/**
* The locator for finding plugins in different locations.
*
* @var Plugin_Locator
*/
private $plugin_locator;
/**
* The processor for transforming cached paths.
*
* @var Path_Processor
*/
private $path_processor;
/**
* The constructor.
*
* @param Plugin_Locator $plugin_locator The locator for finding active plugins.
* @param Path_Processor $path_processor The processor for transforming cached paths.
*/
public function __construct( $plugin_locator, $path_processor ) {
$this->plugin_locator = $plugin_locator;
$this->path_processor = $path_processor;
}
/**
* Gets all of the active plugins we can find.
*
* @param bool $include_deactivating When true, plugins deactivating this request will be considered active.
* @param bool $record_unknown When true, the current plugin will be marked as active and recorded when unknown.
*
* @return string[]
*/
public function get_active_plugins( $include_deactivating, $record_unknown ) {
global $jetpack_autoloader_activating_plugins_paths;
// We're going to build a unique list of plugins from a few different sources
// to find all of our "active" plugins. While we need to return an integer
// array, we're going to use an associative array internally to reduce
// the amount of time that we're going to spend checking uniqueness
// and merging different arrays together to form the output.
$active_plugins = array();
// Make sure that plugins which have activated this request are considered as "active" even though
// they probably won't be present in any option.
if ( is_array( $jetpack_autoloader_activating_plugins_paths ) ) {
foreach ( $jetpack_autoloader_activating_plugins_paths as $path ) {
$active_plugins[ $path ] = $path;
}
}
// This option contains all of the plugins that have been activated.
$plugins = $this->plugin_locator->find_using_option( 'active_plugins' );
foreach ( $plugins as $path ) {
$active_plugins[ $path ] = $path;
}
// This option contains all of the multisite plugins that have been activated.
if ( is_multisite() ) {
$plugins = $this->plugin_locator->find_using_option( 'active_sitewide_plugins', true );
foreach ( $plugins as $path ) {
$active_plugins[ $path ] = $path;
}
}
// These actions contain plugins that are being activated/deactivated during this request.
$plugins = $this->plugin_locator->find_using_request_action( array( 'activate', 'activate-selected', 'deactivate', 'deactivate-selected' ) );
foreach ( $plugins as $path ) {
$active_plugins[ $path ] = $path;
}
// When the current plugin isn't considered "active" there's a problem.
// Since we're here, the plugin is active and currently being loaded.
// We can support this case (mu-plugins and non-standard activation)
// by adding the current plugin to the active list and marking it
// as an unknown (activating) plugin. This also has the benefit
// of causing a reset because the active plugins list has
// been changed since it was saved in the global.
$current_plugin = $this->plugin_locator->find_current_plugin();
if ( $record_unknown && ! in_array( $current_plugin, $active_plugins, true ) ) {
$active_plugins[ $current_plugin ] = $current_plugin;
$jetpack_autoloader_activating_plugins_paths[] = $current_plugin;
}
// When deactivating plugins aren't desired we should entirely remove them from the active list.
if ( ! $include_deactivating ) {
// These actions contain plugins that are being deactivated during this request.
$plugins = $this->plugin_locator->find_using_request_action( array( 'deactivate', 'deactivate-selected' ) );
foreach ( $plugins as $path ) {
unset( $active_plugins[ $path ] );
}
}
// Transform the array so that we don't have to worry about the keys interacting with other array types later.
return array_values( $active_plugins );
}
/**
* Gets all of the cached plugins if there are any.
*
* @return string[]
*/
public function get_cached_plugins() {
$cached = get_transient( self::TRANSIENT_KEY );
if ( ! is_array( $cached ) || empty( $cached ) ) {
return array();
}
// We need to expand the tokens to an absolute path for this webserver.
return array_map( array( $this->path_processor, 'untokenize_path_constants' ), $cached );
}
/**
* Saves the plugin list to the cache.
*
* @param array $plugins The plugin list to save to the cache.
*/
public function cache_plugins( $plugins ) {
// We store the paths in a tokenized form so that that webservers with different absolute paths don't break.
$plugins = array_map( array( $this->path_processor, 'tokenize_path_constants' ), $plugins );
set_transient( self::TRANSIENT_KEY, $plugins );
}
/**
* Checks to see whether or not the plugin list given has changed when compared to the
* shared `$jetpack_autoloader_cached_plugin_paths` global. This allows us to deal
* with cases where the active list may change due to filtering..
*
* @param string[] $plugins The plugins list to check against the global cache.
*
* @return bool True if the plugins have changed, otherwise false.
*/
public function have_plugins_changed( $plugins ) {
global $jetpack_autoloader_cached_plugin_paths;
if ( $jetpack_autoloader_cached_plugin_paths !== $plugins ) {
$jetpack_autoloader_cached_plugin_paths = $plugins;
return true;
}
return false;
}
}

View File

@ -0,0 +1,84 @@
<?php
/* HEADER */ // phpcs:ignore
/**
* This class handles the shutdown of the autoloader.
*/
class Shutdown_Handler {
/**
* The Plugins_Handler instance.
*
* @var Plugins_Handler
*/
private $plugins_handler;
/**
* The plugins cached by this autoloader.
*
* @var string[]
*/
private $cached_plugins;
/**
* Indicates whether or not this autoloader was included by another.
*
* @var bool
*/
private $was_included_by_autoloader;
/**
* Constructor.
*
* @param Plugins_Handler $plugins_handler The Plugins_Handler instance to use.
* @param string[] $cached_plugins The plugins cached by the autoloaer.
* @param bool $was_included_by_autoloader Indicates whether or not the autoloader was included by another.
*/
public function __construct( $plugins_handler, $cached_plugins, $was_included_by_autoloader ) {
$this->plugins_handler = $plugins_handler;
$this->cached_plugins = $cached_plugins;
$this->was_included_by_autoloader = $was_included_by_autoloader;
}
/**
* Handles the shutdown of the autoloader.
*/
public function __invoke() {
// Don't save a broken cache if an error happens during some plugin's initialization.
if ( ! did_action( 'plugins_loaded' ) ) {
// Ensure that the cache is emptied to prevent consecutive failures if the cache is to blame.
if ( ! empty( $this->cached_plugins ) ) {
$this->plugins_handler->cache_plugins( array() );
}
return;
}
// Load the active plugins fresh since the list we pulled earlier might not contain
// plugins that were activated but did not reset the autoloader. This happens
// when a plugin is in the cache but not "active" when the autoloader loads.
// We also want to make sure that plugins which are deactivating are not
// considered "active" so that they will be removed from the cache now.
try {
$active_plugins = $this->plugins_handler->get_active_plugins( false, ! $this->was_included_by_autoloader );
} catch ( \Exception $ex ) {
// When the package is deleted before shutdown it will throw an exception.
// In the event this happens we should erase the cache.
if ( ! empty( $this->cached_plugins ) ) {
$this->plugins_handler->cache_plugins( array() );
}
return;
}
// The paths should be sorted for easy comparisons with those loaded from the cache.
// Note we don't need to sort the cached entries because they're already sorted.
sort( $active_plugins );
// We don't want to waste time saving a cache that hasn't changed.
if ( $this->cached_plugins === $active_plugins ) {
return;
}
$this->plugins_handler->cache_plugins( $active_plugins );
}
}

View File

@ -0,0 +1,156 @@
<?php
/* HEADER */ // phpcs:ignore
/**
* This class loads other classes based on given parameters.
*/
class Version_Loader {
/**
* The Version_Selector object.
*
* @var Version_Selector
*/
private $version_selector;
/**
* A map of available classes and their version and file path.
*
* @var array
*/
private $classmap;
/**
* A map of PSR-4 namespaces and their version and directory path.
*
* @var array
*/
private $psr4_map;
/**
* A map of all the files that we should load.
*
* @var array
*/
private $filemap;
/**
* The constructor.
*
* @param Version_Selector $version_selector The Version_Selector object.
* @param array $classmap The verioned classmap to load using.
* @param array $psr4_map The versioned PSR-4 map to load using.
* @param array $filemap The versioned filemap to load.
*/
public function __construct( $version_selector, $classmap, $psr4_map, $filemap ) {
$this->version_selector = $version_selector;
$this->classmap = $classmap;
$this->psr4_map = $psr4_map;
$this->filemap = $filemap;
}
/**
* Finds the file path for the given class.
*
* @param string $class_name The class to find.
*
* @return string|null $file_path The path to the file if found, null if no class was found.
*/
public function find_class_file( $class_name ) {
$data = $this->select_newest_file(
isset( $this->classmap[ $class_name ] ) ? $this->classmap[ $class_name ] : null,
$this->find_psr4_file( $class_name )
);
if ( ! isset( $data ) ) {
return null;
}
return $data['path'];
}
/**
* Load all of the files in the filemap.
*/
public function load_filemap() {
if ( empty( $this->filemap ) ) {
return;
}
foreach ( $this->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;
}
}
}
/**
* Compares different class sources and returns the newest.
*
* @param array|null $classmap_data The classmap class data.
* @param array|null $psr4_data The PSR-4 class data.
*
* @return array|null $data
*/
private function select_newest_file( $classmap_data, $psr4_data ) {
if ( ! isset( $classmap_data ) ) {
return $psr4_data;
} elseif ( ! isset( $psr4_data ) ) {
return $classmap_data;
}
if ( $this->version_selector->is_version_update_required( $classmap_data['version'], $psr4_data['version'] ) ) {
return $psr4_data;
}
return $classmap_data;
}
/**
* Finds the file for a given class in a PSR-4 namespace.
*
* @param string $class_name The class to find.
*
* @return array|null $data The version and path path to the file if found, null otherwise.
*/
private function find_psr4_file( $class_name ) {
if ( ! isset( $this->psr4_map ) ) {
return null;
}
// Don't bother with classes that have no namespace.
$class_index = strrpos( $class_name, '\\' );
if ( ! $class_index ) {
return null;
}
$class_for_path = str_replace( '\\', '/', $class_name );
// Search for the namespace by iteratively cutting off the last segment until
// we find a match. This allows us to check the most-specific namespaces
// first as well as minimize the amount of time spent looking.
for (
$class_namespace = substr( $class_name, 0, $class_index );
! empty( $class_namespace );
$class_namespace = substr( $class_namespace, 0, strrpos( $class_namespace, '\\' ) )
) {
$namespace = $class_namespace . '\\';
if ( ! isset( $this->psr4_map[ $namespace ] ) ) {
continue;
}
$data = $this->psr4_map[ $namespace ];
foreach ( $data['path'] as $path ) {
$path .= '/' . substr( $class_for_path, strlen( $namespace ) ) . '.php';
if ( file_exists( $path ) ) {
return array(
'version' => $data['version'],
'path' => $path,
);
}
}
}
return null;
}
}

View File

@ -0,0 +1,61 @@
<?php
/* HEADER */ // phpcs:ignore
/**
* Used to select package versions.
*/
class Version_Selector {
/**
* Checks whether the selected package version should be updated. Composer development
* package versions ('9999999-dev' or versions that start with 'dev-') are favored
* when the JETPACK_AUTOLOAD_DEV constant is set to true.
*
* @param String $selected_version The currently selected package version.
* @param String $compare_version The package version that is being evaluated to
* determine if the version needs to be updated.
*
* @return bool Returns true if the selected package version should be updated,
* else false.
*/
public function is_version_update_required( $selected_version, $compare_version ) {
$use_dev_versions = defined( 'JETPACK_AUTOLOAD_DEV' ) && JETPACK_AUTOLOAD_DEV;
if ( is_null( $selected_version ) ) {
return true;
}
if ( $use_dev_versions && $this->is_dev_version( $selected_version ) ) {
return false;
}
if ( $this->is_dev_version( $compare_version ) ) {
if ( $use_dev_versions ) {
return true;
} else {
return false;
}
}
if ( version_compare( $selected_version, $compare_version, '<' ) ) {
return true;
}
return false;
}
/**
* Checks whether the given package version is a development version.
*
* @param String $version The package version.
*
* @return bool True if the version is a dev version, else false.
*/
public function is_dev_version( $version ) {
if ( 'dev-' === substr( $version, 0, 4 ) || '9999999-dev' === $version ) {
return true;
}
return false;
}
}

View File

@ -0,0 +1,124 @@
<?php
/**
* A constants manager for Jetpack.
*
* @package automattic/jetpack-constants
*/
namespace Automattic\Jetpack;
/**
* Class Automattic\Jetpack\Constants
*
* Testing constants is hard. Once you define a constant, it's defined. Constants Manager is an
* abstraction layer so that unit tests can set "constants" for tests.
*
* To test your code, you'll need to swap out `defined( 'CONSTANT' )` with `Automattic\Jetpack\Constants::is_defined( 'CONSTANT' )`
* and replace `CONSTANT` with `Automattic\Jetpack\Constants::get_constant( 'CONSTANT' )`. Then in the unit test, you can set the
* constant with `Automattic\Jetpack\Constants::set_constant( 'CONSTANT', $value )` and then clean up after each test with something like
* this:
*
* function tearDown() {
* Automattic\Jetpack\Constants::clear_constants();
* }
*/
class Constants {
/**
* A container for all defined constants.
*
* @access public
* @static
*
* @var array.
*/
public static $set_constants = array();
/**
* Checks if a "constant" has been set in constants Manager
* and has the value of true
*
* @param string $name The name of the constant.
*
* @return bool
*/
public static function is_true( $name ) {
return self::is_defined( $name ) && self::get_constant( $name );
}
/**
* Checks if a "constant" has been set in constants Manager, and if not,
* checks if the constant was defined with define( 'name', 'value ).
*
* @param string $name The name of the constant.
*
* @return bool
*/
public static function is_defined( $name ) {
return array_key_exists( $name, self::$set_constants )
? true
: defined( $name );
}
/**
* Attempts to retrieve the "constant" from constants Manager, and if it hasn't been set,
* then attempts to get the constant with the constant() function. If that also hasn't
* been set, attempts to get a value from filters.
*
* @param string $name The name of the constant.
*
* @return mixed null if the constant does not exist or the value of the constant.
*/
public static function get_constant( $name ) {
if ( array_key_exists( $name, self::$set_constants ) ) {
return self::$set_constants[ $name ];
}
if ( defined( $name ) ) {
return constant( $name );
}
/**
* Filters the value of the constant.
*
* @since 8.5.0
*
* @param null The constant value to be filtered. The default is null.
* @param String $name The constant name.
*/
return apply_filters( 'jetpack_constant_default_value', null, $name );
}
/**
* Sets the value of the "constant" within constants Manager.
*
* @param string $name The name of the constant.
* @param string $value The value of the constant.
*/
public static function set_constant( $name, $value ) {
self::$set_constants[ $name ] = $value;
}
/**
* Will unset a "constant" from constants Manager if the constant exists.
*
* @param string $name The name of the constant.
*
* @return bool Whether the constant was removed.
*/
public static function clear_single_constant( $name ) {
if ( ! array_key_exists( $name, self::$set_constants ) ) {
return false;
}
unset( self::$set_constants[ $name ] );
return true;
}
/**
* Resets all of the constants within constants Manager.
*/
public static function clear_constants() {
self::$set_constants = array();
}
}