2017-05-31 17:45:09 +00:00
|
|
|
<?php
|
2022-06-27 18:18:44 +00:00
|
|
|
|
2017-05-31 17:45:09 +00:00
|
|
|
/**
|
2021-08-22 21:57:00 +00:00
|
|
|
* Joomla! Content Management System
|
2017-05-31 17:45:09 +00:00
|
|
|
*
|
2020-11-28 01:33:45 +00:00
|
|
|
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
2017-05-31 17:45:09 +00:00
|
|
|
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
|
|
|
|
2022-06-27 18:21:30 +00:00
|
|
|
* @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
|
|
|
|
*/
|
2017-05-31 17:45:09 +00:00
|
|
|
|
2021-04-25 21:01:41 +00:00
|
|
|
use Joomla\CMS\Log\Log;
|
2022-05-04 12:23:22 +00:00
|
|
|
use Joomla\Filesystem\File;
|
|
|
|
use Joomla\Filesystem\Folder;
|
2018-06-19 19:00:00 +00:00
|
|
|
|
2022-08-28 07:39:05 +00:00
|
|
|
// phpcs:disable PSR1.Files.SideEffects
|
|
|
|
\defined('_JEXEC') or die;
|
|
|
|
// phpcs:enable PSR1.Files.SideEffects
|
|
|
|
|
2017-05-31 17:45:09 +00:00
|
|
|
/**
|
2021-01-10 22:07:08 +00:00
|
|
|
* Class JNamespacePsr4Map
|
2017-05-31 17:45:09 +00:00
|
|
|
*
|
2017-11-19 16:34:38 +00:00
|
|
|
* @since 4.0.0
|
2017-05-31 17:45:09 +00:00
|
|
|
*/
|
|
|
|
class JNamespacePsr4Map
|
|
|
|
{
|
2022-06-27 18:18:44 +00:00
|
|
|
/**
|
|
|
|
* Path to the autoloader
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
* @since 4.0.0
|
|
|
|
*/
|
|
|
|
protected $file = JPATH_CACHE . '/autoload_psr4.php';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array|null
|
|
|
|
* @since 4.0.0
|
|
|
|
*/
|
|
|
|
private $cachedMap = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the file exists
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
*
|
|
|
|
* @since 4.0.0
|
|
|
|
*/
|
|
|
|
public function exists()
|
|
|
|
{
|
|
|
|
return is_file($this->file);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the namespace mapping file exists, if not create it
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*
|
|
|
|
* @since 4.0.0
|
|
|
|
*/
|
|
|
|
public function ensureMapFileExists()
|
|
|
|
{
|
|
|
|
if (!$this->exists()) {
|
|
|
|
$this->create();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create the namespace file
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
*
|
|
|
|
* @since 4.0.0
|
|
|
|
*/
|
|
|
|
public function create()
|
|
|
|
{
|
|
|
|
$extensions = array_merge(
|
|
|
|
$this->getNamespaces('component'),
|
|
|
|
$this->getNamespaces('module'),
|
2022-12-29 10:03:00 +00:00
|
|
|
$this->getNamespaces('template'),
|
2022-06-27 18:18:44 +00:00
|
|
|
$this->getNamespaces('plugin'),
|
|
|
|
$this->getNamespaces('library')
|
|
|
|
);
|
|
|
|
|
|
|
|
ksort($extensions);
|
|
|
|
|
|
|
|
$this->writeNamespaceFile($extensions);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load the PSR4 file
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
*
|
|
|
|
* @since 4.0.0
|
|
|
|
*/
|
|
|
|
public function load()
|
|
|
|
{
|
|
|
|
if (!$this->exists()) {
|
|
|
|
$this->create();
|
|
|
|
}
|
|
|
|
|
|
|
|
$map = $this->cachedMap ?: require $this->file;
|
|
|
|
|
|
|
|
$loader = include JPATH_LIBRARIES . '/vendor/autoload.php';
|
|
|
|
|
|
|
|
foreach ($map as $namespace => $path) {
|
|
|
|
$loader->setPsr4($namespace, $path);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Write the Namespace mapping file
|
|
|
|
*
|
|
|
|
* @param array $elements Array of elements
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*
|
|
|
|
* @since 4.0.0
|
|
|
|
*/
|
|
|
|
protected function writeNamespaceFile($elements)
|
|
|
|
{
|
2023-01-14 09:54:47 +00:00
|
|
|
$content = [];
|
2022-06-27 18:18:44 +00:00
|
|
|
$content[] = "<?php";
|
|
|
|
$content[] = 'defined(\'_JEXEC\') or die;';
|
|
|
|
$content[] = 'return [';
|
|
|
|
|
|
|
|
foreach ($elements as $namespace => $path) {
|
|
|
|
$content[] = "\t'" . $namespace . "'" . ' => [' . $path . '],';
|
|
|
|
}
|
|
|
|
|
|
|
|
$content[] = '];';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Backup the current error_reporting level and set a new level
|
|
|
|
*
|
|
|
|
* We do this because file_put_contents can raise a Warning if it cannot write the autoload_psr4.php file
|
|
|
|
* and this will output to the response BEFORE the session has started, causing the session start to fail
|
|
|
|
* and ultimately leading us to a 500 Internal Server Error page just because of the output warning, which
|
|
|
|
* we can safely ignore as we can use an in-memory autoload_psr4 map temporarily, and display real errors later.
|
|
|
|
*/
|
|
|
|
$error_reporting = error_reporting(0);
|
|
|
|
|
|
|
|
try {
|
|
|
|
File::write($this->file, implode("\n", $content));
|
|
|
|
} catch (Exception $e) {
|
|
|
|
Log::add('Could not save ' . $this->file, Log::WARNING);
|
|
|
|
|
|
|
|
$map = [];
|
2023-01-14 12:05:02 +00:00
|
|
|
$constants = ['JPATH_ADMINISTRATOR', 'JPATH_API', 'JPATH_SITE', 'JPATH_PLUGINS', 'JPATH_LIBRARIES'];
|
2022-06-27 18:18:44 +00:00
|
|
|
|
|
|
|
foreach ($elements as $namespace => $path) {
|
|
|
|
foreach ($constants as $constant) {
|
|
|
|
$path = preg_replace(['/^(' . $constant . ")\s\.\s\'/", '/\'$/'], [constant($constant), ''], $path);
|
|
|
|
}
|
|
|
|
|
|
|
|
$namespace = str_replace('\\\\', '\\', $namespace);
|
|
|
|
$map[$namespace] = [ $path ];
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->cachedMap = $map;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Restore previous value of error_reporting
|
|
|
|
error_reporting($error_reporting);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get an array of namespaces with their respective path for the given extension type.
|
|
|
|
*
|
|
|
|
* @param string $type The extension type
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*
|
|
|
|
* @since 4.0.0
|
|
|
|
*/
|
|
|
|
private function getNamespaces(string $type): array
|
|
|
|
{
|
2023-01-14 12:05:02 +00:00
|
|
|
$extensions = [];
|
2022-06-27 18:18:44 +00:00
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
foreach ($this->getExtensions($type) as $extensionPath => $file) {
|
|
|
|
// Load the manifest file
|
|
|
|
$xml = simplexml_load_file($file, 'SimpleXMLElement', LIBXML_NOERROR);
|
|
|
|
|
|
|
|
// When invalid, ignore
|
|
|
|
if (!$xml) {
|
|
|
|
continue;
|
2022-06-27 18:18:44 +00:00
|
|
|
}
|
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
// The namespace node
|
|
|
|
$namespaceNode = $xml->namespace;
|
2022-06-27 18:18:44 +00:00
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
// The namespace string
|
|
|
|
$namespace = (string) $namespaceNode;
|
|
|
|
|
|
|
|
// Ignore when the string is empty
|
|
|
|
if (!$namespace) {
|
2022-06-27 18:18:44 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
// Normalize the namespace string
|
|
|
|
$namespace = str_replace('\\', '\\\\', $namespace) . '\\\\';
|
|
|
|
$namespacePath = rtrim($extensionPath . '/' . $namespaceNode->attributes()->path, '/');
|
2022-06-27 18:18:44 +00:00
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
if ($type === 'plugin' || $type === 'library') {
|
|
|
|
$baseDir = $type === 'plugin' ? 'JPATH_PLUGINS . \'' : 'JPATH_LIBRARIES . \'';
|
|
|
|
$path = substr($namespacePath, strlen($type === 'plugin' ? JPATH_PLUGINS : JPATH_LIBRARIES));
|
2022-12-29 10:03:00 +00:00
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
// Set the namespace
|
|
|
|
$extensions[$namespace] = $baseDir . $path . '\'';
|
2022-06-27 18:18:44 +00:00
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
continue;
|
|
|
|
}
|
2022-06-27 18:18:44 +00:00
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
// Check if we need to use administrator path
|
|
|
|
$isAdministrator = strpos($namespacePath, JPATH_ADMINISTRATOR) === 0;
|
|
|
|
$path = substr($namespacePath, strlen($isAdministrator ? JPATH_ADMINISTRATOR : JPATH_SITE));
|
2022-06-27 18:18:44 +00:00
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
// Add the site path when a component
|
|
|
|
if ($type === 'component') {
|
|
|
|
if (is_dir(JPATH_SITE . $path)) {
|
|
|
|
$extensions[$namespace . 'Site\\\\'] = 'JPATH_SITE . \'' . $path . '\'';
|
2022-06-27 18:18:44 +00:00
|
|
|
}
|
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
if (is_dir(JPATH_API . $path)) {
|
|
|
|
$extensions[$namespace . 'Api\\\\'] = 'JPATH_API . \'' . $path . '\'';
|
2022-06-27 18:18:44 +00:00
|
|
|
}
|
2023-01-14 12:05:02 +00:00
|
|
|
}
|
2022-06-27 18:18:44 +00:00
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
// Add the application specific segment when a component or module
|
|
|
|
$baseDir = $isAdministrator ? 'JPATH_ADMINISTRATOR . \'' : 'JPATH_SITE . \'';
|
2023-01-21 02:23:16 +00:00
|
|
|
$realPath = ($isAdministrator ? JPATH_ADMINISTRATOR : JPATH_SITE) . $path;
|
2023-01-14 12:05:02 +00:00
|
|
|
$namespace .= $isAdministrator ? 'Administrator\\\\' : 'Site\\\\';
|
2022-06-27 18:18:44 +00:00
|
|
|
|
2023-01-21 02:23:16 +00:00
|
|
|
// Validate if the directory exists
|
|
|
|
if (!is_dir($realPath)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
// Set the namespace
|
|
|
|
$extensions[$namespace] = $baseDir . $path . '\'';
|
|
|
|
}
|
2022-06-27 18:18:44 +00:00
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
// Return the namespaces
|
|
|
|
return $extensions;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array of with extension paths as keys and manifest paths as values.
|
|
|
|
*
|
|
|
|
* @param string $type The extension type
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*
|
|
|
|
* @since __DEPLOY_VERSION__
|
|
|
|
*/
|
|
|
|
private function getExtensions(string $type): array
|
|
|
|
{
|
|
|
|
$manifests = [];
|
2022-06-27 18:18:44 +00:00
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
if ($type === 'library') {
|
|
|
|
try {
|
|
|
|
// Scan library manifest directories for XML files
|
|
|
|
foreach (Folder::files(JPATH_MANIFESTS . '/libraries', '\.xml$', true, true) as $file) {
|
|
|
|
// Match manifest to extension directory
|
|
|
|
$manifests[JPATH_LIBRARIES . '/' . File::stripExt(substr($file, strlen(JPATH_MANIFESTS . '/libraries') + 1))] = $file;
|
|
|
|
}
|
|
|
|
} catch (UnexpectedValueException $e) {
|
|
|
|
return [];
|
|
|
|
}
|
2022-06-27 18:18:44 +00:00
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
return $manifests;
|
|
|
|
}
|
2022-06-27 18:18:44 +00:00
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
if ($type === 'component') {
|
|
|
|
$directories = [JPATH_ADMINISTRATOR . '/components'];
|
|
|
|
} elseif ($type === 'module') {
|
|
|
|
$directories = [JPATH_SITE . '/modules', JPATH_ADMINISTRATOR . '/modules'];
|
2023-01-21 02:23:16 +00:00
|
|
|
} elseif ($type === 'template') {
|
|
|
|
$directories = [JPATH_SITE . '/templates', JPATH_ADMINISTRATOR . '/templates'];
|
2023-01-14 12:05:02 +00:00
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
$directories = Folder::folders(JPATH_PLUGINS, '.', false, true);
|
|
|
|
} catch (UnexpectedValueException $e) {
|
|
|
|
$directories = [];
|
|
|
|
}
|
|
|
|
}
|
2022-06-27 18:18:44 +00:00
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
foreach ($directories as $directory) {
|
|
|
|
try {
|
|
|
|
$extensionDirectories = Folder::folders($directory, '.', false, false);
|
|
|
|
} catch (UnexpectedValueException $e) {
|
|
|
|
continue;
|
|
|
|
}
|
2022-06-27 18:18:44 +00:00
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
foreach ($extensionDirectories as $extension) {
|
|
|
|
// Compile the extension path
|
|
|
|
$extensionPath = $directory . '/' . $extension;
|
2022-06-27 18:18:44 +00:00
|
|
|
|
|
|
|
if ($type === 'component') {
|
2023-01-14 12:05:02 +00:00
|
|
|
// Strip the com_ from the extension name for components
|
|
|
|
$file = $extensionPath . '/' . substr($extension, 4) . '.xml';
|
2022-06-27 18:18:44 +00:00
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
if (!is_file($file)) {
|
|
|
|
$file = $extensionPath . '/' . $extension . '.xml';
|
2022-06-27 18:18:44 +00:00
|
|
|
}
|
2023-01-21 03:22:31 +00:00
|
|
|
} elseif ($type === 'template') {
|
2023-01-21 02:23:16 +00:00
|
|
|
// Template manifestfiles have a fix filename
|
|
|
|
$file = $extensionPath . '/templateDetails.xml';
|
2023-01-14 12:05:02 +00:00
|
|
|
} else {
|
|
|
|
$file = $extensionPath . '/' . $extension . '.xml';
|
2022-06-27 18:18:44 +00:00
|
|
|
}
|
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
if (is_file($file)) {
|
|
|
|
$manifests[$extensionPath] = $file;
|
2022-12-29 10:03:00 +00:00
|
|
|
}
|
2022-06-27 18:18:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-14 12:05:02 +00:00
|
|
|
return $manifests;
|
2022-06-27 18:18:44 +00:00
|
|
|
}
|
2017-05-31 17:45:09 +00:00
|
|
|
}
|