PageRenderTime 51ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/modules/main/lib/loader.php

https://gitlab.com/alexprowars/bitrix
PHP | 581 lines | 397 code | 76 blank | 108 comment | 47 complexity | 0f51638b484d4a23e3aec2b291535274 MD5 | raw file
  1. <?php
  2. namespace Bitrix\Main;
  3. use Bitrix\Main\DI\ServiceLocator;
  4. /**
  5. * Class Loader loads required files, classes and modules. It is the only class which is included directly.
  6. * @package Bitrix\Main
  7. */
  8. class Loader
  9. {
  10. /**
  11. * Can be used to prevent loading all modules except main and fileman
  12. */
  13. const SAFE_MODE = false;
  14. const BITRIX_HOLDER = "bitrix";
  15. const LOCAL_HOLDER = "local";
  16. protected static $safeModeModules = ["main" => true, "fileman" => true];
  17. protected static $loadedModules = ["main" => true];
  18. protected static $semiloadedModules = [];
  19. protected static $modulesHolders = ["main" => self::BITRIX_HOLDER];
  20. protected static $sharewareModules = [];
  21. /**
  22. * Custom autoload paths.
  23. * @var array [namespace => [ [path1, depth1], [path2, depth2] ]
  24. */
  25. protected static $namespaces = [];
  26. /**
  27. * Returned by includeSharewareModule() if module is not found
  28. */
  29. const MODULE_NOT_FOUND = 0;
  30. /**
  31. * Returned by includeSharewareModule() if module is installed
  32. */
  33. const MODULE_INSTALLED = 1;
  34. /**
  35. * Returned by includeSharewareModule() if module works in demo mode
  36. */
  37. const MODULE_DEMO = 2;
  38. /**
  39. * Returned by includeSharewareModule() if the trial period is expired
  40. */
  41. const MODULE_DEMO_EXPIRED = 3;
  42. protected static $autoLoadClasses = [];
  43. /**
  44. * @var bool Controls throwing exception by requireModule method
  45. */
  46. protected static $requireThrowException = true;
  47. /** @deprecated */
  48. const ALPHA_LOWER = "qwertyuioplkjhgfdsazxcvbnm";
  49. /** @deprecated */
  50. const ALPHA_UPPER = "QWERTYUIOPLKJHGFDSAZXCVBNM";
  51. /**
  52. * Includes a module by its name.
  53. *
  54. * @param string $moduleName Name of the included module
  55. * @return bool Returns true if module was included successfully, otherwise returns false
  56. * @throws LoaderException
  57. */
  58. public static function includeModule($moduleName)
  59. {
  60. if (!is_string($moduleName) || $moduleName == "")
  61. {
  62. throw new LoaderException("Empty module name");
  63. }
  64. if (preg_match("#[^a-zA-Z0-9._]#", $moduleName))
  65. {
  66. throw new LoaderException(sprintf("Module name '%s' is not correct", $moduleName));
  67. }
  68. $moduleName = strtolower($moduleName);
  69. if (self::SAFE_MODE)
  70. {
  71. if (!isset(self::$safeModeModules[$moduleName]))
  72. {
  73. return false;
  74. }
  75. }
  76. if (isset(self::$loadedModules[$moduleName]))
  77. {
  78. return self::$loadedModules[$moduleName];
  79. }
  80. if (isset(self::$semiloadedModules[$moduleName]))
  81. {
  82. trigger_error("Module '".$moduleName."' is in loading progress", E_USER_WARNING);
  83. }
  84. $arInstalledModules = ModuleManager::getInstalledModules();
  85. if (!isset($arInstalledModules[$moduleName]))
  86. {
  87. return (self::$loadedModules[$moduleName] = false);
  88. }
  89. $documentRoot = self::getDocumentRoot();
  90. $moduleHolder = self::LOCAL_HOLDER;
  91. $pathToInclude = $documentRoot."/".$moduleHolder."/modules/".$moduleName;
  92. if (!file_exists($pathToInclude))
  93. {
  94. $moduleHolder = self::BITRIX_HOLDER;
  95. $pathToInclude = $documentRoot."/".$moduleHolder."/modules/".$moduleName;
  96. if (!file_exists($pathToInclude))
  97. {
  98. return (self::$loadedModules[$moduleName] = false);
  99. }
  100. }
  101. //register a PSR-4 base folder for the module
  102. if(strpos($moduleName, ".") !== false)
  103. {
  104. //partner's module
  105. $baseName = str_replace(".", "\\", ucwords($moduleName, "."));
  106. }
  107. else
  108. {
  109. //bitrix's module
  110. $baseName = "Bitrix\\".ucfirst($moduleName);
  111. }
  112. self::registerNamespace($baseName, $documentRoot."/".$moduleHolder."/modules/".$moduleName."/lib");
  113. self::$modulesHolders[$moduleName] = $moduleHolder;
  114. $res = true;
  115. if(file_exists($pathToInclude."/include.php"))
  116. {
  117. //recursion control
  118. self::$semiloadedModules[$moduleName] = true;
  119. $res = self::includeModuleInternal($pathToInclude."/include.php");
  120. unset(self::$semiloadedModules[$moduleName]);
  121. }
  122. self::$loadedModules[$moduleName] = ($res !== false);
  123. if(self::$loadedModules[$moduleName] == false)
  124. {
  125. //unregister the namespace if "include" fails
  126. self::unregisterNamespace($baseName);
  127. }
  128. else
  129. {
  130. ServiceLocator::getInstance()->registerByModuleSettings($moduleName);
  131. }
  132. return self::$loadedModules[$moduleName];
  133. }
  134. /**
  135. * Includes module by its name, throws an exception in case of failure
  136. *
  137. * @param $moduleName
  138. *
  139. * @return bool
  140. * @throws LoaderException
  141. */
  142. public static function requireModule($moduleName)
  143. {
  144. $included = self::includeModule($moduleName);
  145. if (!$included && self::$requireThrowException)
  146. {
  147. throw new LoaderException("Required module `{$moduleName}` was not found");
  148. }
  149. return $included;
  150. }
  151. private static function includeModuleInternal($path)
  152. {
  153. /** @noinspection PhpUnusedLocalVariableInspection */
  154. global $DB, $MESS;
  155. return include_once($path);
  156. }
  157. /**
  158. * Includes shareware module by its name.
  159. * Module must initialize constant <module name>_DEMO = Y in include.php to define demo mode.
  160. * include.php must return false to define trial period expiration.
  161. * Constants is used because it is easy to obfuscate them.
  162. *
  163. * @param string $moduleName Name of the included module
  164. * @return int One of the following constant: Loader::MODULE_NOT_FOUND, Loader::MODULE_INSTALLED, Loader::MODULE_DEMO, Loader::MODULE_DEMO_EXPIRED
  165. */
  166. public static function includeSharewareModule($moduleName)
  167. {
  168. if (isset(self::$sharewareModules[$moduleName]))
  169. {
  170. return self::$sharewareModules[$moduleName];
  171. }
  172. $module = str_replace(".", "_", $moduleName);
  173. if (self::includeModule($moduleName))
  174. {
  175. if (defined($module."_DEMO") && constant($module."_DEMO") == "Y")
  176. {
  177. self::$sharewareModules[$moduleName] = self::MODULE_DEMO;
  178. }
  179. else
  180. {
  181. self::$sharewareModules[$moduleName] = self::MODULE_INSTALLED;
  182. }
  183. return self::$sharewareModules[$moduleName];
  184. }
  185. if (defined($module."_DEMO") && constant($module."_DEMO") == "Y")
  186. {
  187. return (self::$sharewareModules[$moduleName] = self::MODULE_DEMO_EXPIRED);
  188. }
  189. return (self::$sharewareModules[$moduleName] = self::MODULE_NOT_FOUND);
  190. }
  191. public static function clearModuleCache($moduleName)
  192. {
  193. if (!is_string($moduleName) || $moduleName == "")
  194. {
  195. throw new LoaderException("Empty module name");
  196. }
  197. if($moduleName !== "main")
  198. {
  199. unset(self::$loadedModules[$moduleName]);
  200. unset(self::$modulesHolders[$moduleName]);
  201. }
  202. unset(self::$sharewareModules[$moduleName]);
  203. }
  204. /**
  205. * Returns document root
  206. *
  207. * @return string Document root
  208. */
  209. public static function getDocumentRoot()
  210. {
  211. static $documentRoot = null;
  212. if ($documentRoot === null)
  213. {
  214. $documentRoot = rtrim($_SERVER["DOCUMENT_ROOT"], "/\\");
  215. }
  216. return $documentRoot;
  217. }
  218. /**
  219. * Registers classes for auto loading.
  220. * All the frequently used classes should be registered for auto loading (performance).
  221. * It is not necessary to register rarely used classes. They can be found and loaded dynamically.
  222. *
  223. * @param string $moduleName Name of the module. Can be null if classes are not part of any module
  224. * @param array $classes Array of classes with class names as keys and paths as values.
  225. * @throws LoaderException
  226. */
  227. public static function registerAutoLoadClasses($moduleName, array $classes)
  228. {
  229. if (empty($classes))
  230. {
  231. return;
  232. }
  233. if (($moduleName !== null) && empty($moduleName))
  234. {
  235. throw new LoaderException(sprintf("Module name '%s' is not correct", $moduleName));
  236. }
  237. foreach ($classes as $class => $file)
  238. {
  239. $class = ltrim($class, "\\");
  240. $class = strtolower($class);
  241. self::$autoLoadClasses[$class] = [
  242. "module" => $moduleName,
  243. "file" => $file,
  244. ];
  245. }
  246. }
  247. /**
  248. * Registers namespaces with custom paths.
  249. * e.g. ('Bitrix\Main\Dev', '/home/bitrix/web/site/bitrix/modules/main/dev/lib')
  250. *
  251. * @param string $namespace A namespace prefix.
  252. * @param string $path An absolute path.
  253. */
  254. public static function registerNamespace($namespace, $path)
  255. {
  256. $namespace = trim($namespace, "\\")."\\";
  257. $namespace = strtolower($namespace);
  258. $path = rtrim($path, "/\\");
  259. $depth = substr_count(rtrim($namespace, "\\"), "\\");
  260. self::$namespaces[$namespace][] = [
  261. "path" => $path,
  262. "depth" => $depth,
  263. ];
  264. }
  265. /**
  266. * Unregisters a namespace.
  267. * @param string $namespace
  268. */
  269. public static function unregisterNamespace($namespace)
  270. {
  271. $namespace = trim($namespace, "\\")."\\";
  272. $namespace = strtolower($namespace);
  273. unset(self::$namespaces[$namespace]);
  274. }
  275. /**
  276. * Registers an additional autoload handler.
  277. * @param callable $handler
  278. */
  279. public static function registerHandler(callable $handler)
  280. {
  281. \spl_autoload_register($handler);
  282. }
  283. /**
  284. * PSR-4 compatible autoloader.
  285. * https://www.php-fig.org/psr/psr-4/
  286. *
  287. * @param $className
  288. */
  289. public static function autoLoad($className)
  290. {
  291. // fix web env
  292. $className = ltrim($className, "\\");
  293. $classLower = strtolower($className);
  294. static $documentRoot = null;
  295. if ($documentRoot === null)
  296. {
  297. $documentRoot = self::getDocumentRoot();
  298. }
  299. //optimization via direct paths
  300. if (isset(self::$autoLoadClasses[$classLower]))
  301. {
  302. $pathInfo = self::$autoLoadClasses[$classLower];
  303. if ($pathInfo["module"] != "")
  304. {
  305. $module = $pathInfo["module"];
  306. $holder = (isset(self::$modulesHolders[$module])? self::$modulesHolders[$module] : self::BITRIX_HOLDER);
  307. $filePath = (defined('REPOSITORY_ROOT'))
  308. ? REPOSITORY_ROOT
  309. : "{$documentRoot}/{$holder}/modules";
  310. $filePath .= '/'.$module."/".$pathInfo["file"];
  311. require_once($filePath);
  312. }
  313. else
  314. {
  315. require_once($documentRoot.$pathInfo["file"]);
  316. }
  317. return;
  318. }
  319. if (preg_match("#[^\\\\/a-zA-Z0-9_]#", $className))
  320. {
  321. return;
  322. }
  323. $tryFiles = [[
  324. "real" => $className,
  325. "lower" => $classLower,
  326. ]];
  327. if (substr($classLower, -5) == "table")
  328. {
  329. // old *Table stored in reserved files
  330. $tryFiles[] = [
  331. "real" => substr($className, 0, -5),
  332. "lower" => substr($classLower, 0, -5),
  333. ];
  334. }
  335. foreach ($tryFiles as $classInfo)
  336. {
  337. $classParts = explode("\\", $classInfo["lower"]);
  338. //remove class name
  339. array_pop($classParts);
  340. while(!empty($classParts))
  341. {
  342. //go from the end
  343. $namespace = implode("\\", $classParts)."\\";
  344. if(isset(self::$namespaces[$namespace]))
  345. {
  346. //found
  347. foreach (self::$namespaces[$namespace] as $namespaceLocation)
  348. {
  349. $depth = $namespaceLocation["depth"];
  350. $path = $namespaceLocation["path"];
  351. $fileParts = explode("\\", $classInfo["real"]);
  352. for ($i=0; $i <= $depth; $i++)
  353. {
  354. array_shift($fileParts);
  355. }
  356. $classPath = implode("/", $fileParts);
  357. $classPathLower = strtolower($classPath);
  358. // final path lower case
  359. $filePath = $path.'/'.$classPathLower.".php";
  360. if (file_exists($filePath))
  361. {
  362. require_once($filePath);
  363. break 3;
  364. }
  365. // final path original case
  366. $filePath = $path.'/'.$classPath.".php";
  367. if (file_exists($filePath))
  368. {
  369. require_once($filePath);
  370. break 3;
  371. }
  372. }
  373. }
  374. //try the shorter namespace
  375. array_pop($classParts);
  376. }
  377. }
  378. }
  379. /**
  380. * @param $className
  381. *
  382. * @throws LoaderException
  383. */
  384. public static function requireClass($className)
  385. {
  386. $file = ltrim($className, "\\"); // fix web env
  387. $file = strtolower($file);
  388. if (preg_match("#[^\\\\/a-zA-Z0-9_]#", $file))
  389. return;
  390. $tryFiles = [$file];
  391. if (substr($file, -5) == "table")
  392. {
  393. // old *Table stored in reserved files
  394. $tryFiles[] = substr($file, 0, -5);
  395. }
  396. foreach ($tryFiles as $file)
  397. {
  398. $file = str_replace('\\', '/', $file);
  399. $arFile = explode("/", $file);
  400. if ($arFile[0] === "bitrix")
  401. {
  402. array_shift($arFile);
  403. if (empty($arFile))
  404. {
  405. break;
  406. }
  407. $module = array_shift($arFile);
  408. if ($module == null || empty($arFile))
  409. {
  410. break;
  411. }
  412. }
  413. else
  414. {
  415. $module1 = array_shift($arFile);
  416. $module2 = array_shift($arFile);
  417. if ($module1 == null || $module2 == null || empty($arFile))
  418. {
  419. break;
  420. }
  421. $module = $module1.".".$module2;
  422. }
  423. if (!self::includeModule($module))
  424. {
  425. throw new LoaderException(sprintf(
  426. "There is no `%s` class, module `%s` is unavailable", $className, $module
  427. ));
  428. }
  429. }
  430. self::autoLoad($className);
  431. }
  432. /**
  433. * Checks if file exists in /local or /bitrix directories
  434. *
  435. * @param string $path File path relative to /local/ or /bitrix/
  436. * @param string|null $root Server document root, default self::getDocumentRoot()
  437. * @return string|bool Returns combined path or false if the file does not exist in both dirs
  438. */
  439. public static function getLocal($path, $root = null)
  440. {
  441. if ($root === null)
  442. {
  443. $root = self::getDocumentRoot();
  444. }
  445. if (file_exists($root."/local/".$path))
  446. {
  447. return $root."/local/".$path;
  448. }
  449. elseif (file_exists($root."/bitrix/".$path))
  450. {
  451. return $root."/bitrix/".$path;
  452. }
  453. else
  454. {
  455. return false;
  456. }
  457. }
  458. /**
  459. * Checks if file exists in personal directory.
  460. * If $_SERVER["BX_PERSONAL_ROOT"] is not set than personal directory is equal to /bitrix/
  461. *
  462. * @param string $path File path relative to personal directory
  463. * @return string|bool Returns combined path or false if the file does not exist
  464. */
  465. public static function getPersonal($path)
  466. {
  467. $root = self::getDocumentRoot();
  468. $personal = (isset($_SERVER["BX_PERSONAL_ROOT"])? $_SERVER["BX_PERSONAL_ROOT"] : "");
  469. if ($personal <> '' && file_exists($root.$personal."/".$path))
  470. {
  471. return $root.$personal."/".$path;
  472. }
  473. return self::getLocal($path, $root);
  474. }
  475. /**
  476. * Changes requireModule behavior
  477. *
  478. * @param bool $requireThrowException
  479. */
  480. public static function setRequireThrowException($requireThrowException)
  481. {
  482. self::$requireThrowException = (bool) $requireThrowException;
  483. }
  484. }
  485. class LoaderException extends \Exception
  486. {
  487. public function __construct($message = "", $code = 0, \Exception $previous = null)
  488. {
  489. parent::__construct($message, $code, $previous);
  490. }
  491. }