From ce10952673e351dd2a44c63f6b5285066cc0adc9 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 12 Dec 2023 15:58:40 +0530 Subject: [PATCH 1/3] Allow modules to override module front and admin controllers --- classes/Dispatcher.php | 14 +- classes/controller/AdminController.php | 3 + classes/module/Module.php | 497 +++++++++++++++---------- 3 files changed, 310 insertions(+), 204 deletions(-) diff --git a/classes/Dispatcher.php b/classes/Dispatcher.php index 14ce46b7c..d4ff80579 100644 --- a/classes/Dispatcher.php +++ b/classes/Dispatcher.php @@ -279,7 +279,12 @@ class DispatcherCore $controllers = Dispatcher::getControllers(_PS_MODULE_DIR_.$module_name.'/controllers/front/'); if (isset($controllers[strtolower($this->controller)])) { include_once(_PS_MODULE_DIR_.$module_name.'/controllers/front/'.$this->controller.'.php'); - $controller_class = $module_name.$this->controller.'ModuleFrontController'; + if (file_exists(_PS_OVERRIDE_DIR_.'modules/'.$module_name.'/controllers/front/'.$this->controller.'.php')) { + include_once(_PS_OVERRIDE_DIR_.'modules/'.$module_name.'/controllers/front/'.$this->controller.'.php'); + $controller_class = $module_name.$this->controller.'ModuleFrontControllerOverride'; + } else { + $controller_class = $module_name.$this->controller.'ModuleFrontController'; + } } } $params_hook_action_dispatcher = array('controller_type' => self::FC_FRONT, 'controller_class' => $controller_class, 'is_module' => 1); @@ -305,7 +310,12 @@ class DispatcherCore } else { // Controllers in modules can be named AdminXXX.php or AdminXXXController.php include_once(_PS_MODULE_DIR_.$tab->module.'/controllers/admin/'.$controllers[strtolower($this->controller)].'.php'); - $controller_class = $controllers[strtolower($this->controller)].(strpos($controllers[strtolower($this->controller)], 'Controller') ? '' : 'Controller'); + if (file_exists(_PS_OVERRIDE_DIR_ . 'modules/' . $tab->module . '/controllers/admin/' . $controllers[strtolower($this->controller)] . '.php')) { + include_once(_PS_OVERRIDE_DIR_ . 'modules/' . $tab->module . '/controllers/admin/' . $controllers[strtolower($this->controller)] . '.php'); + $controller_class = $controllers[strtolower($this->controller)] . (strpos($controllers[strtolower($this->controller)], 'Controller') ? 'Override' : 'ControllerOverride'); + } else { + $controller_class = $controllers[strtolower($this->controller)] . (strpos($controllers[strtolower($this->controller)], 'Controller') ? '' : 'Controller'); + } } } $params_hook_action_dispatcher = array('controller_type' => self::FC_ADMIN, 'controller_class' => $controller_class, 'is_module' => 1); diff --git a/classes/controller/AdminController.php b/classes/controller/AdminController.php index ea3a469f7..941439b3a 100644 --- a/classes/controller/AdminController.php +++ b/classes/controller/AdminController.php @@ -407,6 +407,9 @@ class AdminControllerCore extends Controller $this->controller_type = 'admin'; $this->controller_name = get_class($this); + if (strpos($this->controller_name, 'ControllerOverride')) { + $this->controller_name = substr($this->controller_name, 0, -18); + } if (strpos($this->controller_name, 'Controller')) { $this->controller_name = substr($this->controller_name, 0, -10); } diff --git a/classes/module/Module.php b/classes/module/Module.php index 04c4a2cbe..c5cb2d64d 100644 --- a/classes/module/Module.php +++ b/classes/module/Module.php @@ -185,6 +185,10 @@ abstract class ModuleCore public static $hosted_modules_blacklist = array('autoupgrade'); + const OVERRIDE_TYPE_CORE = 1; + const OVERRIDE_TYPE_MODULE = 2; + const OVERRIDE_TYPE_MODULE_CONTROLLER = 3; + /** * Set the flag to indicate we are doing an import * @@ -2887,8 +2891,23 @@ abstract class ModuleCore $result = true; foreach (Tools::scandir($this->getLocalPath().'override', 'php', '', true) as $file) { $class = basename($file, '.php'); - if (PrestaShopAutoload::getInstance()->getClassPath($class.'Core') || Module::getModuleIdByName($class)) { - $result &= $this->addOverride($class); + if ($class == 'index') { + continue; + } + + $classInfo = array('class' => $class, 'file' => $file, 'type' => null); + if (PrestaShopAutoload::getInstance()->getClassPath($class.'Core')) { + $classInfo['type'] = self::OVERRIDE_TYPE_CORE; + } elseif (Module::getModuleIdByName($class)) { + $classInfo['type'] = self::OVERRIDE_TYPE_MODULE; + } elseif (preg_match('#^modules/([a-zA-Z0-9_-]+)/controllers/(admin|front)/([0-9a-zA-Z-_]+).php#', $file, $matches)) { + $classInfo['type'] = self::OVERRIDE_TYPE_MODULE_CONTROLLER; + $classInfo['module'] = $matches[1]; + $classInfo['controller_type'] = $matches[2]; + } + + if ($classInfo['type']) { + $result &= $this->addOverride($classInfo); } } @@ -2909,8 +2928,23 @@ abstract class ModuleCore $result = true; foreach (Tools::scandir($this->getLocalPath().'override', 'php', '', true) as $file) { $class = basename($file, '.php'); - if (PrestaShopAutoload::getInstance()->getClassPath($class.'Core') || Module::getModuleIdByName($class)) { - $result &= $this->removeOverride($class); + if ($class == 'index') { + continue; + } + + $classInfo = array('class' => $class, 'file' => $file, 'type' => null); + if (PrestaShopAutoload::getInstance()->getClassPath($class.'Core')) { + $classInfo['type'] = self::OVERRIDE_TYPE_CORE; + } elseif (Module::getModuleIdByName($class)) { + $classInfo['type'] = self::OVERRIDE_TYPE_MODULE; + } elseif (preg_match('#^modules/([a-zA-Z0-9_-]+)/controllers/(admin|front)/([0-9a-zA-Z-_]+).php#', $file, $matches)) { + $classInfo['type'] = self::OVERRIDE_TYPE_MODULE_CONTROLLER; + $classInfo['module'] = $matches[1]; + $classInfo['controller_type'] = $matches[2]; + } + + if ($classInfo['type']) { + $result &= $this->removeOverride($classInfo); } } @@ -2923,28 +2957,37 @@ abstract class ModuleCore * @param string $classname * @return bool */ - public function addOverride($classname) + public function addOverride($classInfo) { - $orig_path = $path = PrestaShopAutoload::getInstance()->getClassPath($classname.'Core'); - if (!$path) { - $path = 'modules'.DIRECTORY_SEPARATOR.$classname.DIRECTORY_SEPARATOR.$classname.'.php'; - } - $path_override = $this->getLocalPath().'override'.DIRECTORY_SEPARATOR.$path; + $classname = $classInfo['class']; - if (!file_exists($path_override)) { + $parentClassFilePath = ''; + if ($classInfo['type'] == self::OVERRIDE_TYPE_CORE) { + $parentClassFilePath = PrestaShopAutoload::getInstance()->getClassPath($classname.'Core'); + } elseif ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE) { + $parentClassFilePath = 'modules/'.$classname.'/'.$classname.'.php'; + } elseif ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE_CONTROLLER) { + $parentClassFilePath = $classInfo['file']; + } + + // the file in module which contains override information (methods, properties and constants) + $moduleClassFilePath = $this->getLocalPath().'override/'.$parentClassFilePath; + + if (!file_exists($moduleClassFilePath)) { return false; } else { - file_put_contents($path_override, preg_replace('#(\r\n|\r)#ism', "\n", file_get_contents($path_override))); + // save $moduleClassFilePath file with consistent newline character, "\n" + file_put_contents($moduleClassFilePath, preg_replace('#(\r\n|\r)#ism', "\n", file_get_contents($moduleClassFilePath))); } - $pattern_escape_com = '#(^\s*?\/\/.*?\n|\/\*(?!\n\s+\* module:.*?\* date:.*?\* version:.*?\*\/).*?\*\/)#ism'; - // Check if there is already an override file, if not, we just need to copy the file - if ($file = PrestaShopAutoload::getInstance()->getClassPath($classname)) { - // Check if override file is writable - $override_path = _PS_ROOT_DIR_.'/'.$file; + $patternEscapeCommon = '#(^\s*?\/\/.*?\n|\/\*(?!\n\s+\* module:.*?\* date:.*?\* version:.*?\*\/).*?\*\/)#ism'; - if ((!file_exists($override_path) && !is_writable(dirname($override_path))) || (file_exists($override_path) && !is_writable($override_path))) { - throw new Exception(sprintf(Tools::displayError('file (%s) not writable'), $override_path)); + $overrideClassFilePath = _PS_ROOT_DIR_.'/override/'.$parentClassFilePath; + // Case 1: Check if there is already an override file, if so, merge override information otherwise just copy it + if (file_exists($overrideClassFilePath)) { + // Check if override file is writable + if (!is_writable(dirname($overrideClassFilePath)) || !is_writable($overrideClassFilePath)) { + throw new Exception(sprintf(Tools::displayError('file (%s) not writable'), $overrideClassFilePath)); } // Get a uniq id for the class, because you can override a class (or remove the override) twice in the same session and we need to avoid redeclaration @@ -2953,116 +2996,149 @@ abstract class ModuleCore } while (class_exists($classname.'OverrideOriginal_remove', false)); // Make a reflection of the override class and the module override class - $override_file = file($override_path); - $override_file = array_diff($override_file, array("\n")); - eval(preg_replace(array('#^\s*<\?(?:php)?#', '#class\s+'.$classname.'\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?#i'), array(' ', 'class '.$classname.'OverrideOriginal'.$uniq), implode('', $override_file))); - $override_class = new ReflectionClass($classname.'OverrideOriginal'.$uniq); + $overrideClassFileArray = file($overrideClassFilePath); - $module_file = file($path_override); - $module_file = array_diff($module_file, array("\n")); - eval(preg_replace(array('#^\s*<\?(?:php)?#', '#class\s+'.$classname.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i'), array(' ', 'class '.$classname.'Override'.$uniq), implode('', $module_file))); - $module_class = new ReflectionClass($classname.'Override'.$uniq); + $overrideClassName = $classname; + if ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE) { + require_once _PS_ROOT_DIR_.'/'.$parentClassFilePath; + + $overrideClassName = Tools::ucfirst($classname).'Override'; + } elseif ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE_CONTROLLER) { + require_once _PS_ROOT_DIR_.'/'.$parentClassFilePath; + + if ($classInfo['controller_type'] == 'admin') { + $overrideClassName = $classname.((strpos($classname, 'Controller') === false) ? 'Controller' : '').'Override'; + } else { + $overrideClassName = Tools::ucfirst($classInfo['module']).Tools::ucfirst($classInfo['class']).'ModuleFrontControllerOverride'; + } + } + + eval(preg_replace(array('#^\s*<\?(?:php)?#', '#class\s+'.$overrideClassName.'\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?#i'), array(' ', 'class '.$overrideClassName.'OverrideOriginal'.$uniq), implode('', $overrideClassFileArray))); + $reflectionClassOverride = new ReflectionClass($overrideClassName.'OverrideOriginal'.$uniq); + + $moduleClassFileArray = file($moduleClassFilePath); + $moduleClassFileArray = array_diff($moduleClassFileArray, array("\n")); + + eval(preg_replace(array('#^\s*<\?(?:php)?#', '#class\s+'.$overrideClassName.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i'), array(' ', 'class '.$overrideClassName.'Override'.$uniq), implode('', $moduleClassFileArray))); + $reflectionClassModule = new ReflectionClass($overrideClassName.'Override'.$uniq); // Check if none of the methods already exists in the override class - foreach ($module_class->getMethods() as $method) { - if ($override_class->hasMethod($method->getName())) { - $method_override = $override_class->getMethod($method->getName()); - if (preg_match('/module: (.*)/ism', $override_file[$method_override->getStartLine() - 5], $name) && preg_match('/date: (.*)/ism', $override_file[$method_override->getStartLine() - 4], $date) && preg_match('/version: ([0-9.]+)/ism', $override_file[$method_override->getStartLine() - 3], $version)) { + foreach ($reflectionClassModule->getMethods() as $method) { + if ($reflectionClassOverride->hasMethod($method->getName())) { + $overrideMethodInfo = $reflectionClassOverride->getMethod($method->getName()); + if (preg_match('/module: (.*)/ism', $overrideClassFileArray[$overrideMethodInfo->getStartLine() - 5], $name) && preg_match('/date: (.*)/ism', $overrideClassFileArray[$overrideMethodInfo->getStartLine() - 4], $date) && preg_match('/version: ([0-9.]+)/ism', $overrideClassFileArray[$overrideMethodInfo->getStartLine() - 3], $version)) { throw new Exception(sprintf(Tools::displayError('The method %1$s in the class %2$s is already overridden by the module %3$s version %4$s at %5$s.'), $method->getName(), $classname, $name[1], $version[1], $date[1])); } throw new Exception(sprintf(Tools::displayError('The method %1$s in the class %2$s is already overridden.'), $method->getName(), $classname)); } - $module_file = preg_replace('/((:?public|private|protected)\s+(static\s+)?function\s+(?:\b'.$method->getName().'\b))/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1", $module_file); - if ($module_file === null) { + $moduleClassFileArray = preg_replace('/((:?public|private|protected)\s+(static\s+)?function\s+(?:\b'.$method->getName().'\b))/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1", $moduleClassFileArray); + if ($moduleClassFileArray === null) { throw new Exception(sprintf(Tools::displayError('Failed to override method %1$s in class %2$s.'), $method->getName(), $classname)); } } // Check if none of the properties already exists in the override class - foreach ($module_class->getProperties() as $property) { - if ($override_class->hasProperty($property->getName())) { + foreach ($reflectionClassModule->getProperties() as $property) { + if ($reflectionClassOverride->hasProperty($property->getName())) { throw new Exception(sprintf(Tools::displayError('The property %1$s in the class %2$s is already defined.'), $property->getName(), $classname)); } - $module_file = preg_replace('/((?:public|private|protected)\s)\s*(static\s)?\s*(\$\b'.$property->getName().'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2$3", $module_file); - if ($module_file === null) { + $moduleClassFileArray = preg_replace('/((?:public|private|protected)\s)\s*(static\s)?\s*(\$\b'.$property->getName().'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2$3", $moduleClassFileArray); + if ($moduleClassFileArray === null) { throw new Exception(sprintf(Tools::displayError('Failed to override property %1$s in class %2$s.'), $property->getName(), $classname)); } } - foreach ($module_class->getConstants() as $constant => $value) { - if ($override_class->hasConstant($constant)) { + foreach ($reflectionClassModule->getConstants() as $constant => $value) { + if ($reflectionClassOverride->hasConstant($constant)) { throw new Exception(sprintf(Tools::displayError('The constant %1$s in the class %2$s is already defined.'), $constant, $classname)); } - $module_file = preg_replace('/(const\s)\s*(\b'.$constant.'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2", $module_file); - if ($module_file === null) { + $moduleClassFileArray = preg_replace('/(const\s)\s*(\b'.$constant.'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2", $moduleClassFileArray); + if ($moduleClassFileArray === null) { throw new Exception(sprintf(Tools::displayError('Failed to override constant %1$s in class %2$s.'), $constant, $classname)); } } // Insert the methods from module override in override - $copy_from = array_slice($module_file, $module_class->getStartLine() + 1, $module_class->getEndLine() - $module_class->getStartLine() - 2); - array_splice($override_file, $override_class->getEndLine() - 1, 0, $copy_from); - $code = implode('', $override_file); + $generatedCodeToCopy = array_slice($moduleClassFileArray, $reflectionClassModule->getStartLine() + 1, $reflectionClassModule->getEndLine() - $reflectionClassModule->getStartLine() - 2); - file_put_contents($override_path, preg_replace($pattern_escape_com, '', $code)); + // Merge code with that in destination override file + array_splice($overrideClassFileArray, $reflectionClassOverride->getEndLine() - 1, 0, $generatedCodeToCopy); + $code = implode('', $overrideClassFileArray); + + file_put_contents($overrideClassFilePath, preg_replace($patternEscapeCommon, '', $code)); } else { - $override_src = $path_override; + // Case 2: Override file does not exist just copy override information (methods, properties and constants) + $baseDirectoryName = dirname($overrideClassFilePath); - $override_dest = _PS_ROOT_DIR_.DIRECTORY_SEPARATOR.'override'.DIRECTORY_SEPARATOR.$path; - $dir_name = dirname($override_dest); - - if (!$orig_path && !is_dir($dir_name)) { + // Check if directory exists, if not, create + if (!is_dir($baseDirectoryName)) { $oldumask = umask(0000); - @mkdir($dir_name, 0777); + @mkdir($baseDirectoryName, 0777, true); // create subdirectories if needed umask($oldumask); } - if (!is_writable($dir_name)) { - throw new Exception(sprintf(Tools::displayError('directory (%s) not writable'), $dir_name)); + if (!is_writable($baseDirectoryName)) { + throw new Exception(sprintf(Tools::displayError('directory (%s) not writable'), $baseDirectoryName)); } - $module_file = file($override_src); - $module_file = array_diff($module_file, array("\n")); - if ($orig_path) { - do { - $uniq = uniqid(); - } while (class_exists($classname.'OverrideOriginal_remove', false)); - eval(preg_replace(array('#^\s*<\?(?:php)?#', '#class\s+'.$classname.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i'), array(' ', 'class '.$classname.'Override'.$uniq), implode('', $module_file))); - $module_class = new ReflectionClass($classname.'Override'.$uniq); + $moduleClassFileArray = file($moduleClassFilePath); // read file as array + $moduleClassFileArray = array_diff($moduleClassFileArray, array("\n")); // remove empty lines from array - // For each method found in the override, prepend a comment with the module name and version - foreach ($module_class->getMethods() as $method) { - $module_file = preg_replace('/((:?public|private|protected)\s+(static\s+)?function\s+(?:\b'.$method->getName().'\b))/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1", $module_file); - if ($module_file === null) { - throw new Exception(sprintf(Tools::displayError('Failed to override method %1$s in class %2$s.'), $method->getName(), $classname)); - } - } + do { + $uniq = uniqid(); + } while (class_exists($classname.'OverrideOriginal_remove', false)); - // Same loop for properties - foreach ($module_class->getProperties() as $property) { - $module_file = preg_replace('/((?:public|private|protected)\s)\s*(static\s)?\s*(\$\b'.$property->getName().'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2$3", $module_file); - if ($module_file === null) { - throw new Exception(sprintf(Tools::displayError('Failed to override property %1$s in class %2$s.'), $property->getName(), $classname)); - } - } + $overrideClassName = $classname; + if ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE) { + require_once _PS_ROOT_DIR_.'/'.$parentClassFilePath; - // Same loop for constants - foreach ($module_class->getConstants() as $constant => $value) { - $module_file = preg_replace('/(const\s)\s*(\b'.$constant.'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2", $module_file); - if ($module_file === null) { - throw new Exception(sprintf(Tools::displayError('Failed to override constant %1$s in class %2$s.'), $constant, $classname)); - } + $overrideClassName = Tools::ucfirst($classname).'Override'; + } elseif ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE_CONTROLLER) { + require_once _PS_ROOT_DIR_.'/'.$parentClassFilePath; + + if ($classInfo['controller_type'] == 'admin') { + $overrideClassName = $classname.((strpos($classname, 'Controller') === false) ? 'Controller' : '').'Override'; + } else { + $overrideClassName = Tools::ucfirst($classInfo['module']).Tools::ucfirst($classInfo['class']).'ModuleFrontControllerOverride'; } } - file_put_contents($override_dest, preg_replace($pattern_escape_com, '', $module_file)); + eval(preg_replace(array('#^\s*<\?(?:php)?#', '#class\s+'.$overrideClassName.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i'), array(' ', 'class '.$overrideClassName.'Override'.$uniq), implode('', $moduleClassFileArray))); + $reflectionClassModule = new ReflectionClass($overrideClassName.'Override'.$uniq); + + // For each method found in the override, prepend a comment with the module name and version + foreach ($reflectionClassModule->getMethods() as $method) { + $moduleClassFileArray = preg_replace('/((:?public|private|protected)\s+(static\s+)?function\s+(?:\b'.$method->getName().'\b))/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1", $moduleClassFileArray); + if ($moduleClassFileArray === null) { + throw new Exception(sprintf(Tools::displayError('Failed to override method %1$s in class %2$s.'), $method->getName(), $classname)); + } + } + + // For each property found in the override, prepend a comment with the module name and version + foreach ($reflectionClassModule->getProperties() as $property) { + $moduleClassFileArray = preg_replace('/((?:public|private|protected)\s)\s*(static\s)?\s*(\$\b'.$property->getName().'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2$3", $moduleClassFileArray); + if ($moduleClassFileArray === null) { + throw new Exception(sprintf(Tools::displayError('Failed to override property %1$s in class %2$s.'), $property->getName(), $classname)); + } + } + + // For each constant found in the override, prepend a comment with the module name and version + foreach ($reflectionClassModule->getConstants() as $constant => $value) { + $moduleClassFileArray = preg_replace('/(const\s)\s*(\b'.$constant.'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2", $moduleClassFileArray); + if ($moduleClassFileArray === null) { + throw new Exception(sprintf(Tools::displayError('Failed to override constant %1$s in class %2$s.'), $constant, $classname)); + } + } + + file_put_contents($overrideClassFilePath, preg_replace($patternEscapeCommon, '', $moduleClassFileArray)); // Re-generate the class index Tools::generateIndex(); } + return true; } @@ -3072,143 +3148,160 @@ abstract class ModuleCore * @param string $classname * @return bool */ - public function removeOverride($classname) + public function removeOverride($classInfo) { - $orig_path = $path = PrestaShopAutoload::getInstance()->getClassPath($classname.'Core'); + $classname = $classInfo['class']; - if ($orig_path && !$file = PrestaShopAutoload::getInstance()->getClassPath($classname)) { - return true; - } elseif (!$orig_path && Module::getModuleIdByName($classname)) { - $path = 'modules'.DIRECTORY_SEPARATOR.$classname.DIRECTORY_SEPARATOR.$classname.'.php'; + $parentClassFilePath = ''; + if ($classInfo['type'] == self::OVERRIDE_TYPE_CORE) { + $parentClassFilePath = PrestaShopAutoload::getInstance()->getClassPath($classname.'Core'); + } elseif ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE) { + $parentClassFilePath = 'modules/'.$classname.'/'.$classname.'.php'; + } elseif ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE_CONTROLLER) { + $parentClassFilePath = $classInfo['file']; } - // Check if override file is writable - if ($orig_path) { - $override_path = _PS_ROOT_DIR_.'/'.$file; - } else { - $override_path = _PS_OVERRIDE_DIR_.$path; - } + $overrideClassFilePath = _PS_ROOT_DIR_.'/override/'.$parentClassFilePath; - if (!is_file($override_path) || !is_writable($override_path)) { + if (!is_file($overrideClassFilePath) || !is_writable($overrideClassFilePath)) { return false; } - file_put_contents($override_path, preg_replace('#(\r\n|\r)#ism', "\n", file_get_contents($override_path))); + // save $overrideClassFilePath file with consistent newline character, "\n" + file_put_contents($overrideClassFilePath, preg_replace('#(\r\n|\r)#ism', "\n", file_get_contents($overrideClassFilePath))); - if ($orig_path) { - // Get a uniq id for the class, because you can override a class (or remove the override) twice in the same session and we need to avoid redeclaration - do { - $uniq = uniqid(); - } while (class_exists($classname.'OverrideOriginal_remove', false)); + // Get a uniq id for the class, because you can override a class (or remove the override) twice in the same session and we need to avoid redeclaration + do { + $uniq = uniqid(); + } while (class_exists($classname.'OverrideOriginal_remove', false)); - // Make a reflection of the override class and the module override class - $override_file = file($override_path); + // Make a reflection of the override class and the module override class + $overrideClassFileArray = file($overrideClassFilePath); - eval(preg_replace(array('#^\s*<\?(?:php)?#', '#class\s+'.$classname.'\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?#i'), array(' ', 'class '.$classname.'OverrideOriginal_remove'.$uniq), implode('', $override_file))); - $override_class = new ReflectionClass($classname.'OverrideOriginal_remove'.$uniq); + $overrideClassName = $classname; + if ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE) { + require_once _PS_ROOT_DIR_.'/'.$parentClassFilePath; - $module_file = file($this->getLocalPath().'override/'.$path); - eval(preg_replace(array('#^\s*<\?(?:php)?#', '#class\s+'.$classname.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i'), array(' ', 'class '.$classname.'Override_remove'.$uniq), implode('', $module_file))); - $module_class = new ReflectionClass($classname.'Override_remove'.$uniq); + $overrideClassName = Tools::ucfirst($classname).'Override'; + } elseif ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE_CONTROLLER) { + require_once _PS_ROOT_DIR_.'/'.$parentClassFilePath; - // Remove methods from override file - foreach ($module_class->getMethods() as $method) { - if (!$override_class->hasMethod($method->getName())) { - continue; - } - - $method = $override_class->getMethod($method->getName()); - $length = $method->getEndLine() - $method->getStartLine() + 1; - - $module_method = $module_class->getMethod($method->getName()); - $module_length = $module_method->getEndLine() - $module_method->getStartLine() + 1; - - $override_file_orig = $override_file; - - $orig_content = preg_replace('/\s/', '', implode('', array_splice($override_file, $method->getStartLine() - 1, $length, array_pad(array(), $length, '#--remove--#')))); - $module_content = preg_replace('/\s/', '', implode('', array_splice($module_file, $module_method->getStartLine() - 1, $length, array_pad(array(), $length, '#--remove--#')))); - - $replace = true; - if (preg_match('/\* module: ('.$this->name.')/ism', $override_file[$method->getStartLine() - 5])) { - $override_file[$method->getStartLine() - 6] = $override_file[$method->getStartLine() - 5] = $override_file[$method->getStartLine() - 4] = $override_file[$method->getStartLine() - 3] = $override_file[$method->getStartLine() - 2] = '#--remove--#'; - $replace = false; - } - - if (md5($module_content) != md5($orig_content) && $replace) { - $override_file = $override_file_orig; - } + if ($classInfo['controller_type'] == 'admin') { + $overrideClassName = $classname.((strpos($classname, 'Controller') === false) ? 'Controller' : '').'Override'; + } else { + $overrideClassName = Tools::ucfirst($classInfo['module']).Tools::ucfirst($classInfo['class']).'ModuleFrontControllerOverride'; } - - // Remove properties from override file - foreach ($module_class->getProperties() as $property) { - if (!$override_class->hasProperty($property->getName())) { - continue; - } - - // Replace the declaration line by #--remove--# - foreach ($override_file as $line_number => &$line_content) { - if (preg_match('/(public|private|protected)\s+(static\s+)?(\$)?'.$property->getName().'/i', $line_content)) { - if (preg_match('/\* module: ('.$this->name.')/ism', $override_file[$line_number - 4])) { - $override_file[$line_number - 5] = $override_file[$line_number - 4] = $override_file[$line_number - 3] = $override_file[$line_number - 2] = $override_file[$line_number - 1] = '#--remove--#'; - } - $line_content = '#--remove--#'; - break; - } - } - } - - // Remove properties from override file - foreach ($module_class->getConstants() as $constant => $value) { - if (!$override_class->hasConstant($constant)) { - continue; - } - - // Replace the declaration line by #--remove--# - foreach ($override_file as $line_number => &$line_content) { - if (preg_match('/(const)\s+(static\s+)?(\$)?'.$constant.'/i', $line_content)) { - if (preg_match('/\* module: ('.$this->name.')/ism', $override_file[$line_number - 4])) { - $override_file[$line_number - 5] = $override_file[$line_number - 4] = $override_file[$line_number - 3] = $override_file[$line_number - 2] = $override_file[$line_number - 1] = '#--remove--#'; - } - $line_content = '#--remove--#'; - break; - } - } - } - - $count = count($override_file); - for ($i = 0; $i < $count; ++$i) { - if (preg_match('/(^\s*\/\/.*)/i', $override_file[$i])) { - $override_file[$i] = '#--remove--#'; - } elseif (preg_match('/(^\s*\/\*)/i', $override_file[$i])) { - if (!preg_match('/(^\s*\* module:)/i', $override_file[$i + 1]) - && !preg_match('/(^\s*\* date:)/i', $override_file[$i + 2]) - && !preg_match('/(^\s*\* version:)/i', $override_file[$i + 3]) - && !preg_match('/(^\s*\*\/)/i', $override_file[$i + 4])) { - for (; $override_file[$i] && !preg_match('/(.*?\*\/)/i', $override_file[$i]); ++$i) { - $override_file[$i] = '#--remove--#'; - } - $override_file[$i] = '#--remove--#'; - } - } - } - - // Rewrite nice code - $code = ''; - foreach ($override_file as $line) { - if ($line == '#--remove--#') { - continue; - } - - $code .= $line; - } - - $to_delete = preg_match('/<\?(?:php)?\s+(?:abstract|interface)?\s*?class\s+'.$classname.'\s+extends\s+'.$classname.'Core\s*?[{]\s*?[}]/ism', $code); } - if (!isset($to_delete) || $to_delete) { - unlink($override_path); + eval(preg_replace(array('#^\s*<\?(?:php)?#', '#class\s+'.$overrideClassName.'\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?#i'), array(' ', 'class '.$overrideClassName.'Original_remove'.$uniq), implode('', $overrideClassFileArray))); + $reflectionClassOverride = new ReflectionClass($overrideClassName.'Original_remove'.$uniq); + + $moduleClassFileArray = file($this->getLocalPath().'override/'.$parentClassFilePath); + eval(preg_replace(array('#^\s*<\?(?:php)?#', '#class\s+'.$overrideClassName.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i'), array(' ', 'class '.$overrideClassName.'Override_remove'.$uniq), implode('', $moduleClassFileArray))); + $reflectionClassModule = new ReflectionClass($overrideClassName.'Override_remove'.$uniq); + + // Remove methods from override file + foreach ($reflectionClassModule->getMethods() as $method) { + if (!$reflectionClassOverride->hasMethod($method->getName())) { + continue; + } + + $method = $reflectionClassOverride->getMethod($method->getName()); + $length = $method->getEndLine() - $method->getStartLine() + 1; + + $moduleMethodInfo = $reflectionClassModule->getMethod($method->getName()); + + $overrideClassFileArrayOriginal = $overrideClassFileArray; + + $overrideFileContent = preg_replace('/\s/', '', implode('', array_splice($overrideClassFileArray, $method->getStartLine() - 1, $length, array_pad(array(), $length, '#--remove--#')))); + $moduleFileContent = preg_replace('/\s/', '', implode('', array_splice($moduleClassFileArray, $moduleMethodInfo->getStartLine() - 1, $length, array_pad(array(), $length, '#--remove--#')))); + + $replace = true; + if (preg_match('/\* module: ('.$this->name.')/ism', $overrideClassFileArray[$method->getStartLine() - 5])) { + $overrideClassFileArray[$method->getStartLine() - 6] = $overrideClassFileArray[$method->getStartLine() - 5] = $overrideClassFileArray[$method->getStartLine() - 4] = $overrideClassFileArray[$method->getStartLine() - 3] = $overrideClassFileArray[$method->getStartLine() - 2] = '#--remove--#'; + $replace = false; + } + + if (md5($moduleFileContent) != md5($overrideFileContent) && $replace) { + $overrideClassFileArray = $overrideClassFileArrayOriginal; + } + } + + // Remove properties from override file + foreach ($reflectionClassModule->getProperties() as $property) { + if (!$reflectionClassOverride->hasProperty($property->getName())) { + continue; + } + + // Replace the declaration line by #--remove--# + foreach ($overrideClassFileArray as $lineNumber => &$line_content) { + if (preg_match('/(public|private|protected)\s+(static\s+)?(\$)?'.$property->getName().'/i', $line_content)) { + if (preg_match('/\* module: ('.$this->name.')/ism', $overrideClassFileArray[$lineNumber - 4])) { + $overrideClassFileArray[$lineNumber - 5] = $overrideClassFileArray[$lineNumber - 4] = $overrideClassFileArray[$lineNumber - 3] = $overrideClassFileArray[$lineNumber - 2] = $overrideClassFileArray[$lineNumber - 1] = '#--remove--#'; + } + $line_content = '#--remove--#'; + break; + } + } + } + + // Remove properties from override file + foreach ($reflectionClassModule->getConstants() as $constant => $value) { + if (!$reflectionClassOverride->hasConstant($constant)) { + continue; + } + + // Replace the declaration line by #--remove--# + foreach ($overrideClassFileArray as $lineNumber => &$line_content) { + if (preg_match('/(const)\s+(static\s+)?(\$)?'.$constant.'/i', $line_content)) { + if (preg_match('/\* module: ('.$this->name.')/ism', $overrideClassFileArray[$lineNumber - 4])) { + $overrideClassFileArray[$lineNumber - 5] = $overrideClassFileArray[$lineNumber - 4] = $overrideClassFileArray[$lineNumber - 3] = $overrideClassFileArray[$lineNumber - 2] = $overrideClassFileArray[$lineNumber - 1] = '#--remove--#'; + } + $line_content = '#--remove--#'; + break; + } + } + } + + $count = count($overrideClassFileArray); + for ($i = 0; $i < $count; ++$i) { + if (preg_match('/(^\s*\/\/.*)/i', $overrideClassFileArray[$i])) { + $overrideClassFileArray[$i] = '#--remove--#'; + } elseif (preg_match('/(^\s*\/\*)/i', $overrideClassFileArray[$i])) { + if (!preg_match('/(^\s*\* module:)/i', $overrideClassFileArray[$i + 1]) + && !preg_match('/(^\s*\* date:)/i', $overrideClassFileArray[$i + 2]) + && !preg_match('/(^\s*\* version:)/i', $overrideClassFileArray[$i + 3]) + && !preg_match('/(^\s*\*\/)/i', $overrideClassFileArray[$i + 4])) { + for (; $overrideClassFileArray[$i] && !preg_match('/(.*?\*\/)/i', $overrideClassFileArray[$i]); ++$i) { + $overrideClassFileArray[$i] = '#--remove--#'; + } + $overrideClassFileArray[$i] = '#--remove--#'; + } + } + } + + // Rewrite nice code + $code = ''; + foreach ($overrideClassFileArray as $line) { + if ($line == '#--remove--#') { + continue; + } + + $code .= $line; + } + + // set regex for empty class to decide if override class must be deleted + $emptyClassRegex = '/<\?(?:php)?\s+(?:abstract|interface)?\s*?class\s+'.$overrideClassName.'\s+extends\s+'.$classname.(($classInfo['type'] == self::OVERRIDE_TYPE_CORE) ? 'Core' : '').'\s*?[{]\s*?[}]/ism'; + if ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE_CONTROLLER) { + $emptyClassRegex = '/<\?(?:php)?\s+(?:abstract|interface)?\s*?class\s+'.$overrideClassName.'\s+extends\s+'.substr($overrideClassName, 0, -8).'\s*?[{]\s*?[}]/ism'; + } + + $toDelete = preg_match($emptyClassRegex, $code); + + if (!isset($toDelete) || $toDelete) { + unlink($overrideClassFilePath); } else { - file_put_contents($override_path, $code); + file_put_contents($overrideClassFilePath, $code); } // Re-generate the class index From e9ec25d6bc1163721491e7648de1a8a44d1b8c59 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 26 Dec 2023 15:15:59 +0530 Subject: [PATCH 2/3] Refactoring --- classes/module/Module.php | 330 +++++++++++++++++++++----------------- 1 file changed, 182 insertions(+), 148 deletions(-) diff --git a/classes/module/Module.php b/classes/module/Module.php index c5cb2d64d..d0e98d740 100644 --- a/classes/module/Module.php +++ b/classes/module/Module.php @@ -2877,24 +2877,15 @@ abstract class ModuleCore return true; } - /** - * Install overrides files for the module - * - * @return bool - */ - public function installOverrides() + public function getOverrides() { if (!is_dir($this->getLocalPath().'override')) { - return true; + return array(); } - $result = true; + $overrides = array(); foreach (Tools::scandir($this->getLocalPath().'override', 'php', '', true) as $file) { $class = basename($file, '.php'); - if ($class == 'index') { - continue; - } - $classInfo = array('class' => $class, 'file' => $file, 'type' => null); if (PrestaShopAutoload::getInstance()->getClassPath($class.'Core')) { $classInfo['type'] = self::OVERRIDE_TYPE_CORE; @@ -2907,6 +2898,27 @@ abstract class ModuleCore } if ($classInfo['type']) { + $overrides[] = $classInfo; + } + } + + return $overrides; + } + + /** + * Install overrides files for the module + * + * @return bool + */ + public function installOverrides() + { + if (!is_dir($this->getLocalPath().'override')) { + return true; + } + + $result = true; + if ($overrideClassInfos = $this->getOverrides()) { + foreach ($overrideClassInfos as $classInfo) { $result &= $this->addOverride($classInfo); } } @@ -2926,24 +2938,8 @@ abstract class ModuleCore } $result = true; - foreach (Tools::scandir($this->getLocalPath().'override', 'php', '', true) as $file) { - $class = basename($file, '.php'); - if ($class == 'index') { - continue; - } - - $classInfo = array('class' => $class, 'file' => $file, 'type' => null); - if (PrestaShopAutoload::getInstance()->getClassPath($class.'Core')) { - $classInfo['type'] = self::OVERRIDE_TYPE_CORE; - } elseif (Module::getModuleIdByName($class)) { - $classInfo['type'] = self::OVERRIDE_TYPE_MODULE; - } elseif (preg_match('#^modules/([a-zA-Z0-9_-]+)/controllers/(admin|front)/([0-9a-zA-Z-_]+).php#', $file, $matches)) { - $classInfo['type'] = self::OVERRIDE_TYPE_MODULE_CONTROLLER; - $classInfo['module'] = $matches[1]; - $classInfo['controller_type'] = $matches[2]; - } - - if ($classInfo['type']) { + if ($overrideClassInfos = $this->getOverrides()) { + foreach ($overrideClassInfos as $classInfo) { $result &= $this->removeOverride($classInfo); } } @@ -2962,12 +2958,25 @@ abstract class ModuleCore $classname = $classInfo['class']; $parentClassFilePath = ''; + $overrideClassName = $classname; if ($classInfo['type'] == self::OVERRIDE_TYPE_CORE) { $parentClassFilePath = PrestaShopAutoload::getInstance()->getClassPath($classname.'Core'); } elseif ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE) { $parentClassFilePath = 'modules/'.$classname.'/'.$classname.'.php'; + + // require module main file + require_once _PS_ROOT_DIR_.'/'.$parentClassFilePath; + $overrideClassName = Tools::ucfirst($classname).'Override'; } elseif ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE_CONTROLLER) { $parentClassFilePath = $classInfo['file']; + + // require module controller file + require_once _PS_ROOT_DIR_.'/'.$parentClassFilePath; + if ($classInfo['controller_type'] == 'admin') { + $overrideClassName = $classname.((strpos($classname, 'Controller') === false) ? 'Controller' : '').'Override'; + } else { + $overrideClassName = Tools::ucfirst($classInfo['module']).Tools::ucfirst($classInfo['class']).'ModuleFrontControllerOverride'; + } } // the file in module which contains override information (methods, properties and constants) @@ -2997,69 +3006,42 @@ abstract class ModuleCore // Make a reflection of the override class and the module override class $overrideClassFileArray = file($overrideClassFilePath); + $overrideClassFileArray = array_diff($overrideClassFileArray, array("\n")); // remove empty lines from array - $overrideClassName = $classname; - if ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE) { - require_once _PS_ROOT_DIR_.'/'.$parentClassFilePath; - - $overrideClassName = Tools::ucfirst($classname).'Override'; - } elseif ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE_CONTROLLER) { - require_once _PS_ROOT_DIR_.'/'.$parentClassFilePath; - - if ($classInfo['controller_type'] == 'admin') { - $overrideClassName = $classname.((strpos($classname, 'Controller') === false) ? 'Controller' : '').'Override'; - } else { - $overrideClassName = Tools::ucfirst($classInfo['module']).Tools::ucfirst($classInfo['class']).'ModuleFrontControllerOverride'; - } - } - - eval(preg_replace(array('#^\s*<\?(?:php)?#', '#class\s+'.$overrideClassName.'\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?#i'), array(' ', 'class '.$overrideClassName.'OverrideOriginal'.$uniq), implode('', $overrideClassFileArray))); + eval( + preg_replace( + array( + '#^\s*<\?(?:php)?#', + '#class\s+'.$overrideClassName.'\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?#i' + ), + array( + ' ', + 'class '.$overrideClassName.'OverrideOriginal'.$uniq + ), + implode('', $overrideClassFileArray) + ) + ); $reflectionClassOverride = new ReflectionClass($overrideClassName.'OverrideOriginal'.$uniq); $moduleClassFileArray = file($moduleClassFilePath); - $moduleClassFileArray = array_diff($moduleClassFileArray, array("\n")); + $moduleClassFileArray = array_diff($moduleClassFileArray, array("\n")); // remove empty lines from array - eval(preg_replace(array('#^\s*<\?(?:php)?#', '#class\s+'.$overrideClassName.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i'), array(' ', 'class '.$overrideClassName.'Override'.$uniq), implode('', $moduleClassFileArray))); + eval( + preg_replace( + array( + '#^\s*<\?(?:php)?#', + '#class\s+'.$overrideClassName.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i' + ), + array( + ' ', + 'class '.$overrideClassName.'Override'.$uniq + ), + implode('', $moduleClassFileArray) + ) + ); $reflectionClassModule = new ReflectionClass($overrideClassName.'Override'.$uniq); - // Check if none of the methods already exists in the override class - foreach ($reflectionClassModule->getMethods() as $method) { - if ($reflectionClassOverride->hasMethod($method->getName())) { - $overrideMethodInfo = $reflectionClassOverride->getMethod($method->getName()); - if (preg_match('/module: (.*)/ism', $overrideClassFileArray[$overrideMethodInfo->getStartLine() - 5], $name) && preg_match('/date: (.*)/ism', $overrideClassFileArray[$overrideMethodInfo->getStartLine() - 4], $date) && preg_match('/version: ([0-9.]+)/ism', $overrideClassFileArray[$overrideMethodInfo->getStartLine() - 3], $version)) { - throw new Exception(sprintf(Tools::displayError('The method %1$s in the class %2$s is already overridden by the module %3$s version %4$s at %5$s.'), $method->getName(), $classname, $name[1], $version[1], $date[1])); - } - throw new Exception(sprintf(Tools::displayError('The method %1$s in the class %2$s is already overridden.'), $method->getName(), $classname)); - } - - $moduleClassFileArray = preg_replace('/((:?public|private|protected)\s+(static\s+)?function\s+(?:\b'.$method->getName().'\b))/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1", $moduleClassFileArray); - if ($moduleClassFileArray === null) { - throw new Exception(sprintf(Tools::displayError('Failed to override method %1$s in class %2$s.'), $method->getName(), $classname)); - } - } - - // Check if none of the properties already exists in the override class - foreach ($reflectionClassModule->getProperties() as $property) { - if ($reflectionClassOverride->hasProperty($property->getName())) { - throw new Exception(sprintf(Tools::displayError('The property %1$s in the class %2$s is already defined.'), $property->getName(), $classname)); - } - - $moduleClassFileArray = preg_replace('/((?:public|private|protected)\s)\s*(static\s)?\s*(\$\b'.$property->getName().'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2$3", $moduleClassFileArray); - if ($moduleClassFileArray === null) { - throw new Exception(sprintf(Tools::displayError('Failed to override property %1$s in class %2$s.'), $property->getName(), $classname)); - } - } - - foreach ($reflectionClassModule->getConstants() as $constant => $value) { - if ($reflectionClassOverride->hasConstant($constant)) { - throw new Exception(sprintf(Tools::displayError('The constant %1$s in the class %2$s is already defined.'), $constant, $classname)); - } - - $moduleClassFileArray = preg_replace('/(const\s)\s*(\b'.$constant.'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2", $moduleClassFileArray); - if ($moduleClassFileArray === null) { - throw new Exception(sprintf(Tools::displayError('Failed to override constant %1$s in class %2$s.'), $constant, $classname)); - } - } + $moduleClassFileArray = $this->getCommentedClass($moduleClassFileArray, $reflectionClassModule, $reflectionClassOverride, $overrideClassFileArray); // Insert the methods from module override in override $generatedCodeToCopy = array_slice($moduleClassFileArray, $reflectionClassModule->getStartLine() + 1, $reflectionClassModule->getEndLine() - $reflectionClassModule->getStartLine() - 2); @@ -3071,17 +3053,17 @@ abstract class ModuleCore file_put_contents($overrideClassFilePath, preg_replace($patternEscapeCommon, '', $code)); } else { // Case 2: Override file does not exist just copy override information (methods, properties and constants) - $baseDirectoryName = dirname($overrideClassFilePath); + $baseDirectoryPath = dirname($overrideClassFilePath); // Check if directory exists, if not, create - if (!is_dir($baseDirectoryName)) { + if (!is_dir($baseDirectoryPath)) { $oldumask = umask(0000); - @mkdir($baseDirectoryName, 0777, true); // create subdirectories if needed + @mkdir($baseDirectoryPath, 0777, true); // create subdirectories if needed umask($oldumask); } - if (!is_writable($baseDirectoryName)) { - throw new Exception(sprintf(Tools::displayError('directory (%s) not writable'), $baseDirectoryName)); + if (!is_writable($baseDirectoryPath)) { + throw new Exception(sprintf(Tools::displayError('directory (%s) not writable'), $baseDirectoryPath)); } $moduleClassFileArray = file($moduleClassFilePath); // read file as array @@ -3091,47 +3073,22 @@ abstract class ModuleCore $uniq = uniqid(); } while (class_exists($classname.'OverrideOriginal_remove', false)); - $overrideClassName = $classname; - if ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE) { - require_once _PS_ROOT_DIR_.'/'.$parentClassFilePath; - - $overrideClassName = Tools::ucfirst($classname).'Override'; - } elseif ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE_CONTROLLER) { - require_once _PS_ROOT_DIR_.'/'.$parentClassFilePath; - - if ($classInfo['controller_type'] == 'admin') { - $overrideClassName = $classname.((strpos($classname, 'Controller') === false) ? 'Controller' : '').'Override'; - } else { - $overrideClassName = Tools::ucfirst($classInfo['module']).Tools::ucfirst($classInfo['class']).'ModuleFrontControllerOverride'; - } - } - - eval(preg_replace(array('#^\s*<\?(?:php)?#', '#class\s+'.$overrideClassName.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i'), array(' ', 'class '.$overrideClassName.'Override'.$uniq), implode('', $moduleClassFileArray))); + eval( + preg_replace( + array( + '#^\s*<\?(?:php)?#', + '#class\s+'.$overrideClassName.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i' + ), + array( + ' ', + 'class '.$overrideClassName.'Override'.$uniq + ), + implode('', $moduleClassFileArray) + ) + ); $reflectionClassModule = new ReflectionClass($overrideClassName.'Override'.$uniq); - // For each method found in the override, prepend a comment with the module name and version - foreach ($reflectionClassModule->getMethods() as $method) { - $moduleClassFileArray = preg_replace('/((:?public|private|protected)\s+(static\s+)?function\s+(?:\b'.$method->getName().'\b))/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1", $moduleClassFileArray); - if ($moduleClassFileArray === null) { - throw new Exception(sprintf(Tools::displayError('Failed to override method %1$s in class %2$s.'), $method->getName(), $classname)); - } - } - - // For each property found in the override, prepend a comment with the module name and version - foreach ($reflectionClassModule->getProperties() as $property) { - $moduleClassFileArray = preg_replace('/((?:public|private|protected)\s)\s*(static\s)?\s*(\$\b'.$property->getName().'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2$3", $moduleClassFileArray); - if ($moduleClassFileArray === null) { - throw new Exception(sprintf(Tools::displayError('Failed to override property %1$s in class %2$s.'), $property->getName(), $classname)); - } - } - - // For each constant found in the override, prepend a comment with the module name and version - foreach ($reflectionClassModule->getConstants() as $constant => $value) { - $moduleClassFileArray = preg_replace('/(const\s)\s*(\b'.$constant.'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2", $moduleClassFileArray); - if ($moduleClassFileArray === null) { - throw new Exception(sprintf(Tools::displayError('Failed to override constant %1$s in class %2$s.'), $constant, $classname)); - } - } + $moduleClassFileArray = $this->getCommentedClass($moduleClassFileArray, $reflectionClassModule); file_put_contents($overrideClassFilePath, preg_replace($patternEscapeCommon, '', $moduleClassFileArray)); @@ -3142,6 +3099,57 @@ abstract class ModuleCore return true; } + private function getCommentedClass($moduleClassFileArray, $reflectionClassModule, $reflectionClassOverride = null, $overrideClassFileArray = null) + { + // Check if none of the methods already exists in the override class + foreach ($reflectionClassModule->getMethods() as $method) { + if ($reflectionClassOverride) { + if ($reflectionClassOverride->hasMethod($method->getName())) { + $overrideMethodInfo = $reflectionClassOverride->getMethod($method->getName()); + if (preg_match('/module: (.*)/ism', $overrideClassFileArray[$overrideMethodInfo->getStartLine() - 5], $name) && preg_match('/date: (.*)/ism', $overrideClassFileArray[$overrideMethodInfo->getStartLine() - 4], $date) && preg_match('/version: ([0-9.]+)/ism', $overrideClassFileArray[$overrideMethodInfo->getStartLine() - 3], $version)) { + throw new Exception(sprintf(Tools::displayError('The method %1$s in the class %2$s is already overridden by the module %3$s version %4$s at %5$s.'), $method->getName(), $classname, $name[1], $version[1], $date[1])); + } + throw new Exception(sprintf(Tools::displayError('The method %1$s in the class %2$s is already overridden.'), $method->getName(), $classname)); + } + } + + $moduleClassFileArray = preg_replace('/((:?public|private|protected)\s+(static\s+)?function\s+(?:\b'.$method->getName().'\b))/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1", $moduleClassFileArray); + if ($moduleClassFileArray === null) { + throw new Exception(sprintf(Tools::displayError('Failed to override method %1$s in class %2$s.'), $method->getName(), $classname)); + } + } + + // Check if none of the properties already exists in the override class + foreach ($reflectionClassModule->getProperties() as $property) { + if ($reflectionClassOverride) { + if ($reflectionClassOverride->hasProperty($property->getName())) { + throw new Exception(sprintf(Tools::displayError('The property %1$s in the class %2$s is already defined.'), $property->getName(), $classname)); + } + } + + $moduleClassFileArray = preg_replace('/((?:public|private|protected)\s)\s*(static\s)?\s*(\$\b'.$property->getName().'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2$3", $moduleClassFileArray); + if ($moduleClassFileArray === null) { + throw new Exception(sprintf(Tools::displayError('Failed to override property %1$s in class %2$s.'), $property->getName(), $classname)); + } + } + + // Check if none of the constants already exists in the override class + foreach ($reflectionClassModule->getConstants() as $constant => $value) { + if ($reflectionClassOverride) { + if ($reflectionClassOverride->hasConstant($constant)) { + throw new Exception(sprintf(Tools::displayError('The constant %1$s in the class %2$s is already defined.'), $constant, $classname)); + } + } + + $moduleClassFileArray = preg_replace('/(const\s)\s*(\b'.$constant.'\b)/ism', "/*\n * module: ".$this->name."\n * date: ".date('Y-m-d H:i:s')."\n * version: ".$this->version."\n */\n $1$2", $moduleClassFileArray); + if ($moduleClassFileArray === null) { + throw new Exception(sprintf(Tools::displayError('Failed to override constant %1$s in class %2$s.'), $constant, $classname)); + } + } + + return $moduleClassFileArray; + } + /** * Remove all methods in a module override from the override class * @@ -3153,16 +3161,32 @@ abstract class ModuleCore $classname = $classInfo['class']; $parentClassFilePath = ''; + $overrideClassName = $classname; if ($classInfo['type'] == self::OVERRIDE_TYPE_CORE) { $parentClassFilePath = PrestaShopAutoload::getInstance()->getClassPath($classname.'Core'); } elseif ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE) { $parentClassFilePath = 'modules/'.$classname.'/'.$classname.'.php'; + + require_once _PS_ROOT_DIR_.'/'.$parentClassFilePath; + $overrideClassName = Tools::ucfirst($classname).'Override'; } elseif ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE_CONTROLLER) { $parentClassFilePath = $classInfo['file']; + + require_once _PS_ROOT_DIR_.'/'.$parentClassFilePath; + if ($classInfo['controller_type'] == 'admin') { + $overrideClassName = $classname.((strpos($classname, 'Controller') === false) ? 'Controller' : '').'Override'; + } else { + $overrideClassName = Tools::ucfirst($classInfo['module']).Tools::ucfirst($classInfo['class']).'ModuleFrontControllerOverride'; + } } $overrideClassFilePath = _PS_ROOT_DIR_.'/override/'.$parentClassFilePath; + // if override file does not exist return true + if (!file_exists($overrideClassFilePath)) { + return true; + } + if (!is_file($overrideClassFilePath) || !is_writable($overrideClassFilePath)) { return false; } @@ -3177,27 +3201,37 @@ abstract class ModuleCore // Make a reflection of the override class and the module override class $overrideClassFileArray = file($overrideClassFilePath); + $moduleClassFileArray = array_diff($moduleClassFileArray, array("\n")); // remove empty lines from array - $overrideClassName = $classname; - if ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE) { - require_once _PS_ROOT_DIR_.'/'.$parentClassFilePath; - - $overrideClassName = Tools::ucfirst($classname).'Override'; - } elseif ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE_CONTROLLER) { - require_once _PS_ROOT_DIR_.'/'.$parentClassFilePath; - - if ($classInfo['controller_type'] == 'admin') { - $overrideClassName = $classname.((strpos($classname, 'Controller') === false) ? 'Controller' : '').'Override'; - } else { - $overrideClassName = Tools::ucfirst($classInfo['module']).Tools::ucfirst($classInfo['class']).'ModuleFrontControllerOverride'; - } - } - - eval(preg_replace(array('#^\s*<\?(?:php)?#', '#class\s+'.$overrideClassName.'\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?#i'), array(' ', 'class '.$overrideClassName.'Original_remove'.$uniq), implode('', $overrideClassFileArray))); + eval( + preg_replace( + array( + '#^\s*<\?(?:php)?#', + '#class\s+'.$overrideClassName.'\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?#i' + ), + array( + ' ', + 'class '.$overrideClassName.'Original_remove'.$uniq + ), + implode('', $overrideClassFileArray) + ) + ); $reflectionClassOverride = new ReflectionClass($overrideClassName.'Original_remove'.$uniq); $moduleClassFileArray = file($this->getLocalPath().'override/'.$parentClassFilePath); - eval(preg_replace(array('#^\s*<\?(?:php)?#', '#class\s+'.$overrideClassName.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i'), array(' ', 'class '.$overrideClassName.'Override_remove'.$uniq), implode('', $moduleClassFileArray))); + eval( + preg_replace( + array( + '#^\s*<\?(?:php)?#', + '#class\s+'.$overrideClassName.'(\s+extends\s+([a-z0-9_]+)(\s+implements\s+([a-z0-9_]+))?)?#i' + ), + array( + ' ', + 'class '.$overrideClassName.'Override_remove'.$uniq + ), + implode('', $moduleClassFileArray) + ) + ); $reflectionClassModule = new ReflectionClass($overrideClassName.'Override_remove'.$uniq); // Remove methods from override file @@ -3245,7 +3279,7 @@ abstract class ModuleCore } } - // Remove properties from override file + // Remove constants from override file foreach ($reflectionClassModule->getConstants() as $constant => $value) { if (!$reflectionClassOverride->hasConstant($constant)) { continue; From a83411e425de1c5811f0c7d50233b3102d66526c Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 26 Feb 2024 19:48:47 +0530 Subject: [PATCH 3/3] Clean up directory on module uninstall --- classes/module/Module.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/classes/module/Module.php b/classes/module/Module.php index d0e98d740..534666815 100644 --- a/classes/module/Module.php +++ b/classes/module/Module.php @@ -3334,6 +3334,9 @@ abstract class ModuleCore if (!isset($toDelete) || $toDelete) { unlink($overrideClassFilePath); + if ($classInfo['type'] == self::OVERRIDE_TYPE_MODULE_CONTROLLER) { + $this->cleanModulesOverrideDir($overrideClassFilePath); + } } else { file_put_contents($overrideClassFilePath, $code); } @@ -3344,6 +3347,21 @@ abstract class ModuleCore return true; } + private function cleanModulesOverrideDir($dir) + { + if (rtrim($dir, '/') == _PS_ROOT_DIR_.'/override/modules') { + return; + } + + $files = Tools::scandir($dir, ''); + $diffResult = array_diff($files, array('.', '..', 'index.php')); + + if (!count($diffResult)) { + Tools::deleteDirectory($dir); + $this->cleanModulesOverrideDir(dirname($dir)); + } + } + /** * Return the hooks list where this module can be hooked. *