665 lines
16 KiB
PHP
665 lines
16 KiB
PHP
<?php
|
|
|
|
namespace Bitrix\Main;
|
|
|
|
use Bitrix\Main\Config\Configuration;
|
|
use Bitrix\Main\DI\ServiceLocator;
|
|
|
|
/**
|
|
* Class Loader loads required files, classes and modules. It is the only class which is included directly.
|
|
* @package Bitrix\Main
|
|
*/
|
|
class Loader
|
|
{
|
|
/**
|
|
* Can be used to prevent loading all modules except main and fileman
|
|
*/
|
|
const SAFE_MODE = false;
|
|
|
|
const BITRIX_HOLDER = "bitrix";
|
|
const LOCAL_HOLDER = "local";
|
|
/**
|
|
* Returned by includeSharewareModule() if module is not found
|
|
*/
|
|
const MODULE_NOT_FOUND = 0;
|
|
/**
|
|
* Returned by includeSharewareModule() if module is installed
|
|
*/
|
|
const MODULE_INSTALLED = 1;
|
|
/**
|
|
* Returned by includeSharewareModule() if module works in demo mode
|
|
*/
|
|
const MODULE_DEMO = 2;
|
|
/**
|
|
* Returned by includeSharewareModule() if the trial period is expired
|
|
*/
|
|
const MODULE_DEMO_EXPIRED = 3;
|
|
/** @deprecated */
|
|
const ALPHA_LOWER = "qwertyuioplkjhgfdsazxcvbnm";
|
|
/** @deprecated */
|
|
const ALPHA_UPPER = "QWERTYUIOPLKJHGFDSAZXCVBNM";
|
|
|
|
protected static $safeModeModules = ["main" => true, "fileman" => true];
|
|
protected static $loadedModules = ["main" => true];
|
|
protected static $semiloadedModules = [];
|
|
protected static $modulesHolders = ["main" => self::BITRIX_HOLDER];
|
|
protected static $sharewareModules = [];
|
|
/**
|
|
* Custom autoload paths.
|
|
* @var array [namespace => [ [path1, depth1], [path2, depth2] ]
|
|
*/
|
|
protected static $namespaces = [];
|
|
protected static $autoLoadClasses = [];
|
|
protected static $aliases = [];
|
|
protected static $classAliases = [];
|
|
/**
|
|
* @var bool Controls throwing exception by requireModule method
|
|
*/
|
|
protected static $requireThrowException = true;
|
|
|
|
/**
|
|
* Includes a module by its name.
|
|
*
|
|
* @param string $moduleName Name of the included module
|
|
* @return bool Returns true if module was included successfully, otherwise returns false
|
|
* @throws LoaderException
|
|
*/
|
|
public static function includeModule($moduleName)
|
|
{
|
|
if (!is_string($moduleName) || $moduleName == "")
|
|
{
|
|
throw new LoaderException("Empty module name");
|
|
}
|
|
if (!ModuleManager::isValidModule($moduleName))
|
|
{
|
|
throw new LoaderException(sprintf("Module name '%s' is not correct", $moduleName));
|
|
}
|
|
|
|
$moduleName = strtolower($moduleName);
|
|
|
|
if (self::SAFE_MODE)
|
|
{
|
|
if (!isset(self::$safeModeModules[$moduleName]))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (isset(self::$loadedModules[$moduleName]))
|
|
{
|
|
return self::$loadedModules[$moduleName];
|
|
}
|
|
|
|
if (isset(self::$semiloadedModules[$moduleName]))
|
|
{
|
|
trigger_error("Module '" . $moduleName . "' is in loading progress", E_USER_WARNING);
|
|
}
|
|
|
|
$arInstalledModules = ModuleManager::getInstalledModules();
|
|
if (!isset($arInstalledModules[$moduleName]))
|
|
{
|
|
self::$loadedModules[$moduleName] = false;
|
|
return false;
|
|
}
|
|
|
|
$documentRoot = self::getDocumentRoot();
|
|
|
|
$moduleHolder = self::LOCAL_HOLDER;
|
|
$pathToInclude = $documentRoot . "/" . $moduleHolder . "/modules/" . $moduleName;
|
|
if (!file_exists($pathToInclude))
|
|
{
|
|
$moduleHolder = self::BITRIX_HOLDER;
|
|
$pathToInclude = $documentRoot . "/" . $moduleHolder . "/modules/" . $moduleName;
|
|
if (!file_exists($pathToInclude))
|
|
{
|
|
self::$loadedModules[$moduleName] = false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//register a PSR-4 base folder for the module
|
|
if (str_contains($moduleName, "."))
|
|
{
|
|
//partner's module
|
|
$baseName = str_replace(".", "\\", ucwords($moduleName, "."));
|
|
}
|
|
else
|
|
{
|
|
//bitrix's module
|
|
$baseName = "Bitrix\\" . ucfirst($moduleName);
|
|
}
|
|
self::registerNamespace($baseName, $documentRoot . "/" . $moduleHolder . "/modules/" . $moduleName . "/lib");
|
|
|
|
self::$modulesHolders[$moduleName] = $moduleHolder;
|
|
|
|
if (class_exists('\Dev\Main\Migrator\ModuleUpdater'))
|
|
{
|
|
\Dev\Main\Migrator\ModuleUpdater::checkUpdates($moduleName, $pathToInclude);
|
|
}
|
|
|
|
$res = true;
|
|
if (file_exists($pathToInclude . "/include.php"))
|
|
{
|
|
//recursion control
|
|
self::$semiloadedModules[$moduleName] = true;
|
|
|
|
$res = self::includeModuleInternal($pathToInclude . "/include.php");
|
|
|
|
unset(self::$semiloadedModules[$moduleName]);
|
|
}
|
|
|
|
self::$loadedModules[$moduleName] = ($res !== false);
|
|
|
|
if (!self::$loadedModules[$moduleName])
|
|
{
|
|
//unregister the namespace if "include" fails
|
|
self::unregisterNamespace($baseName);
|
|
}
|
|
else
|
|
{
|
|
ServiceLocator::getInstance()->registerByModuleSettings($moduleName);
|
|
}
|
|
|
|
return self::$loadedModules[$moduleName];
|
|
}
|
|
|
|
/**
|
|
* Includes module by its name, throws an exception in case of failure
|
|
*
|
|
* @param string $moduleName
|
|
*
|
|
* @return bool
|
|
* @throws LoaderException
|
|
*/
|
|
public static function requireModule($moduleName)
|
|
{
|
|
$included = self::includeModule($moduleName);
|
|
|
|
if (!$included && self::$requireThrowException)
|
|
{
|
|
throw new LoaderException("Required module `{$moduleName}` was not found");
|
|
}
|
|
|
|
return $included;
|
|
}
|
|
|
|
private static function includeModuleInternal($path)
|
|
{
|
|
/** @noinspection PhpUnusedLocalVariableInspection */
|
|
global $DB, $MESS;
|
|
return include_once($path);
|
|
}
|
|
|
|
/**
|
|
* Includes shareware module by its name.
|
|
* Module must initialize constant <module name>_DEMO = Y in include.php to define demo mode.
|
|
* include.php must return false to define trial period expiration.
|
|
* Constants are used because it is easy to obfuscate them.
|
|
*
|
|
* @param string $moduleName Name of the included module
|
|
* @return int One of the following constant: Loader::MODULE_NOT_FOUND, Loader::MODULE_INSTALLED, Loader::MODULE_DEMO, Loader::MODULE_DEMO_EXPIRED
|
|
*/
|
|
public static function includeSharewareModule($moduleName)
|
|
{
|
|
if (isset(self::$sharewareModules[$moduleName]))
|
|
{
|
|
return self::$sharewareModules[$moduleName];
|
|
}
|
|
|
|
$module = str_replace(".", "_", $moduleName);
|
|
|
|
if (self::includeModule($moduleName))
|
|
{
|
|
if (defined($module . "_DEMO") && constant($module . "_DEMO") == "Y")
|
|
{
|
|
self::$sharewareModules[$moduleName] = self::MODULE_DEMO;
|
|
}
|
|
else
|
|
{
|
|
self::$sharewareModules[$moduleName] = self::MODULE_INSTALLED;
|
|
}
|
|
|
|
return self::$sharewareModules[$moduleName];
|
|
}
|
|
|
|
if (defined($module . "_DEMO") && constant($module . "_DEMO") == "Y")
|
|
{
|
|
return (self::$sharewareModules[$moduleName] = self::MODULE_DEMO_EXPIRED);
|
|
}
|
|
|
|
return (self::$sharewareModules[$moduleName] = self::MODULE_NOT_FOUND);
|
|
}
|
|
|
|
public static function clearModuleCache($moduleName)
|
|
{
|
|
if (!is_string($moduleName) || $moduleName == "")
|
|
{
|
|
throw new LoaderException("Empty module name");
|
|
}
|
|
|
|
if ($moduleName !== "main")
|
|
{
|
|
unset(self::$loadedModules[$moduleName]);
|
|
unset(self::$modulesHolders[$moduleName]);
|
|
}
|
|
|
|
unset(self::$sharewareModules[$moduleName]);
|
|
}
|
|
|
|
/**
|
|
* Returns document root
|
|
*
|
|
* @return string Document root
|
|
*/
|
|
public static function getDocumentRoot()
|
|
{
|
|
static $documentRoot = null;
|
|
if ($documentRoot === null)
|
|
{
|
|
$documentRoot = rtrim($_SERVER["DOCUMENT_ROOT"], "/\\");
|
|
}
|
|
return $documentRoot;
|
|
}
|
|
|
|
/**
|
|
* Registers classes for autoloading.
|
|
* All the frequently used classes should be registered for autoloading (performance).
|
|
* It is not necessary to register rarely used classes. They can be found and loaded dynamically.
|
|
*
|
|
* @param string $moduleName Name of the module. Can be null if classes are not part of any module
|
|
* @param array $classes Array of classes with class names as keys and paths as values.
|
|
* @throws LoaderException
|
|
*/
|
|
public static function registerAutoLoadClasses($moduleName, array $classes)
|
|
{
|
|
if (empty($classes))
|
|
{
|
|
return;
|
|
}
|
|
if (($moduleName !== null) && empty($moduleName))
|
|
{
|
|
throw new LoaderException(sprintf("Module name '%s' is not correct", $moduleName));
|
|
}
|
|
|
|
foreach ($classes as $class => $file)
|
|
{
|
|
$class = ltrim($class, "\\");
|
|
$class = strtolower($class);
|
|
|
|
self::$autoLoadClasses[$class] = [
|
|
"module" => $moduleName,
|
|
"file" => $file,
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registers class aliases for autoloading.
|
|
*
|
|
* @param array $aliases Array with aliases as keys and classes as values.
|
|
*/
|
|
public static function registerClassAliases(array $aliases): void
|
|
{
|
|
foreach ($aliases as $alias => $class)
|
|
{
|
|
$alias = strtolower(ltrim($alias, "\\"));
|
|
$class = strtolower(ltrim($class, "\\"));
|
|
|
|
// one class for an alias
|
|
self::$aliases[$alias] = $class;
|
|
// but many aliases for a class
|
|
self::$classAliases[$class][] = $alias;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Registers namespaces with custom paths.
|
|
* e.g. ('Bitrix\Main\Dev', '/home/bitrix/web/site/bitrix/modules/main/dev/lib')
|
|
*
|
|
* @param string $namespace A namespace prefix.
|
|
* @param string $path An absolute path.
|
|
*/
|
|
public static function registerNamespace($namespace, $path)
|
|
{
|
|
$namespace = trim($namespace, "\\") . "\\";
|
|
$namespace = strtolower($namespace);
|
|
|
|
$path = rtrim($path, "/\\");
|
|
$depth = substr_count(rtrim($namespace, "\\"), "\\");
|
|
|
|
self::$namespaces[$namespace][] = [
|
|
"path" => $path,
|
|
"depth" => $depth,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Unregisters a namespace.
|
|
* @param string $namespace
|
|
*/
|
|
public static function unregisterNamespace($namespace)
|
|
{
|
|
$namespace = trim($namespace, "\\") . "\\";
|
|
$namespace = strtolower($namespace);
|
|
|
|
unset(self::$namespaces[$namespace]);
|
|
}
|
|
|
|
/**
|
|
* Registers an additional autoload handler.
|
|
* @param callable $handler
|
|
*/
|
|
public static function registerHandler(callable $handler)
|
|
{
|
|
\spl_autoload_register($handler);
|
|
}
|
|
|
|
/**
|
|
* PSR-4 compatible autoloader.
|
|
* https://www.php-fig.org/psr/psr-4/
|
|
*
|
|
* @param string $className
|
|
*/
|
|
public static function autoLoad($className)
|
|
{
|
|
// fix web env
|
|
$className = ltrim($className, "\\");
|
|
|
|
$classLower = strtolower($className);
|
|
|
|
// dynamically define the alias for a class
|
|
if (isset(self::$aliases[$classLower]))
|
|
{
|
|
class_alias(self::$aliases[$classLower], $classLower);
|
|
|
|
return;
|
|
}
|
|
|
|
static $documentRoot = null;
|
|
if ($documentRoot === null)
|
|
{
|
|
$documentRoot = self::getDocumentRoot();
|
|
}
|
|
|
|
if (isset(self::$autoLoadClasses[$classLower]))
|
|
{
|
|
// optimization via direct paths
|
|
|
|
$pathInfo = self::$autoLoadClasses[$classLower];
|
|
if ($pathInfo["module"] != "")
|
|
{
|
|
$module = $pathInfo["module"];
|
|
$holder = (self::$modulesHolders[$module] ?? self::BITRIX_HOLDER);
|
|
|
|
$filePath = (defined('REPOSITORY_ROOT') && $holder === self::BITRIX_HOLDER)
|
|
? REPOSITORY_ROOT
|
|
: "{$documentRoot}/{$holder}/modules";
|
|
|
|
$filePath .= '/' . $module . "/" . $pathInfo["file"];
|
|
|
|
require_once($filePath);
|
|
}
|
|
else
|
|
{
|
|
require_once($documentRoot . $pathInfo["file"]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// search the path in namespaces
|
|
|
|
if (preg_match("#[^\\\\/a-zA-Z0-9_]#", $className))
|
|
{
|
|
return;
|
|
}
|
|
|
|
$tryFiles = [[
|
|
"real" => $className,
|
|
"lower" => $classLower,
|
|
]];
|
|
|
|
if (str_ends_with($classLower, "table"))
|
|
{
|
|
// old *Table stored in reserved files
|
|
$tryFiles[] = [
|
|
"real" => substr($className, 0, -5),
|
|
"lower" => substr($classLower, 0, -5),
|
|
];
|
|
}
|
|
|
|
foreach ($tryFiles as $classInfo)
|
|
{
|
|
$classParts = explode("\\", $classInfo["lower"]);
|
|
|
|
//remove class name
|
|
array_pop($classParts);
|
|
|
|
while (!empty($classParts))
|
|
{
|
|
//go from the end
|
|
$namespace = implode("\\", $classParts) . "\\";
|
|
|
|
if (isset(self::$namespaces[$namespace]))
|
|
{
|
|
//found
|
|
foreach (self::$namespaces[$namespace] as $namespaceLocation)
|
|
{
|
|
$depth = $namespaceLocation["depth"];
|
|
$path = $namespaceLocation["path"];
|
|
|
|
$fileParts = explode("\\", $classInfo["real"]);
|
|
|
|
for ($i = 0; $i <= $depth; $i++)
|
|
{
|
|
array_shift($fileParts);
|
|
}
|
|
|
|
$classPath = implode("/", $fileParts);
|
|
|
|
$classPathLower = strtolower($classPath);
|
|
|
|
// final path lower case
|
|
$filePath = $path . '/' . $classPathLower . ".php";
|
|
|
|
if (file_exists($filePath))
|
|
{
|
|
require_once($filePath);
|
|
break 3;
|
|
}
|
|
|
|
// final path original case
|
|
$filePath = $path . '/' . $classPath . ".php";
|
|
|
|
if (file_exists($filePath))
|
|
{
|
|
require_once($filePath);
|
|
break 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
//try the shorter namespace
|
|
array_pop($classParts);
|
|
}
|
|
}
|
|
}
|
|
|
|
// dynamically define the class aliases
|
|
if (isset(self::$classAliases[$classLower]))
|
|
{
|
|
foreach (self::$classAliases[$classLower] as $alias)
|
|
{
|
|
class_exists($alias);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $className
|
|
*
|
|
* @throws LoaderException
|
|
*/
|
|
public static function requireClass($className)
|
|
{
|
|
$file = ltrim($className, "\\"); // fix web env
|
|
$file = strtolower($file);
|
|
|
|
if (preg_match("#[^\\\\/a-zA-Z0-9_]#", $file))
|
|
{
|
|
return;
|
|
}
|
|
|
|
$tryFiles = [$file];
|
|
|
|
if (str_ends_with($file, "table"))
|
|
{
|
|
// old *Table stored in reserved files
|
|
$tryFiles[] = substr($file, 0, -5);
|
|
}
|
|
|
|
foreach ($tryFiles as $file)
|
|
{
|
|
$file = str_replace('\\', '/', $file);
|
|
$arFile = explode("/", $file);
|
|
|
|
if ($arFile[0] === "bitrix")
|
|
{
|
|
array_shift($arFile);
|
|
|
|
if (empty($arFile))
|
|
{
|
|
break;
|
|
}
|
|
|
|
$module = array_shift($arFile);
|
|
if ($module == null || empty($arFile))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$module1 = array_shift($arFile);
|
|
$module2 = array_shift($arFile);
|
|
|
|
if ($module1 == null || $module2 == null || empty($arFile))
|
|
{
|
|
break;
|
|
}
|
|
|
|
$module = $module1 . "." . $module2;
|
|
}
|
|
|
|
if (!self::includeModule($module))
|
|
{
|
|
throw new LoaderException(sprintf(
|
|
"There is no `%s` class, module `%s` is unavailable", $className, $module
|
|
));
|
|
}
|
|
}
|
|
|
|
self::autoLoad($className);
|
|
}
|
|
|
|
/**
|
|
* Checks if file exists in /local or /bitrix directories
|
|
*
|
|
* @param string $path File path relative to /local/ or /bitrix/
|
|
* @param string|null $root Server document root, default self::getDocumentRoot()
|
|
* @return string|bool Returns combined path or false if the file does not exist in both dirs
|
|
*/
|
|
public static function getLocal($path, $root = null)
|
|
{
|
|
if ($root === null)
|
|
{
|
|
$root = self::getDocumentRoot();
|
|
}
|
|
|
|
if (file_exists($root . "/local/" . $path))
|
|
{
|
|
return $root . "/local/" . $path;
|
|
}
|
|
elseif (file_exists($root . "/bitrix/" . $path))
|
|
{
|
|
return $root . "/bitrix/" . $path;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if file exists in personal directory.
|
|
* If $_SERVER["BX_PERSONAL_ROOT"] is not set than personal directory is equal to /bitrix/
|
|
*
|
|
* @param string $path File path relative to personal directory
|
|
* @return string|bool Returns combined path or false if the file does not exist
|
|
*/
|
|
public static function getPersonal($path)
|
|
{
|
|
$root = self::getDocumentRoot();
|
|
$personal = ($_SERVER["BX_PERSONAL_ROOT"] ?? "");
|
|
|
|
if ($personal <> '' && file_exists($root . $personal . "/" . $path))
|
|
{
|
|
return $root . $personal . "/" . $path;
|
|
}
|
|
|
|
return self::getLocal($path, $root);
|
|
}
|
|
|
|
/**
|
|
* Changes requireModule behavior
|
|
*
|
|
* @param bool $requireThrowException
|
|
*/
|
|
public static function setRequireThrowException($requireThrowException)
|
|
{
|
|
self::$requireThrowException = (bool)$requireThrowException;
|
|
}
|
|
|
|
/**
|
|
* Include autoload.php files from composer folder.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function includeComposerAutoload(): void
|
|
{
|
|
// load from config
|
|
$composerSettings = Configuration::getValue('composer');
|
|
if (empty($composerSettings['config_path']))
|
|
{
|
|
return;
|
|
}
|
|
|
|
$composerFilePath = (string)$composerSettings['config_path'];
|
|
if ($composerFilePath[0] !== '/')
|
|
{
|
|
$composerFilePath = realpath(
|
|
self::getDocumentRoot() . '/' . $composerFilePath
|
|
);
|
|
if (empty($composerFilePath))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
require_once dirname($composerFilePath) . '/vendor/autoload.php';
|
|
}
|
|
|
|
public static function getNamespaces(): array
|
|
{
|
|
return self::$namespaces;
|
|
}
|
|
}
|
|
|
|
class LoaderException extends \Exception
|
|
{
|
|
public function __construct($message = "", $code = 0, \Exception $previous = null)
|
|
{
|
|
parent::__construct($message, $code, $previous);
|
|
}
|
|
}
|