diff --git a/htdocs/composer.json b/htdocs/composer.json new file mode 100644 index 000000000000..c865b09771c9 --- /dev/null +++ b/htdocs/composer.json @@ -0,0 +1,81 @@ +{ + "name": "impresscms/impresscms", + "description": "ImpressCMS - A dynamic and user-friendly Content Management System", + "type": "project", + "keywords": ["cms", "content-management", "php", "mysql", "web"], + "homepage": "https://www.impresscms.org/", + "license": "GPL-2.0", + "authors": [ + { + "name": "The ImpressCMS Project", + "homepage": "https://www.impresscms.org/", + "role": "Developer" + } + ], + "require": { + "php": ">=7.4.0", + "ext-gd": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-mysqli": "*", + "ext-pcre": "*", + "ext-pdo": "*", + "ext-session": "*", + "ext-xml": "*", + "ext-zlib": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "squizlabs/php_codesniffer": "^3.0" + }, + "autoload": { + "psr-4": { + "Icms\\": "libraries/icms/" + }, + "psr-0": { + "icms_": "libraries/" + }, + "classmap": [ + "libraries/icms.php" + ] + }, + "autoload-dev": { + "psr-4": { + "Icms\\Tests\\": "../tests/" + } + }, + "config": { + "optimize-autoloader": true, + "classmap-authoritative": false, + "apcu-autoloader": true, + "sort-packages": true, + "allow-plugins": { + "*": false + }, + "cache-dir": "var/cache/composer", + "vendor-dir": "vendor" + }, + "scripts": { + "test": "phpunit", + "cs-check": "phpcs --standard=PSR12 libraries/icms/", + "cs-fix": "phpcbf --standard=PSR12 libraries/icms/", + "optimize-production": [ + "composer dump-autoload --optimize --classmap-authoritative --apcu", + "@php -r \"echo 'Production autoloader optimized for ImpressCMS\\n';\"" + ], + "optimize-development": [ + "composer dump-autoload --optimize", + "@php -r \"echo 'Development autoloader optimized for ImpressCMS\\n';\"" + ], + "post-autoload-dump": [ + "@php -r \"echo 'Autoloader optimized for ImpressCMS\\n';\"" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x" + } + }, + "minimum-stability": "stable", + "prefer-stable": true +} diff --git a/htdocs/include/functions.php b/htdocs/include/functions.php index 79520a84d628..576c8dd2de2a 100644 --- a/htdocs/include/functions.php +++ b/htdocs/include/functions.php @@ -1612,17 +1612,25 @@ function &icms_getModuleHandler($name = null, $module_dir = null, $module_basena if (class_exists($class)) { $handlers[$module_dir][$name] = new $class(icms::$xoopsDB); } else { - if($module_dir !== 'system') { - if (!file_exists($hnd_file = ICMS_ROOT_PATH . "/modules/{$module_dir}/class/". ucfirst(strtolower($name)) ."Handler.php")) { - $hnd_file = ICMS_ROOT_PATH . "/modules/{$module_dir}/class/{$name}.php"; - } + // Try alternative class naming patterns with autoloading + $alternativeClass = ucfirst(strtolower($module_basename)) . ucfirst($name) . 'Handler'; + if (class_exists($alternativeClass)) { + $handlers[$module_dir][$name] = new $alternativeClass(icms::$xoopsDB); } else { - $hnd_file = ICMS_ROOT_PATH . "/modules/{$module_dir}/admin/{$name}/class/{$name}.php"; - } - if (file_exists($hnd_file)) {include_once $hnd_file;} - $class = ucfirst(strtolower($module_basename)) . ucfirst($name) . 'Handler'; - if (class_exists($class)) { - $handlers[$module_dir][$name] = new $class(icms::$xoopsDB); + // Fallback to manual include for legacy modules not yet using autoloading + if($module_dir !== 'system') { + if (!file_exists($hnd_file = ICMS_ROOT_PATH . "/modules/{$module_dir}/class/". ucfirst(strtolower($name)) ."Handler.php")) { + $hnd_file = ICMS_ROOT_PATH . "/modules/{$module_dir}/class/{$name}.php"; + } + } else { + $hnd_file = ICMS_ROOT_PATH . "/modules/{$module_dir}/admin/{$name}/class/{$name}.php"; + } + if (file_exists($hnd_file)) { + include_once $hnd_file; + if (class_exists($alternativeClass)) { + $handlers[$module_dir][$name] = new $alternativeClass(icms::$xoopsDB); + } + } } } } diff --git a/htdocs/install/common.inc.php b/htdocs/install/common.inc.php index 5a9650eb4ae6..a691e859fac0 100644 --- a/htdocs/install/common.inc.php +++ b/htdocs/install/common.inc.php @@ -37,9 +37,21 @@ require_once 'include/functions.php'; include_once './class/IcmsInstallWizard.php'; +// Initialize the modern autoloading system +if (file_exists('../vendor/autoload.php')) { + require_once '../vendor/autoload.php'; +} + +// Load the compatibility bridge +require_once '../libraries/icms/ComposerAutoloadBridge.php'; + +// Initialize legacy autoloader for backward compatibility require_once '../libraries/icms/Autoloader.php'; icms_Autoloader::setup(); +// Initialize the bridge +icms_ComposerAutoloadBridge::initialize(); + error_reporting(E_ALL); $pageHasHelp = false; diff --git a/htdocs/libraries/icms.php b/htdocs/libraries/icms.php index 42f9b13ceabf..4c90b871abbc 100644 --- a/htdocs/libraries/icms.php +++ b/htdocs/libraries/icms.php @@ -111,13 +111,35 @@ static public function setup() { self::$paths['www'] = array(ICMS_ROOT_PATH, ICMS_URL); self::$paths['modules'] = array(ICMS_ROOT_PATH . '/modules', ICMS_URL . '/modules'); self::$paths['themes'] = array(ICMS_THEME_PATH, ICMS_THEME_URL); - // Initialize the autoloader - require_once dirname(__FILE__ ) . '/icms/Autoloader.php'; - icms_Autoloader::setup(); + + // Initialize autoloading system + self::initializeAutoloading(); + register_shutdown_function(array(__CLASS__, 'shutdown')); self::buildRelevantUrls(); } + /** + * Initialize the autoloading system with Composer and legacy support + */ + static private function initializeAutoloading() { + // Try to load Composer autoloader first - now located in htdocs/vendor/ + $composerAutoloadPath = ICMS_ROOT_PATH . '/vendor/autoload.php'; + if (file_exists($composerAutoloadPath)) { + require_once $composerAutoloadPath; + } + + // Load the compatibility bridge + require_once dirname(__FILE__) . '/icms/ComposerAutoloadBridge.php'; + + // Initialize legacy autoloader for backward compatibility + require_once dirname(__FILE__) . '/icms/Autoloader.php'; + icms_Autoloader::setup(); + + // Initialize the bridge + icms_ComposerAutoloadBridge::initialize(); + } + /** * Launch bootstrap and instanciate global services * @return void diff --git a/htdocs/libraries/icms/AutoloadPerformanceMonitor.php b/htdocs/libraries/icms/AutoloadPerformanceMonitor.php new file mode 100644 index 000000000000..4367d7dd77e8 --- /dev/null +++ b/htdocs/libraries/icms/AutoloadPerformanceMonitor.php @@ -0,0 +1,212 @@ + 0, + 'load_times' => array(), + 'failed_loads' => 0, + 'cache_hits' => 0, + 'cache_misses' => 0 + ); + + /** + * Whether monitoring is enabled + * @var bool + */ + private static $enabled = false; + + /** + * Start performance monitoring + */ + public static function enable() { + if (!self::$enabled) { + self::$enabled = true; + self::registerMonitoringAutoloader(); + } + } + + /** + * Stop performance monitoring + */ + public static function disable() { + self::$enabled = false; + } + + /** + * Register a monitoring autoloader + */ + private static function registerMonitoringAutoloader() { + spl_autoload_register(array(__CLASS__, 'monitoringAutoloader'), true, true); + } + + /** + * Monitoring autoloader that tracks performance + * + * @param string $class The class name + * @return bool + */ + public static function monitoringAutoloader($class) { + if (!self::$enabled) { + return false; + } + + $startTime = microtime(true); + + // Let other autoloaders handle the actual loading + $loaded = false; + $autoloaders = spl_autoload_functions(); + + foreach ($autoloaders as $autoloader) { + if ($autoloader === array(__CLASS__, 'monitoringAutoloader')) { + continue; // Skip ourselves + } + + if (is_callable($autoloader)) { + if (call_user_func($autoloader, $class)) { + $loaded = true; + break; + } + } + } + + $endTime = microtime(true); + $loadTime = ($endTime - $startTime) * 1000; // Convert to milliseconds + + // Record metrics + if ($loaded) { + self::$metrics['class_loads']++; + self::$metrics['load_times'][] = $loadTime; + + // Check if it was a cache hit (very fast load) + if ($loadTime < 0.1) { + self::$metrics['cache_hits']++; + } else { + self::$metrics['cache_misses']++; + } + } else { + self::$metrics['failed_loads']++; + } + + return $loaded; + } + + /** + * Get performance metrics + * + * @return array + */ + public static function getMetrics() { + $metrics = self::$metrics; + + if (!empty($metrics['load_times'])) { + $metrics['average_load_time'] = array_sum($metrics['load_times']) / count($metrics['load_times']); + $metrics['max_load_time'] = max($metrics['load_times']); + $metrics['min_load_time'] = min($metrics['load_times']); + } + + $metrics['cache_hit_ratio'] = 0; + if (($metrics['cache_hits'] + $metrics['cache_misses']) > 0) { + $metrics['cache_hit_ratio'] = $metrics['cache_hits'] / ($metrics['cache_hits'] + $metrics['cache_misses']); + } + + return $metrics; + } + + /** + * Reset performance metrics + */ + public static function resetMetrics() { + self::$metrics = array( + 'class_loads' => 0, + 'load_times' => array(), + 'failed_loads' => 0, + 'cache_hits' => 0, + 'cache_misses' => 0 + ); + } + + /** + * Generate a performance report + * + * @return string + */ + public static function generateReport() { + $metrics = self::getMetrics(); + + $report = "ImpressCMS Autoload Performance Report\n"; + $report .= "=====================================\n\n"; + + $report .= "Classes Loaded: " . $metrics['class_loads'] . "\n"; + $report .= "Failed Loads: " . $metrics['failed_loads'] . "\n"; + $report .= "Cache Hits: " . $metrics['cache_hits'] . "\n"; + $report .= "Cache Misses: " . $metrics['cache_misses'] . "\n"; + $report .= "Cache Hit Ratio: " . number_format($metrics['cache_hit_ratio'] * 100, 2) . "%\n\n"; + + if (isset($metrics['average_load_time'])) { + $report .= "Average Load Time: " . number_format($metrics['average_load_time'], 3) . "ms\n"; + $report .= "Max Load Time: " . number_format($metrics['max_load_time'], 3) . "ms\n"; + $report .= "Min Load Time: " . number_format($metrics['min_load_time'], 3) . "ms\n\n"; + } + + // Performance recommendations + $report .= "Recommendations:\n"; + $report .= "----------------\n"; + + if ($metrics['cache_hit_ratio'] < 0.8) { + $report .= "- Consider enabling APCu for better caching performance\n"; + $report .= "- Run 'composer optimize-production' for production environments\n"; + } + + if (isset($metrics['average_load_time']) && $metrics['average_load_time'] > 1.0) { + $report .= "- Average load time is high, consider optimizing autoloader\n"; + $report .= "- Check for unnecessary file system operations\n"; + } + + if ($metrics['failed_loads'] > 0) { + $report .= "- " . $metrics['failed_loads'] . " failed loads detected, check class naming\n"; + } + + return $report; + } + + /** + * Check if autoloader optimizations are available + * + * @return array + */ + public static function checkOptimizations() { + $optimizations = array(); + + // Check for APCu + $optimizations['apcu_available'] = extension_loaded('apcu') && apcu_enabled(); + + // Check for Composer optimizations + $optimizations['composer_optimized'] = false; + if (file_exists(ICMS_ROOT_PATH . '/vendor/composer/autoload_classmap.php')) { + $classmap = include ICMS_ROOT_PATH . '/vendor/composer/autoload_classmap.php'; + $optimizations['composer_optimized'] = !empty($classmap); + } + + // Check for production mode + $optimizations['production_mode'] = !defined('ICMS_DEBUG') || !ICMS_DEBUG; + + return $optimizations; + } +} diff --git a/htdocs/libraries/icms/Autoloader.php b/htdocs/libraries/icms/Autoloader.php index 53dc6f733001..64cdfbd1b463 100644 --- a/htdocs/libraries/icms/Autoloader.php +++ b/htdocs/libraries/icms/Autoloader.php @@ -34,10 +34,19 @@ class icms_Autoloader { /** * Initialize the autoloader, and register its autoload method + * + * @deprecated 1.5.0 The legacy icms_Autoloader is deprecated in favor of Composer's PSR-4 autoloader. + * It is maintained for backward compatibility but will be removed in a future version. + * New code should rely on Composer's autoloading system. * @return void */ static public function setup() { if (!self::$initialized) { + // Log deprecation notice in debug mode + if (defined('ICMS_DEBUG') && ICMS_DEBUG) { + error_log('DEPRECATED: icms_Autoloader is deprecated. Use Composer autoloader instead.'); + } + self::register(dirname(dirname(__FILE__))); spl_autoload_register(array('icms_Autoloader', 'autoload')); spl_autoload_register(array('icms_Autoloader', 'registerLegacy')); diff --git a/htdocs/libraries/icms/ComposerAutoloadBridge.php b/htdocs/libraries/icms/ComposerAutoloadBridge.php new file mode 100644 index 000000000000..2123bfcc2547 --- /dev/null +++ b/htdocs/libraries/icms/ComposerAutoloadBridge.php @@ -0,0 +1,159 @@ + 'icms_core_Security', + 'Icms\\Core\\Logger' => 'icms_core_Logger', + 'Icms\\Core\\Session' => 'icms_core_Session', + 'Icms\\Auth\\Factory' => 'icms_auth_Factory', + 'Icms\\Config\\Handler' => 'icms_config_Handler', + 'Icms\\Module\\Object' => 'icms_module_Object', + 'Icms\\Module\\Handler' => 'icms_module_Handler', + + // Legacy XOOPS compatibility + 'XoopsObject' => 'icms_core_Object', + 'XoopsObjectHandler' => 'icms_core_ObjectHandler', + ); + } + + /** + * Register a class alias + * + * @param string $alias The alias name + * @param string $realClass The real class name + */ + public static function registerAlias($alias, $realClass) { + self::$classAliases[$alias] = $realClass; + } + + /** + * Register multiple class aliases + * + * @param array $aliases Array of alias => realClass mappings + */ + public static function registerAliases(array $aliases) { + self::$classAliases = array_merge(self::$classAliases, $aliases); + } + + /** + * Check if Composer autoloader is available + * + * @return bool True if Composer autoloader is loaded + */ + public static function isComposerAvailable() { + return class_exists('Composer\\Autoload\\ClassLoader', false); + } + + /** + * Get the legacy autoloader instance + * + * @return string|null The legacy autoloader class name + */ + public static function getLegacyAutoloader() { + return self::$legacyAutoloader; + } + + /** + * Disable the bridge (for testing purposes) + */ + public static function disable() { + if (self::$initialized) { + spl_autoload_unregister(array(__CLASS__, 'autoload')); + self::$initialized = false; + } + } +} diff --git a/htdocs/libraries/icms/module/Object.php b/htdocs/libraries/icms/module/Object.php index 68237685b80c..1502daf45587 100644 --- a/htdocs/libraries/icms/module/Object.php +++ b/htdocs/libraries/icms/module/Object.php @@ -129,12 +129,57 @@ public function registerClassPath($isactive = NULL) { // check if module is active (only if applicable) if ($isactive !== NULL && $this->getVar("isactive") != (int) $isactive) return; - // register class path + // register class path with both legacy and Composer autoloaders if ($this->getVar("ipf")) { $modname = ($this->getVar("modname") != "") ? $this->getVar("modname") : $this->getVar("dirname"); - icms_Autoloader::register($class_path, "mod_" . $modname); + $namespace = "mod_" . $modname; + + // Register with legacy autoloader for backward compatibility + icms_Autoloader::register($class_path, $namespace); + + // Register with Composer autoloader if available + $this->registerWithComposer($class_path, $namespace); } else { + // Register with legacy autoloader icms_Autoloader::register($class_path); + + // Register with Composer autoloader if available + $this->registerWithComposer($class_path); + } + } + + /** + * Register module classes with Composer autoloader + * + * @param string $class_path Path to the module classes + * @param string $namespace Optional namespace prefix + */ + private function registerWithComposer($class_path, $namespace = '') { + // Check if Composer autoloader is available + if (!class_exists('Composer\\Autoload\\ClassLoader', false)) { + return; + } + + // Get the Composer autoloader instance + $autoloadFiles = spl_autoload_functions(); + $composerLoader = null; + + foreach ($autoloadFiles as $autoloader) { + if (is_array($autoloader) && + is_object($autoloader[0]) && + $autoloader[0] instanceof Composer\Autoload\ClassLoader) { + $composerLoader = $autoloader[0]; + break; + } + } + + if ($composerLoader) { + // Add PSR-0 mapping for module classes + if ($namespace) { + $composerLoader->add($namespace . '_', $class_path); + } else { + $composerLoader->add('', $class_path); + } } }