diff --git a/composer.json b/composer.json index c47b689300..88ccd91052 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "laminas/laminas-mail": "^2.11", "laminas/laminas-servicemanager": "^3.5", "league/oauth2-google": "^4.0.1", - "nikic/php-parser": "~5.6.0", + "nikic/php-parser": "dev-master", "pear/archive_tar": "~1.4.14", "pelago/emogrifier": "^7.2.0", "psr/log": "^3.0.0", @@ -41,6 +41,10 @@ "symfony/stopwatch": "~6.4.0", "symfony/web-profiler-bundle": "~6.4.0" }, + "repositories": [{ + "type": "vcs", + "url": "https://github.com/Combodo/PHP-Parser" + }], "suggest": { "ext-libsodium": "Required to use the AttributeEncryptedString.", "ext-openssl": "Can be used as a polyfill if libsodium is not installed", diff --git a/composer.lock b/composer.lock index ce5ea70c06..64958ffeca 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a0021a80fcc9bccc3f577530893e2974", + "content-hash": "be4951ced82be6e0ac8c18fa6ddaafc9", "packages": [ { "name": "apereo/phpcas", @@ -1020,16 +1020,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.0", + "version": "dev-master", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" + "url": "https://github.com/Combodo/PHP-Parser.git", + "reference": "8ffc1239ff48ed2476b2672dbcc939fcdc5b0f7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", - "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "url": "https://api.github.com/repos/Combodo/PHP-Parser/zipball/8ffc1239ff48ed2476b2672dbcc939fcdc5b0f7a", + "reference": "8ffc1239ff48ed2476b2672dbcc939fcdc5b0f7a", "shasum": "" }, "require": { @@ -1042,13 +1042,14 @@ "ircmaxell/php-yacc": "^0.0.7", "phpunit/phpunit": "^9.0" }, + "default-branch": true, "bin": [ "bin/php-parse" ], "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -1056,7 +1057,11 @@ "PhpParser\\": "lib/PhpParser" } }, - "notification-url": "https://packagist.org/downloads/", + "autoload-dev": { + "psr-4": { + "PhpParser\\": "test/PhpParser/" + } + }, "license": [ "BSD-3-Clause" ], @@ -1071,10 +1076,9 @@ "php" ], "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0" + "source": "https://github.com/Combodo/PHP-Parser/tree/master" }, - "time": "2025-07-27T20:03:57+00:00" + "time": "2025-09-09T09:14:16+00:00" }, { "name": "paragonie/random_compat", @@ -5243,7 +5247,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": { + "nikic/php-parser": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 4a52b098d0..85a39b5873 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -2852,20 +2852,8 @@ public function UpdateIncludes($sModulesDir, $aSelectedModules = null) } } } - if (isset($aModuleInfo['installer'])) - { - $sModuleInstallerClass = $aModuleInfo['installer']; - if (!class_exists($sModuleInstallerClass)) - { - throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not a PHP class - Module: ".$aModuleInfo['label']); - } - if (!is_subclass_of($sModuleInstallerClass, 'ModuleInstallerAPI')) - { - throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not derived from 'ModuleInstallerAPI' - Module: ".$aModuleInfo['label']); - } - $aCallSpec = array($sModuleInstallerClass, 'BeforeWritingConfig'); - call_user_func_array($aCallSpec, array($this)); - } + + RunTimeEnvironment::CallInstallerHandler($aModuleInfo, "BeforeWritingConfig", [$this]); } } $this->SetAddOns($aAddOns); diff --git a/lib/autoload.php b/lib/autoload.php index 9ee03077e4..db10dc8675 100644 --- a/lib/autoload.php +++ b/lib/autoload.php @@ -14,7 +14,10 @@ echo $err; } } - throw new RuntimeException($err); + trigger_error( + $err, + E_USER_ERROR + ); } require_once __DIR__ . '/composer/autoload_real.php'; diff --git a/lib/bin/php-parse.bat b/lib/bin/php-parse.bat deleted file mode 100755 index 2c5096dc39..0000000000 --- a/lib/bin/php-parse.bat +++ /dev/null @@ -1,5 +0,0 @@ -@ECHO OFF -setlocal DISABLEDELAYEDEXPANSION -SET BIN_TARGET=%~dp0/php-parse -SET COMPOSER_RUNTIME_BIN_DIR=%~dp0 -php "%BIN_TARGET%" %* diff --git a/lib/composer/InstalledVersions.php b/lib/composer/InstalledVersions.php index 2052022fd8..07b32ed6ef 100644 --- a/lib/composer/InstalledVersions.php +++ b/lib/composer/InstalledVersions.php @@ -26,23 +26,12 @@ */ class InstalledVersions { - /** - * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to - * @internal - */ - private static $selfDir = null; - /** * @var mixed[]|null * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null */ private static $installed; - /** - * @var bool - */ - private static $installedIsLocalDir; - /** * @var bool|null */ @@ -320,24 +309,6 @@ public static function reload($data) { self::$installed = $data; self::$installedByVendor = array(); - - // when using reload, we disable the duplicate protection to ensure that self::$installed data is - // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, - // so we have to assume it does not, and that may result in duplicate data being returned when listing - // all installed packages for example - self::$installedIsLocalDir = false; - } - - /** - * @return string - */ - private static function getSelfDir() - { - if (self::$selfDir === null) { - self::$selfDir = strtr(__DIR__, '\\', '/'); - } - - return self::$selfDir; } /** @@ -354,9 +325,7 @@ private static function getInstalled() $copiedLocalDir = false; if (self::$canGetVendors) { - $selfDir = self::getSelfDir(); foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { - $vendorDir = strtr($vendorDir, '\\', '/'); if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (is_file($vendorDir.'/composer/installed.php')) { @@ -364,14 +333,11 @@ private static function getInstalled() $required = require $vendorDir.'/composer/installed.php'; self::$installedByVendor[$vendorDir] = $required; $installed[] = $required; - if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { + if (strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { self::$installed = $required; - self::$installedIsLocalDir = true; + $copiedLocalDir = true; } } - if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { - $copiedLocalDir = true; - } } } diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 5060546ef3..6d7c37ac53 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -6,13 +6,13 @@ $baseDir = dirname($vendorDir); return array( - 'AbstractApplicationUIExtension' => $baseDir . '/application/applicationextension.inc.php', - 'AbstractLoginFSMExtension' => $baseDir . '/application/applicationextension.inc.php', - 'AbstractPageUIBlockExtension' => $baseDir . '/application/applicationextension.inc.php', - 'AbstractPortalUIExtension' => $baseDir . '/application/applicationextension.inc.php', - 'AbstractPreferencesExtension' => $baseDir . '/application/applicationextension.inc.php', + 'AbstractApplicationUIExtension' => $baseDir . '/application/applicationextension/backoffice/AbstractApplicationUIExtension.php', + 'AbstractLoginFSMExtension' => $baseDir . '/application/applicationextension/login/AbstractLoginFSMExtension.php', + 'AbstractPageUIBlockExtension' => $baseDir . '/application/applicationextension/backoffice/AbstractPageUIBlockExtension.php', + 'AbstractPortalUIExtension' => $baseDir . '/application/applicationextension/portal/AbstractPortalUIExtension.php', + 'AbstractPreferencesExtension' => $baseDir . '/application/applicationextension/backoffice/AbstractPreferencesExtension.php', 'AbstractWeeklyScheduledProcess' => $baseDir . '/core/backgroundprocess.inc.php', - 'AbstractWelcomePopupExtension' => $baseDir . '/application/applicationextension.inc.php', + 'AbstractWelcomePopupExtension' => $baseDir . '/application/applicationextension/backoffice/AbstractWelcomePopupExtension.php', 'Action' => $baseDir . '/core/action.class.inc.php', 'ActionChecker' => $baseDir . '/core/userrights.class.inc.php', 'ActionEmail' => $baseDir . '/core/action.class.inc.php', @@ -21,7 +21,7 @@ 'ApplicationContext' => $baseDir . '/application/applicationcontext.class.inc.php', 'ApplicationException' => $baseDir . '/application/exceptions/ApplicationException.php', 'ApplicationMenu' => $baseDir . '/application/menunode.class.inc.php', - 'ApplicationPopupMenuItem' => $baseDir . '/application/applicationextension.inc.php', + 'ApplicationPopupMenuItem' => $baseDir . '/application/applicationextension/backoffice/ApplicationPopupMenuItem.php', 'Archive_Tar' => $vendorDir . '/pear/archive_tar/Archive/Tar.php', 'ArchivedObjectException' => $baseDir . '/application/exceptions/ArchivedObjectException.php', 'AsyncSendEmail' => $baseDir . '/core/asynctask.class.inc.php', @@ -468,6 +468,7 @@ 'Combodo\\iTop\\Form\\Validator\\NotEmptyExtKeyValidator' => $baseDir . '/sources/Form/Validator/NotEmptyExtKeyValidator.php', 'Combodo\\iTop\\Form\\Validator\\SelectObjectValidator' => $baseDir . '/sources/Form/Validator/SelectObjectValidator.php', 'Combodo\\iTop\\Kernel' => $baseDir . '/sources/Kernel.php', + 'Combodo\\iTop\\PhpParser\\Evaluation\\PhpExpressionEvaluator' => $baseDir . '/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php', 'Combodo\\iTop\\Renderer\\BlockRenderer' => $baseDir . '/sources/Renderer/BlockRenderer.php', 'Combodo\\iTop\\Renderer\\Bootstrap\\BsFieldRendererMappings' => $baseDir . '/sources/Renderer/Bootstrap/BsFieldRendererMappings.php', 'Combodo\\iTop\\Renderer\\Bootstrap\\BsFormRenderer' => $baseDir . '/sources/Renderer/Bootstrap/BsFormRenderer.php', @@ -753,8 +754,8 @@ 'InvalidPasswordAttributeOneWayPassword' => $baseDir . '/application/exceptions/InvalidPasswordAttributeOneWayPassword.php', 'IssueLog' => $baseDir . '/core/log.class.inc.php', 'ItopCounter' => $baseDir . '/core/counter.class.inc.php', - 'JSButtonItem' => $baseDir . '/application/applicationextension.inc.php', - 'JSPopupMenuItem' => $baseDir . '/application/applicationextension.inc.php', + 'JSButtonItem' => $baseDir . '/application/applicationextension/backoffice/JSButtonItem.php', + 'JSPopupMenuItem' => $baseDir . '/application/applicationextension/backoffice/JSPopupMenuItem.php', 'KeyValueStore' => $baseDir . '/core/counter.class.inc.php', 'Laminas\\Loader\\AutoloaderFactory' => $vendorDir . '/laminas/laminas-loader/src/AutoloaderFactory.php', 'Laminas\\Loader\\ClassMapAutoloader' => $vendorDir . '/laminas/laminas-loader/src/ClassMapAutoloader.php', @@ -1507,10 +1508,10 @@ 'RelationTypeIterator' => $baseDir . '/core/simplegraph.class.inc.php', 'ReportValue' => $baseDir . '/core/bulkchange.class.inc.php', 'RestDelete' => $baseDir . '/core/restservices.class.inc.php', - 'RestResult' => $baseDir . '/application/applicationextension.inc.php', + 'RestResult' => $baseDir . '/application/applicationextension/rest/RestResult.php', 'RestResultWithObjects' => $baseDir . '/core/restservices.class.inc.php', 'RestResultWithRelations' => $baseDir . '/core/restservices.class.inc.php', - 'RestUtils' => $baseDir . '/application/applicationextension.inc.php', + 'RestUtils' => $baseDir . '/application/applicationextension/rest/RestUtils.php', 'ReturnTypeWillChange' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', 'RotatingLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php', 'RowStatus' => $baseDir . '/core/bulkchange.class.inc.php', @@ -1623,7 +1624,7 @@ 'ScssPhp\\ScssPhp\\Warn' => $vendorDir . '/scssphp/scssphp/src/Warn.php', 'SearchMenuNode' => $baseDir . '/application/menunode.class.inc.php', 'SecurityException' => $baseDir . '/application/exceptions/SecurityException.php', - 'SeparatorPopupMenuItem' => $baseDir . '/application/applicationextension.inc.php', + 'SeparatorPopupMenuItem' => $baseDir . '/application/applicationextension/backoffice/SeparatorPopupMenuItem.php', 'SetupLog' => $baseDir . '/core/log.class.inc.php', 'Shortcut' => $baseDir . '/application/shortcut.class.inc.php', 'ShortcutContainerMenuNode' => $baseDir . '/application/menunode.class.inc.php', @@ -3155,8 +3156,8 @@ 'UIPasswordWidget' => $baseDir . '/application/ui.passwordwidget.class.inc.php', 'UISearchFormForeignKeys' => $baseDir . '/application/ui.searchformforeignkeys.class.inc.php', 'UIWizard' => $baseDir . '/application/uiwizard.class.inc.php', - 'URLButtonItem' => $baseDir . '/application/applicationextension.inc.php', - 'URLPopupMenuItem' => $baseDir . '/application/applicationextension.inc.php', + 'URLButtonItem' => $baseDir . '/application/applicationextension/backoffice/URLButtonItem.php', + 'URLPopupMenuItem' => $baseDir . '/application/applicationextension/backoffice/URLPopupMenuItem.php', 'UnaryExpression' => $baseDir . '/core/oql/expression.class.inc.php', 'UnknownClassOqlException' => $baseDir . '/core/oql/oqlinterpreter.class.inc.php', 'User' => $baseDir . '/core/userrights.class.inc.php', @@ -3184,43 +3185,43 @@ 'appUserPreferences' => $baseDir . '/application/user.preferences.class.inc.php', 'cmdbAbstractObject' => $baseDir . '/application/cmdbabstract.class.inc.php', 'cmdbDataGenerator' => $baseDir . '/core/data.generator.class.inc.php', - 'iApplicationUIExtension' => $baseDir . '/application/applicationextension.inc.php', + 'iApplicationUIExtension' => $baseDir . '/application/applicationextension/backoffice/iApplicationUIExtension.php', 'iAttributeNoGroupBy' => $baseDir . '/core/attributedef.class.inc.php', 'iBackgroundProcess' => $baseDir . '/core/backgroundprocess.inc.php', - 'iBackofficeDictEntriesExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iBackofficeDictEntriesPrefixesExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iBackofficeEarlyScriptExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iBackofficeInitScriptExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iBackofficeLinkedScriptsExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iBackofficeLinkedStylesheetsExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iBackofficeReadyScriptExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iBackofficeSassExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iBackofficeScriptExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iBackofficeStyleExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iBackupExtraFilesExtension' => $baseDir . '/application/applicationextension.inc.php', + 'iBackofficeDictEntriesExtension' => $baseDir . '/application/applicationextension/backoffice/iBackofficeDictEntriesExtension.php', + 'iBackofficeDictEntriesPrefixesExtension' => $baseDir . '/application/applicationextension/backoffice/iBackofficeDictEntriesPrefixesExtension.php', + 'iBackofficeEarlyScriptExtension' => $baseDir . '/application/applicationextension/backoffice/iBackofficeEarlyScriptExtension.php', + 'iBackofficeInitScriptExtension' => $baseDir . '/application/applicationextension/backoffice/iBackofficeInitScriptExtension.php', + 'iBackofficeLinkedScriptsExtension' => $baseDir . '/application/applicationextension/backoffice/iBackofficeLinkedScriptsExtension.php', + 'iBackofficeLinkedStylesheetsExtension' => $baseDir . '/application/applicationextension/backoffice/iBackofficeLinkedStylesheetsExtension.php', + 'iBackofficeReadyScriptExtension' => $baseDir . '/application/applicationextension/backoffice/iBackofficeReadyScriptExtension.php', + 'iBackofficeSassExtension' => $baseDir . '/application/applicationextension/backoffice/iBackofficeSassExtension.php', + 'iBackofficeScriptExtension' => $baseDir . '/application/applicationextension/backoffice/iBackofficeScriptExtension.php', + 'iBackofficeStyleExtension' => $baseDir . '/application/applicationextension/backoffice/iBackofficeStyleExtension.php', + 'iBackupExtraFilesExtension' => $baseDir . '/application/applicationextension/iBackupExtraFilesExtension.php', 'iCMDBChangeOp' => $baseDir . '/core/cmdbchangeop.class.inc.php', 'iDBObjectSetIterator' => $baseDir . '/core/dbobjectiterator.php', 'iDBObjectURLMaker' => $baseDir . '/application/applicationcontext.class.inc.php', 'iDisplay' => $baseDir . '/core/dbobject.class.php', - 'iFieldRendererMappingsExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iKPILoggerExtension' => $baseDir . '/application/applicationextension.inc.php', + 'iFieldRendererMappingsExtension' => $baseDir . '/application/applicationextension/backoffice/iFieldRendererMappingsExtension.php', + 'iKPILoggerExtension' => $baseDir . '/application/applicationextension/iKPILoggerExtension.php', 'iLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php', - 'iLoginExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iLoginFSMExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iLoginUIExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iLogoutExtension' => $baseDir . '/application/applicationextension.inc.php', + 'iLoginExtension' => $baseDir . '/application/applicationextension/login/iLoginExtension.php', + 'iLoginFSMExtension' => $baseDir . '/application/applicationextension/login/iLoginFSMExtension.php', + 'iLoginUIExtension' => $baseDir . '/application/applicationextension/login/iLoginUIExtension.php', + 'iLogoutExtension' => $baseDir . '/application/applicationextension/login/iLogoutExtension.php', 'iMetricComputer' => $baseDir . '/core/computing.inc.php', - 'iModuleExtension' => $baseDir . '/application/applicationextension.inc.php', + 'iModuleExtension' => $baseDir . '/application/applicationextension/iModuleExtension.php', 'iNewsroomProvider' => $baseDir . '/application/newsroomprovider.class.inc.php', 'iOnClassInitialization' => $baseDir . '/core/metamodelmodifier.inc.php', - 'iPageUIBlockExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iPopupMenuExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iPortalUIExtension' => $baseDir . '/application/applicationextension.inc.php', - 'iPreferencesExtension' => $baseDir . '/application/applicationextension.inc.php', + 'iPageUIBlockExtension' => $baseDir . '/application/applicationextension/backoffice/iPageUIBlockExtension.php', + 'iPopupMenuExtension' => $baseDir . '/application/applicationextension/backoffice/iPopupMenuExtension.php', + 'iPortalUIExtension' => $baseDir . '/application/applicationextension/portal/iPortalUIExtension.php', + 'iPreferencesExtension' => $baseDir . '/application/applicationextension/backoffice/iPreferencesExtension.php', 'iProcess' => $baseDir . '/core/backgroundprocess.inc.php', 'iQueryModifier' => $baseDir . '/core/querymodifier.class.inc.php', - 'iRestInputSanitizer' => $baseDir . '/application/applicationextension.inc.php', - 'iRestServiceProvider' => $baseDir . '/application/applicationextension.inc.php', + 'iRestInputSanitizer' => $baseDir . '/application/applicationextension/rest/iRestInputSanitizer.php', + 'iRestServiceProvider' => $baseDir . '/application/applicationextension/rest/iRestServiceProvider.php', 'iScheduledProcess' => $baseDir . '/core/backgroundprocess.inc.php', 'iSelfRegister' => $baseDir . '/core/userrights.class.inc.php', 'iTopConfigParser' => $baseDir . '/core/iTopConfigParser.php', @@ -3229,7 +3230,7 @@ 'iTopOwnershipToken' => $baseDir . '/core/ownershiplock.class.inc.php', 'iTopStandardURLMaker' => $baseDir . '/application/applicationcontext.class.inc.php', 'iTopXmlException' => $baseDir . '/application/exceptions/iTopXmlException.php', - 'iWelcomePopupExtension' => $baseDir . '/application/applicationextension.inc.php', + 'iWelcomePopupExtension' => $baseDir . '/application/applicationextension/backoffice/iWelcomePopupExtension.php', 'iWorkingTimeComputer' => $baseDir . '/core/computing.inc.php', 'lnkAuditCategoryToAuditDomain' => $baseDir . '/application/audit.domain.class.inc.php', 'lnkTriggerAction' => $baseDir . '/core/trigger.class.inc.php', diff --git a/lib/composer/autoload_psr4.php b/lib/composer/autoload_psr4.php index 18f7002d40..d57e598609 100644 --- a/lib/composer/autoload_psr4.php +++ b/lib/composer/autoload_psr4.php @@ -56,7 +56,7 @@ 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), 'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'), 'Pelago\\Emogrifier\\' => array($vendorDir . '/pelago/emogrifier/src'), - 'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-google/src', $vendorDir . '/league/oauth2-client/src'), + 'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src', $vendorDir . '/league/oauth2-google/src'), 'Laminas\\Validator\\' => array($vendorDir . '/laminas/laminas-validator/src'), 'Laminas\\Stdlib\\' => array($vendorDir . '/laminas/laminas-stdlib/src'), 'Laminas\\ServiceManager\\' => array($vendorDir . '/laminas/laminas-servicemanager/src'), diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 6f05aa431f..b599f8d234 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -317,8 +317,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f ), 'League\\OAuth2\\Client\\' => array ( - 0 => __DIR__ . '/..' . '/league/oauth2-google/src', - 1 => __DIR__ . '/..' . '/league/oauth2-client/src', + 0 => __DIR__ . '/..' . '/league/oauth2-client/src', + 1 => __DIR__ . '/..' . '/league/oauth2-google/src', ), 'Laminas\\Validator\\' => array ( @@ -384,13 +384,13 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f ); public static $classMap = array ( - 'AbstractApplicationUIExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'AbstractLoginFSMExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'AbstractPageUIBlockExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'AbstractPortalUIExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'AbstractPreferencesExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'AbstractApplicationUIExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/AbstractApplicationUIExtension.php', + 'AbstractLoginFSMExtension' => __DIR__ . '/../..' . '/application/applicationextension/login/AbstractLoginFSMExtension.php', + 'AbstractPageUIBlockExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/AbstractPageUIBlockExtension.php', + 'AbstractPortalUIExtension' => __DIR__ . '/../..' . '/application/applicationextension/portal/AbstractPortalUIExtension.php', + 'AbstractPreferencesExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/AbstractPreferencesExtension.php', 'AbstractWeeklyScheduledProcess' => __DIR__ . '/../..' . '/core/backgroundprocess.inc.php', - 'AbstractWelcomePopupExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'AbstractWelcomePopupExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/AbstractWelcomePopupExtension.php', 'Action' => __DIR__ . '/../..' . '/core/action.class.inc.php', 'ActionChecker' => __DIR__ . '/../..' . '/core/userrights.class.inc.php', 'ActionEmail' => __DIR__ . '/../..' . '/core/action.class.inc.php', @@ -399,7 +399,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'ApplicationContext' => __DIR__ . '/../..' . '/application/applicationcontext.class.inc.php', 'ApplicationException' => __DIR__ . '/../..' . '/application/exceptions/ApplicationException.php', 'ApplicationMenu' => __DIR__ . '/../..' . '/application/menunode.class.inc.php', - 'ApplicationPopupMenuItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'ApplicationPopupMenuItem' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/ApplicationPopupMenuItem.php', 'Archive_Tar' => __DIR__ . '/..' . '/pear/archive_tar/Archive/Tar.php', 'ArchivedObjectException' => __DIR__ . '/../..' . '/application/exceptions/ArchivedObjectException.php', 'AsyncSendEmail' => __DIR__ . '/../..' . '/core/asynctask.class.inc.php', @@ -846,6 +846,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\Form\\Validator\\NotEmptyExtKeyValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/NotEmptyExtKeyValidator.php', 'Combodo\\iTop\\Form\\Validator\\SelectObjectValidator' => __DIR__ . '/../..' . '/sources/Form/Validator/SelectObjectValidator.php', 'Combodo\\iTop\\Kernel' => __DIR__ . '/../..' . '/sources/Kernel.php', + 'Combodo\\iTop\\PhpParser\\Evaluation\\PhpExpressionEvaluator' => __DIR__ . '/../..' . '/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php', 'Combodo\\iTop\\Renderer\\BlockRenderer' => __DIR__ . '/../..' . '/sources/Renderer/BlockRenderer.php', 'Combodo\\iTop\\Renderer\\Bootstrap\\BsFieldRendererMappings' => __DIR__ . '/../..' . '/sources/Renderer/Bootstrap/BsFieldRendererMappings.php', 'Combodo\\iTop\\Renderer\\Bootstrap\\BsFormRenderer' => __DIR__ . '/../..' . '/sources/Renderer/Bootstrap/BsFormRenderer.php', @@ -1131,8 +1132,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'InvalidPasswordAttributeOneWayPassword' => __DIR__ . '/../..' . '/application/exceptions/InvalidPasswordAttributeOneWayPassword.php', 'IssueLog' => __DIR__ . '/../..' . '/core/log.class.inc.php', 'ItopCounter' => __DIR__ . '/../..' . '/core/counter.class.inc.php', - 'JSButtonItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'JSPopupMenuItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'JSButtonItem' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/JSButtonItem.php', + 'JSPopupMenuItem' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/JSPopupMenuItem.php', 'KeyValueStore' => __DIR__ . '/../..' . '/core/counter.class.inc.php', 'Laminas\\Loader\\AutoloaderFactory' => __DIR__ . '/..' . '/laminas/laminas-loader/src/AutoloaderFactory.php', 'Laminas\\Loader\\ClassMapAutoloader' => __DIR__ . '/..' . '/laminas/laminas-loader/src/ClassMapAutoloader.php', @@ -1885,10 +1886,10 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'RelationTypeIterator' => __DIR__ . '/../..' . '/core/simplegraph.class.inc.php', 'ReportValue' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php', 'RestDelete' => __DIR__ . '/../..' . '/core/restservices.class.inc.php', - 'RestResult' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'RestResult' => __DIR__ . '/../..' . '/application/applicationextension/rest/RestResult.php', 'RestResultWithObjects' => __DIR__ . '/../..' . '/core/restservices.class.inc.php', 'RestResultWithRelations' => __DIR__ . '/../..' . '/core/restservices.class.inc.php', - 'RestUtils' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'RestUtils' => __DIR__ . '/../..' . '/application/applicationextension/rest/RestUtils.php', 'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', 'RotatingLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php', 'RowStatus' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php', @@ -2001,7 +2002,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'ScssPhp\\ScssPhp\\Warn' => __DIR__ . '/..' . '/scssphp/scssphp/src/Warn.php', 'SearchMenuNode' => __DIR__ . '/../..' . '/application/menunode.class.inc.php', 'SecurityException' => __DIR__ . '/../..' . '/application/exceptions/SecurityException.php', - 'SeparatorPopupMenuItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'SeparatorPopupMenuItem' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/SeparatorPopupMenuItem.php', 'SetupLog' => __DIR__ . '/../..' . '/core/log.class.inc.php', 'Shortcut' => __DIR__ . '/../..' . '/application/shortcut.class.inc.php', 'ShortcutContainerMenuNode' => __DIR__ . '/../..' . '/application/menunode.class.inc.php', @@ -3533,8 +3534,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'UIPasswordWidget' => __DIR__ . '/../..' . '/application/ui.passwordwidget.class.inc.php', 'UISearchFormForeignKeys' => __DIR__ . '/../..' . '/application/ui.searchformforeignkeys.class.inc.php', 'UIWizard' => __DIR__ . '/../..' . '/application/uiwizard.class.inc.php', - 'URLButtonItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'URLPopupMenuItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'URLButtonItem' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/URLButtonItem.php', + 'URLPopupMenuItem' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/URLPopupMenuItem.php', 'UnaryExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php', 'UnknownClassOqlException' => __DIR__ . '/../..' . '/core/oql/oqlinterpreter.class.inc.php', 'User' => __DIR__ . '/../..' . '/core/userrights.class.inc.php', @@ -3562,43 +3563,43 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'appUserPreferences' => __DIR__ . '/../..' . '/application/user.preferences.class.inc.php', 'cmdbAbstractObject' => __DIR__ . '/../..' . '/application/cmdbabstract.class.inc.php', 'cmdbDataGenerator' => __DIR__ . '/../..' . '/core/data.generator.class.inc.php', - 'iApplicationUIExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'iApplicationUIExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iApplicationUIExtension.php', 'iAttributeNoGroupBy' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php', 'iBackgroundProcess' => __DIR__ . '/../..' . '/core/backgroundprocess.inc.php', - 'iBackofficeDictEntriesExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iBackofficeDictEntriesPrefixesExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iBackofficeEarlyScriptExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iBackofficeInitScriptExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iBackofficeLinkedScriptsExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iBackofficeLinkedStylesheetsExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iBackofficeReadyScriptExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iBackofficeSassExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iBackofficeScriptExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iBackofficeStyleExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iBackupExtraFilesExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'iBackofficeDictEntriesExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iBackofficeDictEntriesExtension.php', + 'iBackofficeDictEntriesPrefixesExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iBackofficeDictEntriesPrefixesExtension.php', + 'iBackofficeEarlyScriptExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iBackofficeEarlyScriptExtension.php', + 'iBackofficeInitScriptExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iBackofficeInitScriptExtension.php', + 'iBackofficeLinkedScriptsExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iBackofficeLinkedScriptsExtension.php', + 'iBackofficeLinkedStylesheetsExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iBackofficeLinkedStylesheetsExtension.php', + 'iBackofficeReadyScriptExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iBackofficeReadyScriptExtension.php', + 'iBackofficeSassExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iBackofficeSassExtension.php', + 'iBackofficeScriptExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iBackofficeScriptExtension.php', + 'iBackofficeStyleExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iBackofficeStyleExtension.php', + 'iBackupExtraFilesExtension' => __DIR__ . '/../..' . '/application/applicationextension/iBackupExtraFilesExtension.php', 'iCMDBChangeOp' => __DIR__ . '/../..' . '/core/cmdbchangeop.class.inc.php', 'iDBObjectSetIterator' => __DIR__ . '/../..' . '/core/dbobjectiterator.php', 'iDBObjectURLMaker' => __DIR__ . '/../..' . '/application/applicationcontext.class.inc.php', 'iDisplay' => __DIR__ . '/../..' . '/core/dbobject.class.php', - 'iFieldRendererMappingsExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iKPILoggerExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'iFieldRendererMappingsExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iFieldRendererMappingsExtension.php', + 'iKPILoggerExtension' => __DIR__ . '/../..' . '/application/applicationextension/iKPILoggerExtension.php', 'iLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php', - 'iLoginExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iLoginFSMExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iLoginUIExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iLogoutExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'iLoginExtension' => __DIR__ . '/../..' . '/application/applicationextension/login/iLoginExtension.php', + 'iLoginFSMExtension' => __DIR__ . '/../..' . '/application/applicationextension/login/iLoginFSMExtension.php', + 'iLoginUIExtension' => __DIR__ . '/../..' . '/application/applicationextension/login/iLoginUIExtension.php', + 'iLogoutExtension' => __DIR__ . '/../..' . '/application/applicationextension/login/iLogoutExtension.php', 'iMetricComputer' => __DIR__ . '/../..' . '/core/computing.inc.php', - 'iModuleExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'iModuleExtension' => __DIR__ . '/../..' . '/application/applicationextension/iModuleExtension.php', 'iNewsroomProvider' => __DIR__ . '/../..' . '/application/newsroomprovider.class.inc.php', 'iOnClassInitialization' => __DIR__ . '/../..' . '/core/metamodelmodifier.inc.php', - 'iPageUIBlockExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iPopupMenuExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iPortalUIExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iPreferencesExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'iPageUIBlockExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iPageUIBlockExtension.php', + 'iPopupMenuExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iPopupMenuExtension.php', + 'iPortalUIExtension' => __DIR__ . '/../..' . '/application/applicationextension/portal/iPortalUIExtension.php', + 'iPreferencesExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iPreferencesExtension.php', 'iProcess' => __DIR__ . '/../..' . '/core/backgroundprocess.inc.php', 'iQueryModifier' => __DIR__ . '/../..' . '/core/querymodifier.class.inc.php', - 'iRestInputSanitizer' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'iRestServiceProvider' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'iRestInputSanitizer' => __DIR__ . '/../..' . '/application/applicationextension/rest/iRestInputSanitizer.php', + 'iRestServiceProvider' => __DIR__ . '/../..' . '/application/applicationextension/rest/iRestServiceProvider.php', 'iScheduledProcess' => __DIR__ . '/../..' . '/core/backgroundprocess.inc.php', 'iSelfRegister' => __DIR__ . '/../..' . '/core/userrights.class.inc.php', 'iTopConfigParser' => __DIR__ . '/../..' . '/core/iTopConfigParser.php', @@ -3607,7 +3608,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'iTopOwnershipToken' => __DIR__ . '/../..' . '/core/ownershiplock.class.inc.php', 'iTopStandardURLMaker' => __DIR__ . '/../..' . '/application/applicationcontext.class.inc.php', 'iTopXmlException' => __DIR__ . '/../..' . '/application/exceptions/iTopXmlException.php', - 'iWelcomePopupExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'iWelcomePopupExtension' => __DIR__ . '/../..' . '/application/applicationextension/backoffice/iWelcomePopupExtension.php', 'iWorkingTimeComputer' => __DIR__ . '/../..' . '/core/computing.inc.php', 'lnkAuditCategoryToAuditDomain' => __DIR__ . '/../..' . '/application/audit.domain.class.inc.php', 'lnkTriggerAction' => __DIR__ . '/../..' . '/core/trigger.class.inc.php', diff --git a/lib/composer/installed.json b/lib/composer/installed.json index dea59f15f8..76902c2c0b 100644 --- a/lib/composer/installed.json +++ b/lib/composer/installed.json @@ -1053,17 +1053,17 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.0", - "version_normalized": "5.6.0.0", + "version": "dev-master", + "version_normalized": "dev-master", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" + "url": "https://github.com/Combodo/PHP-Parser.git", + "reference": "8ffc1239ff48ed2476b2672dbcc939fcdc5b0f7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", - "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "url": "https://api.github.com/repos/Combodo/PHP-Parser/zipball/8ffc1239ff48ed2476b2672dbcc939fcdc5b0f7a", + "reference": "8ffc1239ff48ed2476b2672dbcc939fcdc5b0f7a", "shasum": "" }, "require": { @@ -1076,14 +1076,15 @@ "ircmaxell/php-yacc": "^0.0.7", "phpunit/phpunit": "^9.0" }, - "time": "2025-07-27T20:03:57+00:00", + "time": "2025-09-09T09:14:16+00:00", + "default-branch": true, "bin": [ "bin/php-parse" ], "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "installation-source": "dist", @@ -1092,7 +1093,11 @@ "PhpParser\\": "lib/PhpParser" } }, - "notification-url": "https://packagist.org/downloads/", + "autoload-dev": { + "psr-4": { + "PhpParser\\": "test/PhpParser/" + } + }, "license": [ "BSD-3-Clause" ], @@ -1107,8 +1112,7 @@ "php" ], "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0" + "source": "https://github.com/Combodo/PHP-Parser/tree/master" }, "install-path": "../nikic/php-parser" }, diff --git a/lib/composer/installed.php b/lib/composer/installed.php index 3cbeb89498..dfaf79467f 100644 --- a/lib/composer/installed.php +++ b/lib/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'combodo/itop', 'pretty_version' => 'dev-develop', 'version' => 'dev-develop', - 'reference' => '965c9dba1435ed8605dc3d05f6021e77654137c9', + 'reference' => '9436a0221b1fda281dd098b54149127a7d3d23c3', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -22,7 +22,7 @@ 'combodo/itop' => array( 'pretty_version' => 'dev-develop', 'version' => 'dev-develop', - 'reference' => '965c9dba1435ed8605dc3d05f6021e77654137c9', + 'reference' => '9436a0221b1fda281dd098b54149127a7d3d23c3', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -143,12 +143,14 @@ 'dev_requirement' => false, ), 'nikic/php-parser' => array( - 'pretty_version' => 'v5.6.0', - 'version' => '5.6.0.0', - 'reference' => '221b0d0fdf1369c71047ad1d18bb5880017bbc56', + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => '8ffc1239ff48ed2476b2672dbcc939fcdc5b0f7a', 'type' => 'library', 'install_path' => __DIR__ . '/../nikic/php-parser', - 'aliases' => array(), + 'aliases' => array( + 0 => '5.x-dev', + ), 'dev_requirement' => false, ), 'paragonie/random_compat' => array( diff --git a/lib/composer/platform_check.php b/lib/composer/platform_check.php index 72145773d0..dee74e1736 100644 --- a/lib/composer/platform_check.php +++ b/lib/composer/platform_check.php @@ -36,7 +36,8 @@ echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; } } - throw new \RuntimeException( - 'Composer detected issues in your platform: ' . implode(' ', $issues) + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR ); } diff --git a/lib/nikic/php-parser/composer.json b/lib/nikic/php-parser/composer.json index b52f3ee57d..7a8591d48c 100644 --- a/lib/nikic/php-parser/composer.json +++ b/lib/nikic/php-parser/composer.json @@ -24,7 +24,7 @@ }, "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { diff --git a/lib/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php b/lib/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php index 9526787142..457bddc860 100644 --- a/lib/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php +++ b/lib/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php @@ -3,7 +3,10 @@ namespace PhpParser; use PhpParser\Node\Expr; +use PhpParser\Node\Identifier; +use PhpParser\Node\Name; use PhpParser\Node\Scalar; +use Exception; use function array_merge; @@ -30,6 +33,12 @@ class ConstExprEvaluator { /** @var callable|null */ private $fallbackEvaluator; + /** @var array $functionsWhiteList */ + private $functionsWhiteList; + + /** @var array $staticCallsWhitelist */ + private $staticCallsWhitelist; + /** * Create a constant expression evaluator. * @@ -44,6 +53,17 @@ public function __construct(?callable $fallbackEvaluator = null) { "Expression of type {$expr->getType()} cannot be evaluated" ); }; + + $this->functionsWhiteList = []; + $this->staticCallsWhitelist = []; + } + + public function setFunctionsWhitelist(array $functionsWhiteList): void { + $this->functionsWhiteList = $functionsWhiteList; + } + + public function setStaticCallsWhitelist(array $staticCallsWhitelist): void { + $this->staticCallsWhitelist = $staticCallsWhitelist; } /** @@ -115,6 +135,10 @@ private function evaluate(Expr $expr) { return $this->evaluateArray($expr); } + if ($expr instanceof Expr\Variable) { + return $this->evaluateVariable($expr); + } + // Unary operators if ($expr instanceof Expr\UnaryPlus) { return +$this->evaluate($expr->expr); @@ -145,6 +169,38 @@ private function evaluate(Expr $expr) { return $this->evaluateConstFetch($expr); } + if ($expr instanceof Expr\Isset_) { + return $this->evaluateIsset($expr); + } + + if ($expr instanceof Expr\ClassConstFetch) { + return $this->evaluateClassConstFetch($expr); + } + + if ($expr instanceof Expr\Cast) { + return $this->evaluateCast($expr); + } + + if ($expr instanceof Expr\StaticPropertyFetch) { + return $this->evaluateStaticPropertyFetch($expr); + } + + if ($expr instanceof Expr\FuncCall) { + return $this->evaluateFuncCall($expr); + } + + if ($expr instanceof Expr\StaticCall) { + return $this->evaluateStaticCall($expr); + } + + if ($expr instanceof Expr\NullsafePropertyFetch||$expr instanceof Expr\PropertyFetch) { + return $this->evaluatePropertyFetch($expr); + } + + if ($expr instanceof Expr\NullsafeMethodCall||$expr instanceof Expr\MethodCall) { + return $this->evaluateMethodCall($expr); + } + return ($this->fallbackEvaluator)($expr); } @@ -175,12 +231,15 @@ private function evaluateTernary(Expr\Ternary $expr) { /** @return mixed */ private function evaluateBinaryOp(Expr\BinaryOp $expr) { - if ($expr instanceof Expr\BinaryOp\Coalesce - && $expr->left instanceof Expr\ArrayDimFetch - ) { - // This needs to be special cased to respect BP_VAR_IS fetch semantics - return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)] - ?? $this->evaluate($expr->right); + if ($expr instanceof Expr\BinaryOp\Coalesce) { + try { + $var = $this->evaluate($expr->left); + } catch(\Throwable $t) { + //left expression cannot be evaluated (! isset for exeample) + return $this->evaluate($expr->right); + } + + return $var ?? $this->evaluate($expr->right); } // The evaluate() calls are repeated in each branch, because some of the operators are @@ -225,13 +284,272 @@ private function evaluateBinaryOp(Expr\BinaryOp $expr) { /** @return mixed */ private function evaluateConstFetch(Expr\ConstFetch $expr) { - $name = $expr->name->toLowerString(); - switch ($name) { - case 'null': return null; - case 'false': return false; - case 'true': return true; - } + try { + $name = $expr->name; + if(! is_string($name)) { + //PHP_VERSION_ID usecase + $name = $name->name; + } + + if (defined($name)) { + return constant($name); + } + } catch(\Throwable $t) {} return ($this->fallbackEvaluator)($expr); } + + /** @return bool */ + private function evaluateIsset(Expr\Isset_ $expr) { + try { + foreach ($expr->vars as $var) { + $var = $this->evaluate($var); + if (! isset($var)) { + return false; + } + } + + return true; + } catch(\Throwable $t) { + return false; + } + } + + /** @return mixed */ + private function evaluateClassConstFetch(Expr\ClassConstFetch $expr) { + try { + $classname = $expr->class->name; + $property = $expr->name->name; + + if ('class' === $property) { + return $classname; + } + + if (class_exists($classname)) { + $class = new \ReflectionClass($classname); + if (array_key_exists($property, $class->getConstants())) { + $oReflectionConstant = $class->getReflectionConstant($property); + if ($oReflectionConstant->isPublic()) { + return $class->getConstant($property); + } + } + } + } catch(\Throwable $t) {} + + return ($this->fallbackEvaluator)($expr); + } + + /** @return mixed */ + private function evaluateCast(Expr\Cast $expr) { + try { + $subexpr = $this->evaluate($expr->expr); + $type = get_class($expr); + switch ($type) { + case Expr\Cast\Array_::class: + return (array) $subexpr; + + case Expr\Cast\Bool_::class: + return (bool) $subexpr; + + case Expr\Cast\Double::class: + switch ($expr->getAttribute("kind")) { + case Expr\Cast\Double::KIND_DOUBLE: + return (double) $subexpr; + + case Expr\Cast\Double::KIND_FLOAT: + case Expr\Cast\Double::KIND_REAL: + return (float) $subexpr; + } + + break; + + case Expr\Cast\Int_::class: + return (int) $subexpr; + + case Expr\Cast\Object_::class: + return (object) $subexpr; + + case Expr\Cast\String_::class: + return (string) $subexpr; + } + } catch(\Throwable $t) {} + + return ($this->fallbackEvaluator)($expr); + } + + /** @return mixed */ + private function evaluateStaticPropertyFetch(Expr\StaticPropertyFetch $expr) { + try { + $classname = $expr->class->name; + if ($expr->name instanceof Identifier) { + $property = $expr->name->name; + } else { + $property = $this->evaluate($expr->name); + } + + if (class_exists($classname)) { + $class = new \ReflectionClass($classname); + if (array_key_exists($property, $class->getStaticProperties())) { + $oReflectionProperty = $class->getProperty($property); + if ($oReflectionProperty->isPublic()) { + return $class->getStaticPropertyValue($property); + } + } + } + } + catch (\Throwable $t) {} + + return ($this->fallbackEvaluator)($expr); + } + + /** @return mixed */ + private function evaluateFuncCall(Expr\FuncCall $expr) { + try { + $name = $expr->name; + if ($name instanceof Name) { + $function = $name->name; + } else { + $function = $this->evaluate($name); + } + + if (! in_array($function, $this->functionsWhiteList)) { + throw new Exception("FuncCall $function not supported"); + } + + $args=[]; + foreach ($expr->args as $arg) { + /** @var \PhpParser\Node\Arg $arg */ + $args[]=$arg->value->value; + } + + $reflection_function = new \ReflectionFunction($function); + return $reflection_function->invoke(...$args); + } + catch (\Throwable $t) {} + + return ($this->fallbackEvaluator)($expr); + } + + /** @return mixed */ + private function evaluateVariable(Expr\Variable $expr) { + try { + $name = $expr->name; + if (array_key_exists($name, get_defined_vars())) { + return $$name; + } + + if (array_key_exists($name, $GLOBALS)) { + global $$name; + return $$name; + } + } catch (\Throwable $t) { + } + + return ($this->fallbackEvaluator)($expr); + } + + /** @return mixed */ + private function evaluateStaticCall(Expr\StaticCall $expr) { + try { + $class = $expr->class->name; + if ($expr->name instanceof Identifier) { + $method = $expr->name->name; + } else { + $method = $this->evaluate($expr->name); + } + + $static_call_description = "$class::$method"; + if (! in_array($static_call_description, $this->staticCallsWhitelist)) { + throw new Exception("StaticCall $static_call_description not supported"); + } + + $args=[]; + foreach ($expr->args as $arg) { + /** @var \PhpParser\Node\Arg $arg */ + $args[]=$arg->value->value; + } + + $class = new \ReflectionClass($class); + $method = $class->getMethod($method); + if ($method->isPublic()) { + return $method->invokeArgs(null, $args); + } + } catch (\Throwable $t) {} + + return ($this->fallbackEvaluator)($expr); + } + + /** + * @param \PhpParser\Node\Expr\NullsafePropertyFetch|\PhpParser\Node\Expr\PropertyFetch $expr + * + * @return mixed + */ + private function evaluatePropertyFetch($expr) { + try { + $var = $this->evaluate($expr->var); + } catch (\Throwable $t) { + $var = null; + } + + if (! is_null($var)) { + try { + if ($expr->name instanceof Identifier) { + $name = $expr->name->name; + } else { + $name = $this->evaluate($expr->name); + } + + $reflectionClass = new \ReflectionClass(get_class($var)); + $property = $reflectionClass->getProperty($name); + if ($property->isPublic()) { + return $property->getValue($var); + } + } + catch (\Throwable $t) {} + } else if ($expr instanceof Expr\NullsafePropertyFetch) { + return null; + } + + return ($this->fallbackEvaluator)($expr); + } + + /** + * @param \PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\NullsafeMethodCall $expr + * + * @return mixed + */ + private function evaluateMethodCall($expr) { + try { + $var = $this->evaluate($expr->var); + } catch (\Throwable $t) { + $var = null; + } + + if (! is_null($var)) { + try { + $args = []; + foreach ($expr->args as $arg) { + /** @var \PhpParser\Node\Arg $arg */ + $args[] = $arg->value->value; + } + + if ($expr->name instanceof Identifier) { + $name = $expr->name->name; + } else { + $name = $this->evaluate($expr->name); + } + + $reflectionClass = new \ReflectionClass(get_class($var)); + $method = $reflectionClass->getMethod($name); + if ($method->isPublic()) { + return $method->invokeArgs($var, $args); + } + } + catch (\Throwable $t) {} + } else if ($expr instanceof Expr\NullsafeMethodCall) { + return null; + } + + return ($this->fallbackEvaluator)($expr); + } } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php index be9d0708d1..55ef163502 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php @@ -5,7 +5,11 @@ require __DIR__ . '/../ArrayItem.php'; if (false) { - // For classmap-authoritative support. + /** + * For classmap-authoritative support. + * + * @deprecated use \PhpParser\Node\ArrayItem instead. + */ class ArrayItem extends \PhpParser\Node\ArrayItem { } } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.php b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.php index 3aed497c71..ca02586dd1 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.php @@ -5,6 +5,10 @@ use PhpParser\Node\Expr\Cast; class Bool_ extends Cast { + // For use in "kind" attribute + public const KIND_BOOL = 1; // "bool" syntax + public const KIND_BOOLEAN = 2; // "boolean" syntax + public function getType(): string { return 'Expr_Cast_Bool'; } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.php b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.php index 20744b9b0f..7e5d4a36a2 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.php @@ -5,6 +5,10 @@ use PhpParser\Node\Expr\Cast; class Int_ extends Cast { + // For use in "kind" attribute + public const KIND_INT = 1; // "int" syntax + public const KIND_INTEGER = 2; // "integer" syntax + public function getType(): string { return 'Expr_Cast_Int'; } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.php b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.php index c7605ab82c..a8f8c25486 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.php @@ -5,6 +5,10 @@ use PhpParser\Node\Expr\Cast; class String_ extends Cast { + // For use in "kind" attribute + public const KIND_STRING = 1; // "string" syntax + public const KIND_BINARY = 2; // "binary" syntax + public function getType(): string { return 'Expr_Cast_String'; } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php index b395617202..279aa26adc 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.php @@ -5,7 +5,11 @@ require __DIR__ . '/../ClosureUse.php'; if (false) { - // For classmap-authoritative support. + /** + * For classmap-authoritative support. + * + * @deprecated use \PhpParser\Node\ClosureUse instead. + */ class ClosureUse extends \PhpParser\Node\ClosureUse { } } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Param.php b/lib/nikic/php-parser/lib/PhpParser/Node/Param.php index 57d15b7b0c..6634930bd5 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Param.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Param.php @@ -77,7 +77,7 @@ public function isPublic(): bool { return true; } - if ($this->hooks === []) { + if (!$this->isPromoted()) { return false; } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php b/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php index aaafc5fece..5b5e410d9c 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php @@ -5,7 +5,11 @@ require __DIR__ . '/Float_.php'; if (false) { - // For classmap-authoritative support. + /** + * For classmap-authoritative support. + * + * @deprecated use \PhpParser\Node\Scalar\Float_ instead. + */ class DNumber extends Float_ { } } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php b/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php index a97545e7bd..57fccebc6e 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php @@ -5,7 +5,11 @@ require __DIR__ . '/InterpolatedString.php'; if (false) { - // For classmap-authoritative support. + /** + * For classmap-authoritative support. + * + * @deprecated use \PhpParser\Node\Scalar\InterpolatedString instead. + */ class Encapsed extends InterpolatedString { } } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php b/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php index 7ef5fb0c09..d67011a0f7 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.php @@ -7,7 +7,11 @@ require __DIR__ . '/../InterpolatedStringPart.php'; if (false) { - // For classmap-authoritative support. + /** + * For classmap-authoritative support. + * + * @deprecated use \PhpParser\Node\InterpolatedStringPart instead. + */ class EncapsedStringPart extends InterpolatedStringPart { } } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php b/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php index 0d1287105d..868d78f860 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php @@ -5,7 +5,11 @@ require __DIR__ . '/Int_.php'; if (false) { - // For classmap-authoritative support. + /** + * For classmap-authoritative support. + * + * @deprecated use \PhpParser\Node\Scalar\Int_ instead. + */ class LNumber extends Int_ { } } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php b/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php index c965366d43..b8d169347e 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php @@ -124,7 +124,7 @@ function ($matches) { // If it overflowed to float, treat as INT_MAX, it will throw an error anyway. return self::codePointToUtf8(\is_int($dec) ? $dec : \PHP_INT_MAX); } else { - return chr(octdec($str)); + return chr(octdec($str) & 255); } }, $str diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php index 1a3c3616d4..c18613438f 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php @@ -7,7 +7,11 @@ require __DIR__ . '/../DeclareItem.php'; if (false) { - // For classmap-authoritative support. + /** + * For classmap-authoritative support. + * + * @deprecated use \PhpParser\Node\DeclareItem instead. + */ class DeclareDeclare extends DeclareItem { } } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php index fe7c99736b..62556e709a 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.php @@ -7,7 +7,11 @@ require __DIR__ . '/../PropertyItem.php'; if (false) { - // For classmap-authoritative support. + /** + * For classmap-authoritative support. + * + * @deprecated use \PhpParser\Node\PropertyItem instead. + */ class PropertyProperty extends PropertyItem { } } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php index 6775b9597f..a3c5fa6e6f 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.php @@ -5,7 +5,11 @@ require __DIR__ . '/../StaticVar.php'; if (false) { - // For classmap-authoritative support. + /** + * For classmap-authoritative support. + * + * @deprecated use \PhpParser\Node\StaticVar instead. + */ class StaticVar extends \PhpParser\Node\StaticVar { } } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php index b14fbd6ea1..9e504f8922 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.php @@ -7,7 +7,11 @@ require __DIR__ . '/../UseItem.php'; if (false) { - // For classmap-authoritative support. + /** + * For classmap-authoritative support. + * + * @deprecated use \PhpParser\Node\UseItem instead. + */ class UseUse extends UseItem { } } diff --git a/lib/nikic/php-parser/lib/PhpParser/Parser/Php7.php b/lib/nikic/php-parser/lib/PhpParser/Parser/Php7.php index 2d29b6c8c1..7784e8806c 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Parser/Php7.php +++ b/lib/nikic/php-parser/lib/PhpParser/Parser/Php7.php @@ -2478,7 +2478,9 @@ protected function initReduceCallbacks(): void { $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); }, 487 => static function ($self, $stackPos) { - $self->semValue = new Expr\Cast\Int_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getIntCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\Int_($self->semStack[$stackPos-(2-2)], $attrs); }, 488 => static function ($self, $stackPos) { $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); @@ -2486,7 +2488,9 @@ protected function initReduceCallbacks(): void { $self->semValue = new Expr\Cast\Double($self->semStack[$stackPos-(2-2)], $attrs); }, 489 => static function ($self, $stackPos) { - $self->semValue = new Expr\Cast\String_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getStringCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\String_($self->semStack[$stackPos-(2-2)], $attrs); }, 490 => static function ($self, $stackPos) { $self->semValue = new Expr\Cast\Array_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); @@ -2495,7 +2499,9 @@ protected function initReduceCallbacks(): void { $self->semValue = new Expr\Cast\Object_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); }, 492 => static function ($self, $stackPos) { - $self->semValue = new Expr\Cast\Bool_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getBoolCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\Bool_($self->semStack[$stackPos-(2-2)], $attrs); }, 493 => static function ($self, $stackPos) { $self->semValue = new Expr\Cast\Unset_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); @@ -2686,10 +2692,10 @@ protected function initReduceCallbacks(): void { 561 => static function ($self, $stackPos) { $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Expr\Array_::KIND_LONG; $self->semValue = new Expr\Array_($self->semStack[$stackPos-(4-3)], $attrs); - $self->createdArrays->attach($self->semValue); + $self->createdArrays->offsetSet($self->semValue); }, 562 => static function ($self, $stackPos) { - $self->semValue = $self->semStack[$stackPos-(1-1)]; $self->createdArrays->attach($self->semValue); + $self->semValue = $self->semStack[$stackPos-(1-1)]; $self->createdArrays->offsetSet($self->semValue); }, 563 => static function ($self, $stackPos) { $self->semValue = Scalar\String_::fromString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]), $self->phpVersion->supportsUnicodeEscapes()); diff --git a/lib/nikic/php-parser/lib/PhpParser/Parser/Php8.php b/lib/nikic/php-parser/lib/PhpParser/Parser/Php8.php index 8745e67cb0..7a15cef564 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Parser/Php8.php +++ b/lib/nikic/php-parser/lib/PhpParser/Parser/Php8.php @@ -2479,7 +2479,9 @@ protected function initReduceCallbacks(): void { $self->semValue = new Expr\Include_($self->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); }, 490 => static function ($self, $stackPos) { - $self->semValue = new Expr\Cast\Int_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getIntCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\Int_($self->semStack[$stackPos-(2-2)], $attrs); }, 491 => static function ($self, $stackPos) { $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); @@ -2487,7 +2489,9 @@ protected function initReduceCallbacks(): void { $self->semValue = new Expr\Cast\Double($self->semStack[$stackPos-(2-2)], $attrs); }, 492 => static function ($self, $stackPos) { - $self->semValue = new Expr\Cast\String_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getStringCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\String_($self->semStack[$stackPos-(2-2)], $attrs); }, 493 => static function ($self, $stackPos) { $self->semValue = new Expr\Cast\Array_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); @@ -2496,7 +2500,9 @@ protected function initReduceCallbacks(): void { $self->semValue = new Expr\Cast\Object_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); }, 495 => static function ($self, $stackPos) { - $self->semValue = new Expr\Cast\Bool_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); + $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos]); + $attrs['kind'] = $self->getBoolCastKind($self->semStack[$stackPos-(2-1)]); + $self->semValue = new Expr\Cast\Bool_($self->semStack[$stackPos-(2-2)], $attrs); }, 496 => static function ($self, $stackPos) { $self->semValue = new Expr\Cast\Unset_($self->semStack[$stackPos-(2-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(2-1)], $self->tokenEndStack[$stackPos])); @@ -2687,10 +2693,10 @@ protected function initReduceCallbacks(): void { 564 => static function ($self, $stackPos) { $attrs = $self->getAttributes($self->tokenStartStack[$stackPos-(4-1)], $self->tokenEndStack[$stackPos]); $attrs['kind'] = Expr\Array_::KIND_LONG; $self->semValue = new Expr\Array_($self->semStack[$stackPos-(4-3)], $attrs); - $self->createdArrays->attach($self->semValue); + $self->createdArrays->offsetSet($self->semValue); }, 565 => static function ($self, $stackPos) { - $self->semValue = $self->semStack[$stackPos-(1-1)]; $self->createdArrays->attach($self->semValue); + $self->semValue = $self->semStack[$stackPos-(1-1)]; $self->createdArrays->offsetSet($self->semValue); }, 566 => static function ($self, $stackPos) { $self->semValue = Scalar\String_::fromString($self->semStack[$stackPos-(1-1)], $self->getAttributes($self->tokenStartStack[$stackPos-(1-1)], $self->tokenEndStack[$stackPos]), $self->phpVersion->supportsUnicodeEscapes()); diff --git a/lib/nikic/php-parser/lib/PhpParser/ParserAbstract.php b/lib/nikic/php-parser/lib/PhpParser/ParserAbstract.php index cf643ee892..c10a0c9757 100644 --- a/lib/nikic/php-parser/lib/PhpParser/ParserAbstract.php +++ b/lib/nikic/php-parser/lib/PhpParser/ParserAbstract.php @@ -736,6 +736,33 @@ protected function getFloatCastKind(string $cast): int { return Double::KIND_DOUBLE; } + protected function getIntCastKind(string $cast): int { + $cast = strtolower($cast); + if (strpos($cast, 'integer') !== false) { + return Expr\Cast\Int_::KIND_INTEGER; + } + + return Expr\Cast\Int_::KIND_INT; + } + + protected function getBoolCastKind(string $cast): int { + $cast = strtolower($cast); + if (strpos($cast, 'boolean') !== false) { + return Expr\Cast\Bool_::KIND_BOOLEAN; + } + + return Expr\Cast\Bool_::KIND_BOOL; + } + + protected function getStringCastKind(string $cast): int { + $cast = strtolower($cast); + if (strpos($cast, 'binary') !== false) { + return Expr\Cast\String_::KIND_BINARY; + } + + return Expr\Cast\String_::KIND_STRING; + } + /** @param array $attributes */ protected function parseLNumber(string $str, array $attributes, bool $allowInvalidOctal = false): Int_ { try { @@ -976,7 +1003,7 @@ protected function createEmptyElemAttributes(int $tokenPos): array { } protected function fixupArrayDestructuring(Array_ $node): Expr\List_ { - $this->createdArrays->detach($node); + $this->createdArrays->offsetUnset($node); return new Expr\List_(array_map(function (Node\ArrayItem $item) { if ($item->value instanceof Expr\Error) { // We used Error as a placeholder for empty elements, which are legal for destructuring. diff --git a/setup/extensionsmap.class.inc.php b/setup/extensionsmap.class.inc.php index 440608f12e..851a63bdd6 100644 --- a/setup/extensionsmap.class.inc.php +++ b/setup/extensionsmap.class.inc.php @@ -11,52 +11,52 @@ class iTopExtension const SOURCE_WIZARD = 'datamodels'; const SOURCE_MANUAL = 'extensions'; const SOURCE_REMOTE = 'data'; - + /** * @var string */ public $sCode; - + /** * @var string */ public $sVersion; - - /** + + /** * @var string */ public $sInstalledVersion; - -/** + + /** * @var string */ public $sLabel; - + /** * @var string */ public $sDescription; - + /** * @var string */ public $sSource; - + /** * @var bool */ public $bMandatory; - + /** * @var string */ public $sMoreInfoUrl; - + /** * @var bool */ public $bMarkedAsChosen; - + /** * @var bool */ @@ -87,7 +87,7 @@ class iTopExtension * @var string[] */ public $aMissingDependencies; - + public function __construct() { $this->sCode = ''; @@ -120,13 +120,13 @@ class iTopExtensionsMap * @return void */ protected $aExtensions; - + /** * The list of directories browsed using the ReadDir method when building the map * @var string[] */ protected $aScannedDirs; - + public function __construct($sFromEnvironment = 'production', $bNormalizeOldExtensions = true, $aExtraDirs = array()) { $this->aExtensions = array(); @@ -134,7 +134,7 @@ public function __construct($sFromEnvironment = 'production', $bNormalizeOldExte $this->ScanDisk($sFromEnvironment); foreach($aExtraDirs as $sDir) { - $this->ReadDir($sDir, iTopExtension::SOURCE_REMOTE); + $this->ReadDir($sDir, iTopExtension::SOURCE_REMOTE); } $this->CheckDependencies($sFromEnvironment); if ($bNormalizeOldExtensions) @@ -142,7 +142,7 @@ public function __construct($sFromEnvironment = 'production', $bNormalizeOldExte $this->NormalizeOldExtensions(); } } - + /** * Populate the list of available (pseudo)extensions by scanning the disk * where the iTop files are located @@ -158,7 +158,7 @@ protected function ScanDisk($sEnvironment) $this->ReadDir(APPROOT.'/extensions', iTopExtension::SOURCE_MANUAL); $this->ReadDir(APPROOT.'/data/'.$sEnvironment.'-modules', iTopExtension::SOURCE_REMOTE); } - + /** * Read the information contained in the "installation.xml" file in the given directory * and create pseudo extensions from the list of choices described in this file @@ -168,7 +168,7 @@ protected function ScanDisk($sEnvironment) protected function ReadInstallationWizard($sDir) { if (!is_readable($sDir.'/installation.xml')) return false; - + $oXml = new XMLParameters($sDir.'/installation.xml'); foreach($oXml->Get('steps') as $aStepInfo) { @@ -183,7 +183,7 @@ protected function ReadInstallationWizard($sDir) } return true; } - + /** * Helper to process a "choice" array read from the installation.xml file * @param array $aChoices @@ -218,7 +218,7 @@ protected function ProcessWizardChoices($aChoices) } } } - + /** * Add an extension to the list of existing extensions, taking care of removing duplicates * (only the latest/greatest version is kept) @@ -248,7 +248,7 @@ protected function AddExtension(iTopExtension $oNewExtension) // Finally it's not a duplicate, let's add it to the list $this->aExtensions[$oNewExtension->sCode.'/'.$oNewExtension->sVersion] = $oNewExtension; } - + /** * Read (recursively) a directory to find if it contains extensions (or modules) * @@ -264,11 +264,11 @@ protected function ReadDir($sSearchDir, $sSource, $sParentExtensionId = null) $hDir = opendir($sSearchDir); if ($hDir !== false) { - if ($sParentExtensionId == null) - { - // We're not recursing, let's add the directory to the list of scanned dirs - $this->aScannedDirs[] = $sSearchDir; - } + if ($sParentExtensionId == null) + { + // We're not recursing, let's add the directory to the list of scanned dirs + $this->aScannedDirs[] = $sSearchDir; + } $sExtensionId = null; $aSubDirectories = array(); @@ -285,7 +285,7 @@ protected function ReadDir($sSearchDir, $sSource, $sParentExtensionId = null) $oExtension->sMoreInfoUrl = $oXml->Get('more_info_url'); $oExtension->sSource = $sSource; $oExtension->sSourceDir = $sSearchDir; - + $sParentExtensionId = $sExtensionId = $oExtension->sCode.'/'.$oExtension->sVersion; $this->AddExtension($oExtension); } @@ -303,7 +303,11 @@ protected function ReadDir($sSearchDir, $sSource, $sParentExtensionId = null) else if (preg_match('/^module\.(.*).php$/i', $sFile, $aMatches)) { // Found a module - $aModuleInfo = $this->GetModuleInfo($sSearchDir.'/'.$sFile); + try { + $aModuleInfo = ModuleFileReader::GetInstance()->ReadModuleFileInformation($sSearchDir.'/'.$sFile); + } catch(ModuleFileReaderException $e){ + continue; + } // If we are not already inside a formal extension, then the module itself is considered // as an extension, otherwise, the module is just added to the list of modules belonging // to this extension @@ -314,7 +318,7 @@ protected function ReadDir($sSearchDir, $sSource, $sParentExtensionId = null) // Provide a default module version since version is mandatory when recording ExtensionInstallation $sModuleVersion = '0.0.1'; } - + if (($sParentExtensionId !== null) && (array_key_exists($sParentExtensionId, $this->aExtensions)) && ($this->aExtensions[$sParentExtensionId] instanceof iTopExtension)) { // Already inside an extension, let's add this module the list of modules belonging to this extension $this->aExtensions[$sParentExtensionId]->aModules[] = $sModuleName; @@ -324,15 +328,15 @@ protected function ReadDir($sSearchDir, $sSource, $sParentExtensionId = null) else { // Not already inside an folder containing an 'extension.xml' file - + // Ignore non-visible modules and auto-select ones, since these are never prompted // as a choice to the end-user $bVisible = true; if (!$aModuleInfo[2]['visible'] || isset($aModuleInfo[2]['auto_select'])) { - $bVisible = false; + $bVisible = false; } - + // Let's create a "fake" extension from this module (containing just this module) for backwards compatibility $oExtension = new iTopExtension(); $oExtension->sCode = $sModuleName; @@ -366,7 +370,7 @@ protected function ReadDir($sSearchDir, $sSource, $sParentExtensionId = null) } return false; } - + /** * Check if some extension contains a module with missing dependencies... * If so, populate the aMissingDepenencies array @@ -376,7 +380,7 @@ protected function ReadDir($sSearchDir, $sSource, $sParentExtensionId = null) protected function CheckDependencies($sFromEnvironment) { $aSearchDirs = array(); - + if (is_dir(APPROOT.'/datamodels/2.x')) { $aSearchDirs[] = APPROOT.'/datamodels/2.x'; @@ -386,7 +390,7 @@ protected function CheckDependencies($sFromEnvironment) $aSearchDirs[] = APPROOT.'/datamodels/1.x'; } $aSearchDirs = array_merge($aSearchDirs, $this->aScannedDirs); - + try { $aAllModules = ModuleDiscovery::GetAvailableModules($aSearchDirs, true); @@ -404,7 +408,7 @@ protected function CheckDependencies($sFromEnvironment) // This information is not available for pseudo modules defined in the installation wizard, but let's ignore them $sVersion = $oExtension->aModuleVersion[$sModuleName]; $sModuleId = $sModuleName.'/'.$sVersion; - + if (array_key_exists($sModuleId, $e->aModulesInfo)) { // The extension actually contains a module which has unmet dependencies @@ -416,61 +420,7 @@ protected function CheckDependencies($sFromEnvironment) } } } - - /** - * Read the information from a module file (module.xxx.php) - * Closely inspired (almost copied/pasted !!) from ModuleDiscovery::ListModuleFiles - * @param string $sModuleFile - * @return array - */ - protected function GetModuleInfo($sModuleFile) - { - static $iDummyClassIndex = 0; - - $aModuleInfo = array(); // will be filled by the "eval" line below... - try - { - $aMatches = array(); - $sModuleFileContents = file_get_contents($sModuleFile); - $sModuleFileContents = str_replace(array(''), '', $sModuleFileContents); - $sModuleFileContents = str_replace('__FILE__', "'".addslashes($sModuleFile)."'", $sModuleFileContents); - preg_match_all('/class ([A-Za-z0-9_]+) extends ([A-Za-z0-9_]+)/', $sModuleFileContents, $aMatches); - //print_r($aMatches); - $idx = 0; - foreach($aMatches[1] as $sClassName) - { - if (class_exists($sClassName)) - { - // rename any class declaration inside the code to prevent a "duplicate class" declaration - // and change its parent class as well so that nobody will find it and try to execute it - // Note: don't use the same naming scheme as ModuleDiscovery otherwise you 'll have the duplicate class error again !! - $sModuleFileContents = str_replace($sClassName.' extends '.$aMatches[2][$idx], $sClassName.'_Ext_'.($iDummyClassIndex++).' extends DummyHandler', $sModuleFileContents); - } - $idx++; - } - // Replace the main function call by an assignment to a variable, as an array... - $sModuleFileContents = str_replace(array('SetupWebPage::AddModule', 'ModuleDiscovery::AddModule'), '$aModuleInfo = array', $sModuleFileContents); - - eval($sModuleFileContents); // Assigns $aModuleInfo - - if (count($aModuleInfo) === 0) - { - SetupLog::Warning("Eval of $sModuleFile did not return the expected information..."); - } - } - catch(ParseError $e) - { - // Continue... - SetupLog::Warning("Eval of $sModuleFile caused a parse error: ".$e->getMessage()." at line ".$e->getLine()); - } - catch(Exception $e) - { - // Continue... - SetupLog::Warning("Eval of $sModuleFile caused an exception: ".$e->getMessage()); - } - return $aModuleInfo; - } - + /** * Get all available extensions * @return iTopExtension[] @@ -479,7 +429,7 @@ public function GetAllExtensions() { return $this->aExtensions; } - + /** * Mark the given extension as chosen * @param string $sExtensionCode The code of the extension (code without verison number) @@ -497,7 +447,7 @@ public function MarkAsChosen($sExtensionCode, $bMark = true) } } } - + /** * Tells if a given extension(code) is marked as chosen * @param string $sExtensionCode @@ -532,7 +482,7 @@ protected function SetInstalledVersion($sExtensionCode, $sInstalledVersion) } } } - + /** * Get the list of the "chosen" extensions * @return iTopExtension[] @@ -549,7 +499,7 @@ public function GetChoices() } return $aResult; } - + /** * Load the choices (i.e. MarkedAsChosen) from the database defined in the supplied Config * @param Config $oConfig @@ -572,7 +522,7 @@ public function LoadChoicesFromDatabase(Config $oConfig) // No database or erroneous information return false; } - + foreach($aInstalledExtensions as $aDBInfo) { $this->MarkAsChosen($aDBInfo['code']); @@ -580,7 +530,7 @@ public function LoadChoicesFromDatabase(Config $oConfig) } return true; } - + /** * Find is a single-module extension is contained within another extension * @param iTopExtension $oExtension @@ -590,7 +540,7 @@ public function IsExtensionObsoletedByAnother(iTopExtension $oExtension) { // Complex extensions (more than 1 module) are never considered as obsolete if (count($oExtension->aModules) != 1) return null; - + foreach($this->GetAllExtensions() as $oOtherExtension) { if (($oOtherExtension->sSourceDir != $oExtension->sSourceDir) && ($oOtherExtension->sSource != iTopExtension::SOURCE_WIZARD)) @@ -603,12 +553,12 @@ public function IsExtensionObsoletedByAnother(iTopExtension $oExtension) } } } - + // No match at all return null; - + } - + /** * Search for multi-module extensions that are NOT deployed as an extension (i.e. shipped with an extension.xml file) * but as a bunch of un-related modules based on the signature of some well-known extensions. If such an extension is found, @@ -617,53 +567,53 @@ public function IsExtensionObsoletedByAnother(iTopExtension $oExtension) */ public function NormalizeOldExtensions($sInSourceOnly = iTopExtension::SOURCE_MANUAL) { - $aSignatures = $this->GetOldExtensionsSignatures(); - foreach($aSignatures as $sExtensionCode => $aExtensionSignatures) - { - $bFound = false; - foreach($aExtensionSignatures['versions'] as $sVersion => $aModules) - { - $bInstalled = true; - foreach($aModules as $sModuleId) - { - if(!$this->ModuleIsPresent($sModuleId, $sInSourceOnly)) - { - $bFound = false; - break; // One missing module is enough to determine that the extension/version is not present - } - else - { - $bInstalled = $bInstalled && (!$this->ModuleIsInstalled($sModuleId, $sInSourceOnly)); - $bFound = true; - } - } - if ($bFound) break; // The current version matches the signature - } - - if ($bFound) - { - $oExtension = new iTopExtension(); - $oExtension->sCode = $sExtensionCode; - $oExtension->sLabel = $aExtensionSignatures['label']; - $oExtension->sSource = $sInSourceOnly; - $oExtension->sDescription = $aExtensionSignatures['description']; - $oExtension->sVersion = $sVersion; - $oExtension->aModules = array(); - if ($bInstalled) - { - $oExtension->sInstalledVersion = $sVersion; - $oExtension->bMarkedAsChosen = true; - } - foreach($aModules as $sModuleId) - { - list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId); - $oExtension->aModules[] = $sModuleName; - } - $this->ReplaceModulesByNormalizedExtension($aExtensionSignatures['versions'][$sVersion], $oExtension); - } - } + $aSignatures = $this->GetOldExtensionsSignatures(); + foreach($aSignatures as $sExtensionCode => $aExtensionSignatures) + { + $bFound = false; + foreach($aExtensionSignatures['versions'] as $sVersion => $aModules) + { + $bInstalled = true; + foreach($aModules as $sModuleId) + { + if(!$this->ModuleIsPresent($sModuleId, $sInSourceOnly)) + { + $bFound = false; + break; // One missing module is enough to determine that the extension/version is not present + } + else + { + $bInstalled = $bInstalled && (!$this->ModuleIsInstalled($sModuleId, $sInSourceOnly)); + $bFound = true; + } + } + if ($bFound) break; // The current version matches the signature + } + + if ($bFound) + { + $oExtension = new iTopExtension(); + $oExtension->sCode = $sExtensionCode; + $oExtension->sLabel = $aExtensionSignatures['label']; + $oExtension->sSource = $sInSourceOnly; + $oExtension->sDescription = $aExtensionSignatures['description']; + $oExtension->sVersion = $sVersion; + $oExtension->aModules = array(); + if ($bInstalled) + { + $oExtension->sInstalledVersion = $sVersion; + $oExtension->bMarkedAsChosen = true; + } + foreach($aModules as $sModuleId) + { + list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId); + $oExtension->aModules[] = $sModuleName; + } + $this->ReplaceModulesByNormalizedExtension($aExtensionSignatures['versions'][$sVersion], $oExtension); + } + } } - + /** * Check if the given module-code/version is present on the disk * @param string $sModuleIdToFind The module ID (code/version) to search for @@ -672,9 +622,9 @@ public function NormalizeOldExtensions($sInSourceOnly = iTopExtension::SOURCE_MA */ protected function ModuleIsPresent($sModuleIdToFind, $sInSourceOnly) { - return (array_key_exists($sModuleIdToFind, $this->aExtensions) && ($this->aExtensions[$sModuleIdToFind]->sSource == $sInSourceOnly)); + return (array_key_exists($sModuleIdToFind, $this->aExtensions) && ($this->aExtensions[$sModuleIdToFind]->sSource == $sInSourceOnly)); } - + /** * Check if the given module-code/version is currently installed * @param string $sModuleIdToFind The module ID (code/version) to search for @@ -683,11 +633,11 @@ protected function ModuleIsPresent($sModuleIdToFind, $sInSourceOnly) */ protected function ModuleIsInstalled($sModuleIdToFind, $sInSourceOnly) { - return (array_key_exists($sModuleIdToFind, $this->aExtensions) && - ($this->aExtensions[$sModuleIdToFind]->sSource == $sInSourceOnly) && - ($this->aExtensions[$sModuleIdToFind]->sInstalledVersion !== '') ); + return (array_key_exists($sModuleIdToFind, $this->aExtensions) && + ($this->aExtensions[$sModuleIdToFind]->sSource == $sInSourceOnly) && + ($this->aExtensions[$sModuleIdToFind]->sInstalledVersion !== '') ); } - + /** * Tells if the given module name is "chosen" since it is part of a "chosen" extension (in the specified source dir) * @param string $sModuleNameToFind @@ -697,7 +647,7 @@ protected function ModuleIsInstalled($sModuleIdToFind, $sInSourceOnly) public function ModuleIsChosenAsPartOfAnExtension($sModuleNameToFind, $sInSourceOnly = iTopExtension::SOURCE_REMOTE) { $bChosen = false; - + foreach($this->GetAllExtensions() as $oExtension) { if (($oExtension->sSource == $sInSourceOnly) && @@ -709,7 +659,7 @@ public function ModuleIsChosenAsPartOfAnExtension($sModuleNameToFind, $sInSource } return false; } - + /** * Replace a given set of stand-alone modules by one single "extension" * @param string[] $aModules @@ -717,13 +667,13 @@ public function ModuleIsChosenAsPartOfAnExtension($sModuleNameToFind, $sInSource */ protected function ReplaceModulesByNormalizedExtension($aModules, iTopExtension $oNewExtension) { - foreach($aModules as $sModuleId) - { - unset($this->aExtensions[$sModuleId]); - } - $this->AddExtension($oNewExtension); + foreach($aModules as $sModuleId) + { + unset($this->aExtensions[$sModuleId]); + } + $this->AddExtension($oNewExtension); } - + /** * Get the list of signatures of some well-known multi-module extensions without extension.xml file (should not exist anymore) * @@ -731,637 +681,637 @@ protected function ReplaceModulesByNormalizedExtension($aModules, iTopExtension */ protected function GetOldExtensionsSignatures() { - // Generated by the Factory using the page export_component_versions_for_normalisation.php - return array ( - 'combodo-approval-process-light' => - array ( - 'label' => 'Approval process light', - 'description' => 'Approve a request via a simple email', - 'versions' => - array ( - '1.0.1' => - array ( - 0 => 'approval-base/2.1.0', - 1 => 'combodo-approval-light/1.0.1', - ), - '1.0.2' => - array ( - 0 => 'approval-base/2.1.1', - 1 => 'combodo-approval-light/1.0.2', - ), - '1.0.3' => - array ( - 0 => 'approval-base/2.1.2', - 1 => 'combodo-approval-light/1.0.2', - ), - '1.1.0' => - array ( - 0 => 'approval-base/2.2.2', - 1 => 'combodo-approval-light/1.0.2', - ), - '1.1.1' => - array ( - 0 => 'approval-base/2.2.3', - 1 => 'combodo-approval-light/1.0.2', - ), - '1.1.2' => - array ( - 0 => 'approval-base/2.2.6', - 1 => 'combodo-approval-light/1.0.2', - ), - '1.1.3' => - array ( - 0 => 'approval-base/2.2.6', - 1 => 'combodo-approval-light/1.0.3', - ), - '1.2.0' => - array ( - 0 => 'approval-base/2.3.0', - 1 => 'combodo-approval-light/1.0.3', - ), - '1.2.1' => - array ( - 0 => 'approval-base/2.4.0', - 1 => 'combodo-approval-light/1.0.4', - ), - '1.3.0' => - array ( - 0 => 'approval-base/2.4.2', - 1 => 'combodo-approval-light/1.1.1', - ), - '1.3.1' => - array ( - 0 => 'approval-base/2.5.0', - 1 => 'combodo-approval-light/1.1.1', - ), - '1.3.2' => - array ( - 0 => 'approval-base/2.5.0', - 1 => 'combodo-approval-light/1.1.2', - ), - '1.2.2' => - array ( - 0 => 'approval-base/2.4.2', - 1 => 'combodo-approval-light/1.0.5', - ), - '1.3.3' => - array ( - 0 => 'approval-base/2.5.1', - 1 => 'combodo-approval-light/1.1.2', - ), - '1.3.4' => - array ( - 0 => 'approval-base/2.5.2', - 1 => 'combodo-approval-light/1.1.2', - ), - '1.3.5' => - array ( - 0 => 'approval-base/2.5.3', - 1 => 'combodo-approval-light/1.1.2', - ), - '1.4.0' => - array ( - 0 => 'approval-base/2.5.3', - 1 => 'combodo-approval-light/1.1.2', - 2 => 'itop-approval-portal/1.0.0', - ), - ), - ), - 'combodo-approval-process-automation' => - array ( - 'label' => 'Approval process automation', - 'description' => 'Control your approval process with predefined rules based on service catalog', - 'versions' => - array ( - '1.0.1' => - array ( - 0 => 'approval-base/2.1.0', - 1 => 'combodo-approval-extended/1.0.2', - ), - '1.0.2' => - array ( - 0 => 'approval-base/2.1.1', - 1 => 'combodo-approval-extended/1.0.4', - ), - '1.0.3' => - array ( - 0 => 'approval-base/2.1.2', - 1 => 'combodo-approval-extended/1.0.4', - ), - '1.1.0' => - array ( - 0 => 'approval-base/2.2.2', - 1 => 'combodo-approval-extended/1.0.4', - ), - '1.1.1' => - array ( - 0 => 'approval-base/2.2.3', - 1 => 'combodo-approval-extended/1.0.4', - ), - '1.1.2' => - array ( - 0 => 'approval-base/2.2.6', - 1 => 'combodo-approval-extended/1.0.5', - ), - '1.1.3' => - array ( - 0 => 'approval-base/2.2.6', - 1 => 'combodo-approval-extended/1.0.6', - ), - '1.2.0' => - array ( - 0 => 'approval-base/2.3.0', - 1 => 'combodo-approval-extended/1.0.7', - ), - '1.2.1' => - array ( - 0 => 'approval-base/2.4.0', - 1 => 'combodo-approval-extended/1.0.8', - ), - '1.3.0' => - array ( - 0 => 'approval-base/2.4.2', - 1 => 'combodo-approval-extended/1.2.1', - ), - '1.3.1' => - array ( - 0 => 'approval-base/2.5.0', - 1 => 'combodo-approval-extended/1.2.1', - ), - '1.3.2' => - array ( - 0 => 'approval-base/2.5.0', - 1 => 'combodo-approval-extended/1.2.2', - ), - '1.2.2' => - array ( - 0 => 'approval-base/2.4.2', - 1 => 'combodo-approval-extended/1.0.9', - ), - '1.3.3' => - array ( - 0 => 'approval-base/2.5.1', - 1 => 'combodo-approval-extended/1.2.3', - ), - '1.3.4' => - array ( - 0 => 'approval-base/2.5.2', - 1 => 'combodo-approval-extended/1.2.3', - ), - '1.3.5' => - array ( - 0 => 'approval-base/2.5.3', - 1 => 'combodo-approval-extended/1.2.3', - ), - '1.4.0' => - array ( - 0 => 'approval-base/2.5.3', - 1 => 'combodo-approval-extended/1.2.3', - 3 => 'itop-approval-portal/1.0.0', - ), - ), - ), - 'combodo-predefined-response-models' => - array ( - 'label' => 'Predefined response models', - 'description' => 'Pick common answers from a list of predefined replies grouped by categories to update tickets log', - 'versions' => - array ( - '1.0.0' => - array ( - 0 => 'precanned-replies/1.0.0', - 1 => 'precanned-replies-pro/1.0.0', - ), - '1.0.1' => - array ( - 0 => 'precanned-replies/1.0.1', - 1 => 'precanned-replies-pro/1.0.1', - ), - '1.0.2' => - array ( - 0 => 'precanned-replies/1.0.2', - 1 => 'precanned-replies-pro/1.0.1', - ), - '1.0.3' => - array ( - 0 => 'precanned-replies/1.0.3', - 1 => 'precanned-replies-pro/1.0.1', - ), - '1.0.4' => - array ( - 0 => 'precanned-replies/1.0.3', - 1 => 'precanned-replies-pro/1.0.2', - ), - '1.0.5' => - array ( - 0 => 'precanned-replies/1.0.4', - 1 => 'precanned-replies-pro/1.0.2', - ), - '1.1.0' => - array ( - 0 => 'precanned-replies/1.1.0', - 1 => 'precanned-replies-pro/1.0.2', - ), - '1.1.1' => - array ( - 0 => 'precanned-replies/1.1.1', - 1 => 'precanned-replies-pro/1.0.2', - ), - ), - ), - 'combodo-customized-request-forms' => - array ( - 'label' => 'Customized request forms', - 'description' => 'Define personalized request forms based on the service catalog. Add extra fields for a given type of request.', - 'versions' => - array ( - '1.0.1' => - array ( - 0 => 'templates-base/2.1.1', - 1 => 'itop-request-template/1.0.0', - ), - '1.0.2' => - array ( - 0 => 'templates-base/2.1.2', - 1 => 'itop-request-template/1.0.0', - ), - '1.0.3' => - array ( - 0 => 'templates-base/2.1.2', - 1 => 'itop-request-template/1.0.1', - ), - '1.0.4' => - array ( - 0 => 'templates-base/2.1.3', - 1 => 'itop-request-template/1.0.1', - ), - '1.0.5' => - array ( - 0 => 'templates-base/2.1.4', - 1 => 'itop-request-template/1.0.1', - ), - '2.0.0' => - array ( - 0 => 'templates-base/3.0.0', - 1 => 'itop-request-template/2.0.0', - 2 => 'itop-request-template-portal/1.0.0', - ), - '2.0.1' => - array ( - 0 => 'templates-base/3.0.1', - 1 => 'itop-request-template/2.0.0', - 2 => 'itop-request-template-portal/1.0.0', - ), - '2.0.2' => - array ( - 0 => 'templates-base/3.0.2', - 1 => 'itop-request-template/2.0.0', - 2 => 'itop-request-template-portal/1.0.0', - ), - '2.0.3' => - array ( - 0 => 'templates-base/3.0.4', - 1 => 'itop-request-template/2.0.0', - 2 => 'itop-request-template-portal/1.0.0', - ), - '2.0.4' => - array ( - 0 => 'templates-base/3.0.5', - 1 => 'itop-request-template/2.0.0', - 2 => 'itop-request-template-portal/1.0.0', - ), - '2.0.5' => - array ( - 0 => 'templates-base/3.0.6', - 1 => 'itop-request-template/2.0.0', - 2 => 'itop-request-template-portal/1.0.0', - ), - '2.0.6' => - array ( - 0 => 'templates-base/3.0.8', - 1 => 'itop-request-template/2.0.0', - 2 => 'itop-request-template-portal/1.0.0', - ), - '2.0.7' => - array ( - 0 => 'templates-base/3.0.9', - 1 => 'itop-request-template/2.0.0', - 2 => 'itop-request-template-portal/1.0.0', - ), - '2.0.8' => - array ( - 0 => 'templates-base/3.0.12', - 1 => 'itop-request-template/2.0.0', - 2 => 'itop-request-template-portal/1.0.0', - ), - ), - ), - 'combodo-sla-considering-business-hours' => - array ( - 'label' => 'SLA considering business hours', - 'description' => 'Compute SLAs taking into account service coverage window and holidays', - 'versions' => - array ( - '2.0.1' => - array ( - 0 => 'combodo-sla-computation/2.0.1', - 1 => 'combodo-coverage-windows-computation/2.0.0', - ), - '2.1.0' => - array ( - 0 => 'combodo-sla-computation/2.1.0', - 1 => 'combodo-coverage-windows-computation/2.0.0', - ), - '2.1.1' => - array ( - 0 => 'combodo-sla-computation/2.1.1', - 1 => 'combodo-coverage-windows-computation/2.0.0', - ), - '2.1.2' => - array ( - 0 => 'combodo-sla-computation/2.1.2', - 1 => 'combodo-coverage-windows-computation/2.0.0', - ), - '2.1.3' => - array ( - 0 => 'combodo-sla-computation/2.1.2', - 1 => 'combodo-coverage-windows-computation/2.0.1', - ), - '2.0.2' => - array ( - 0 => 'combodo-sla-computation/2.0.1', - 1 => 'combodo-coverage-windows-computation/2.0.1', - ), - '2.1.4' => - array ( - 0 => 'combodo-sla-computation/2.1.3', - 1 => 'combodo-coverage-windows-computation/2.0.1', - ), - '2.1.5' => - array ( - 0 => 'combodo-sla-computation/2.1.5', - 1 => 'combodo-coverage-windows-computation/2.0.1', - ), - '2.1.6' => - array ( - 0 => 'combodo-sla-computation/2.1.5', - 1 => 'combodo-coverage-windows-computation/2.0.2', - ), - '2.1.7' => - array ( - 0 => 'combodo-sla-computation/2.1.6', - 1 => 'combodo-coverage-windows-computation/2.0.2', - ), - '2.1.8' => - array ( - 0 => 'combodo-sla-computation/2.1.7', - 1 => 'combodo-coverage-windows-computation/2.0.2', - ), - '2.1.9' => - array ( - 0 => 'combodo-sla-computation/2.1.8', - 1 => 'combodo-coverage-windows-computation/2.0.2', - ), - ), - ), - 'combodo-mail-to-ticket-automation' => - array ( - 'label' => 'Mail to ticket automation', - 'description' => 'Scan several mailboxes to create or update tickets.', - 'versions' => - array ( - '2.6.0' => - array ( - 0 => 'combodo-email-synchro/2.6.0', - 1 => 'itop-standard-email-synchro/2.6.0', - ), - '2.6.1' => - array ( - 0 => 'combodo-email-synchro/2.6.1', - 1 => 'itop-standard-email-synchro/2.6.0', - ), - '2.6.2' => - array ( - 0 => 'combodo-email-synchro/2.6.2', - 1 => 'itop-standard-email-synchro/2.6.0', - ), - '2.6.3' => - array ( - 0 => 'combodo-email-synchro/2.6.2', - 1 => 'itop-standard-email-synchro/2.6.1', - ), - '2.6.4' => - array ( - 0 => 'combodo-email-synchro/2.6.3', - 1 => 'itop-standard-email-synchro/2.6.2', - ), - '2.6.5' => - array ( - 0 => 'combodo-email-synchro/2.6.4', - 1 => 'itop-standard-email-synchro/2.6.2', - ), - '2.6.6' => - array ( - 0 => 'combodo-email-synchro/2.6.5', - 1 => 'itop-standard-email-synchro/2.6.3', - ), - '2.6.7' => - array ( - 0 => 'combodo-email-synchro/2.6.6', - 1 => 'itop-standard-email-synchro/2.6.4', - ), - '2.6.8' => - array ( - 0 => 'combodo-email-synchro/2.6.7', - 1 => 'itop-standard-email-synchro/2.6.4', - ), - '2.6.9' => - array ( - 0 => 'combodo-email-synchro/2.6.8', - 1 => 'itop-standard-email-synchro/2.6.5', - ), - '2.6.10' => - array ( - 0 => 'combodo-email-synchro/2.6.9', - 1 => 'itop-standard-email-synchro/2.6.6', - ), - '2.6.11' => - array ( - 0 => 'combodo-email-synchro/2.6.10', - 1 => 'itop-standard-email-synchro/2.6.6', - ), - '2.6.12' => - array ( - 0 => 'combodo-email-synchro/2.6.11', - 1 => 'itop-standard-email-synchro/2.6.6', - ), - '3.0.0' => - array ( - 0 => 'combodo-email-synchro/3.0.0', - 1 => 'itop-standard-email-synchro/3.0.0', - ), - '3.0.1' => - array ( - 0 => 'combodo-email-synchro/3.0.1', - 1 => 'itop-standard-email-synchro/3.0.1', - ), - '3.0.2' => - array ( - 0 => 'combodo-email-synchro/3.0.2', - 1 => 'itop-standard-email-synchro/3.0.1', - ), - '3.0.3' => - array ( - 0 => 'combodo-email-synchro/3.0.3', - 1 => 'itop-standard-email-synchro/3.0.3', - ), - '3.0.4' => - array ( - 0 => 'combodo-email-synchro/3.0.3', - 1 => 'itop-standard-email-synchro/3.0.4', - ), - '3.0.5' => - array ( - 0 => 'combodo-email-synchro/3.0.4', - 1 => 'itop-standard-email-synchro/3.0.4', - ), - '3.0.6' => - array ( - 0 => 'combodo-email-synchro/3.0.5', - 1 => 'itop-standard-email-synchro/3.0.4', - ), - '3.0.7' => - array ( - 0 => 'combodo-email-synchro/3.0.5', - 1 => 'itop-standard-email-synchro/3.0.5', - ), - ), - ), - 'combodo-configurator-for-automatic-object-creation' => - array ( - 'label' => 'Configurator for automatic object creation', - 'description' => 'Templating based on existing objects.', - 'versions' => - array ( - '1.0.13' => - array ( - 1 => 'itop-stencils/1.0.6', - ), - ), - ), - 'combodo-user-actions-configurator' => - array ( - 'label' => 'User actions configurator', - 'description' => 'Configure user actions to simplify and automate processes (e.g. create an incident from a CI).', - 'versions' => - array ( - '1.0.0' => - array ( - 0 => 'itop-object-copier/1.0.0', - ), - '1.0.1' => - array ( - 0 => 'itop-object-copier/1.0.1', - ), - '1.0.2' => - array ( - 0 => 'itop-object-copier/1.0.2', - ), - '1.0.3' => - array ( - 0 => 'itop-object-copier/1.0.3', - ), - '1.1.0' => - array ( - 0 => 'itop-object-copier/1.1.0', - ), - '1.1.1' => - array ( - 0 => 'itop-object-copier/1.1.1', - ), - '1.1.2' => - array ( - 0 => 'itop-object-copier/1.1.2', - ), - '1.1.3' => - array ( - 0 => 'itop-object-copier/1.1.3', - ), - '1.1.4' => - array ( - 0 => 'itop-object-copier/1.1.4', - ), - '1.1.5' => - array ( - 0 => 'itop-object-copier/1.1.5', - ), - '1.1.6' => - array ( - 0 => 'itop-object-copier/1.1.6', - ), - '1.1.7' => - array ( - 0 => 'itop-object-copier/1.1.7', - ), - '1.1.8' => - array ( - 0 => 'itop-object-copier/1.1.8', - ), - ), - ), - 'combodo-send-updates-by-email' => - array ( - 'label' => 'Send updates by email', - 'description' => 'Send an email to pre-configured contacts when a ticket log is updated.', - 'versions' => - array ( - '1.0.1' => - array ( - 0 => 'email-reply/1.0.1', - ), - '1.0.3' => - array ( - 0 => 'email-reply/1.0.3', - ), - '1.1.1' => - array ( - 0 => 'email-reply/1.1.1', - ), - '1.1.2' => - array ( - 0 => 'email-reply/1.1.2', - ), - '1.1.3' => - array ( - 0 => 'email-reply/1.1.3', - ), - '1.1.4' => - array ( - 0 => 'email-reply/1.1.4', - ), - '1.1.5' => - array ( - 0 => 'email-reply/1.1.5', - ), - '1.1.6' => - array ( - 0 => 'email-reply/1.1.6', - ), - '1.1.7' => - array ( - 0 => 'email-reply/1.1.7', - ), - // 1.1.8 was never released - '1.1.9' => - array ( - 0 => 'email-reply/1.1.9', - ), - '1.1.10' => - array ( - 0 => 'email-reply/1.1.10', - ), - ), - ), - ); + // Generated by the Factory using the page export_component_versions_for_normalisation.php + return array ( + 'combodo-approval-process-light' => + array ( + 'label' => 'Approval process light', + 'description' => 'Approve a request via a simple email', + 'versions' => + array ( + '1.0.1' => + array ( + 0 => 'approval-base/2.1.0', + 1 => 'combodo-approval-light/1.0.1', + ), + '1.0.2' => + array ( + 0 => 'approval-base/2.1.1', + 1 => 'combodo-approval-light/1.0.2', + ), + '1.0.3' => + array ( + 0 => 'approval-base/2.1.2', + 1 => 'combodo-approval-light/1.0.2', + ), + '1.1.0' => + array ( + 0 => 'approval-base/2.2.2', + 1 => 'combodo-approval-light/1.0.2', + ), + '1.1.1' => + array ( + 0 => 'approval-base/2.2.3', + 1 => 'combodo-approval-light/1.0.2', + ), + '1.1.2' => + array ( + 0 => 'approval-base/2.2.6', + 1 => 'combodo-approval-light/1.0.2', + ), + '1.1.3' => + array ( + 0 => 'approval-base/2.2.6', + 1 => 'combodo-approval-light/1.0.3', + ), + '1.2.0' => + array ( + 0 => 'approval-base/2.3.0', + 1 => 'combodo-approval-light/1.0.3', + ), + '1.2.1' => + array ( + 0 => 'approval-base/2.4.0', + 1 => 'combodo-approval-light/1.0.4', + ), + '1.3.0' => + array ( + 0 => 'approval-base/2.4.2', + 1 => 'combodo-approval-light/1.1.1', + ), + '1.3.1' => + array ( + 0 => 'approval-base/2.5.0', + 1 => 'combodo-approval-light/1.1.1', + ), + '1.3.2' => + array ( + 0 => 'approval-base/2.5.0', + 1 => 'combodo-approval-light/1.1.2', + ), + '1.2.2' => + array ( + 0 => 'approval-base/2.4.2', + 1 => 'combodo-approval-light/1.0.5', + ), + '1.3.3' => + array ( + 0 => 'approval-base/2.5.1', + 1 => 'combodo-approval-light/1.1.2', + ), + '1.3.4' => + array ( + 0 => 'approval-base/2.5.2', + 1 => 'combodo-approval-light/1.1.2', + ), + '1.3.5' => + array ( + 0 => 'approval-base/2.5.3', + 1 => 'combodo-approval-light/1.1.2', + ), + '1.4.0' => + array ( + 0 => 'approval-base/2.5.3', + 1 => 'combodo-approval-light/1.1.2', + 2 => 'itop-approval-portal/1.0.0', + ), + ), + ), + 'combodo-approval-process-automation' => + array ( + 'label' => 'Approval process automation', + 'description' => 'Control your approval process with predefined rules based on service catalog', + 'versions' => + array ( + '1.0.1' => + array ( + 0 => 'approval-base/2.1.0', + 1 => 'combodo-approval-extended/1.0.2', + ), + '1.0.2' => + array ( + 0 => 'approval-base/2.1.1', + 1 => 'combodo-approval-extended/1.0.4', + ), + '1.0.3' => + array ( + 0 => 'approval-base/2.1.2', + 1 => 'combodo-approval-extended/1.0.4', + ), + '1.1.0' => + array ( + 0 => 'approval-base/2.2.2', + 1 => 'combodo-approval-extended/1.0.4', + ), + '1.1.1' => + array ( + 0 => 'approval-base/2.2.3', + 1 => 'combodo-approval-extended/1.0.4', + ), + '1.1.2' => + array ( + 0 => 'approval-base/2.2.6', + 1 => 'combodo-approval-extended/1.0.5', + ), + '1.1.3' => + array ( + 0 => 'approval-base/2.2.6', + 1 => 'combodo-approval-extended/1.0.6', + ), + '1.2.0' => + array ( + 0 => 'approval-base/2.3.0', + 1 => 'combodo-approval-extended/1.0.7', + ), + '1.2.1' => + array ( + 0 => 'approval-base/2.4.0', + 1 => 'combodo-approval-extended/1.0.8', + ), + '1.3.0' => + array ( + 0 => 'approval-base/2.4.2', + 1 => 'combodo-approval-extended/1.2.1', + ), + '1.3.1' => + array ( + 0 => 'approval-base/2.5.0', + 1 => 'combodo-approval-extended/1.2.1', + ), + '1.3.2' => + array ( + 0 => 'approval-base/2.5.0', + 1 => 'combodo-approval-extended/1.2.2', + ), + '1.2.2' => + array ( + 0 => 'approval-base/2.4.2', + 1 => 'combodo-approval-extended/1.0.9', + ), + '1.3.3' => + array ( + 0 => 'approval-base/2.5.1', + 1 => 'combodo-approval-extended/1.2.3', + ), + '1.3.4' => + array ( + 0 => 'approval-base/2.5.2', + 1 => 'combodo-approval-extended/1.2.3', + ), + '1.3.5' => + array ( + 0 => 'approval-base/2.5.3', + 1 => 'combodo-approval-extended/1.2.3', + ), + '1.4.0' => + array ( + 0 => 'approval-base/2.5.3', + 1 => 'combodo-approval-extended/1.2.3', + 3 => 'itop-approval-portal/1.0.0', + ), + ), + ), + 'combodo-predefined-response-models' => + array ( + 'label' => 'Predefined response models', + 'description' => 'Pick common answers from a list of predefined replies grouped by categories to update tickets log', + 'versions' => + array ( + '1.0.0' => + array ( + 0 => 'precanned-replies/1.0.0', + 1 => 'precanned-replies-pro/1.0.0', + ), + '1.0.1' => + array ( + 0 => 'precanned-replies/1.0.1', + 1 => 'precanned-replies-pro/1.0.1', + ), + '1.0.2' => + array ( + 0 => 'precanned-replies/1.0.2', + 1 => 'precanned-replies-pro/1.0.1', + ), + '1.0.3' => + array ( + 0 => 'precanned-replies/1.0.3', + 1 => 'precanned-replies-pro/1.0.1', + ), + '1.0.4' => + array ( + 0 => 'precanned-replies/1.0.3', + 1 => 'precanned-replies-pro/1.0.2', + ), + '1.0.5' => + array ( + 0 => 'precanned-replies/1.0.4', + 1 => 'precanned-replies-pro/1.0.2', + ), + '1.1.0' => + array ( + 0 => 'precanned-replies/1.1.0', + 1 => 'precanned-replies-pro/1.0.2', + ), + '1.1.1' => + array ( + 0 => 'precanned-replies/1.1.1', + 1 => 'precanned-replies-pro/1.0.2', + ), + ), + ), + 'combodo-customized-request-forms' => + array ( + 'label' => 'Customized request forms', + 'description' => 'Define personalized request forms based on the service catalog. Add extra fields for a given type of request.', + 'versions' => + array ( + '1.0.1' => + array ( + 0 => 'templates-base/2.1.1', + 1 => 'itop-request-template/1.0.0', + ), + '1.0.2' => + array ( + 0 => 'templates-base/2.1.2', + 1 => 'itop-request-template/1.0.0', + ), + '1.0.3' => + array ( + 0 => 'templates-base/2.1.2', + 1 => 'itop-request-template/1.0.1', + ), + '1.0.4' => + array ( + 0 => 'templates-base/2.1.3', + 1 => 'itop-request-template/1.0.1', + ), + '1.0.5' => + array ( + 0 => 'templates-base/2.1.4', + 1 => 'itop-request-template/1.0.1', + ), + '2.0.0' => + array ( + 0 => 'templates-base/3.0.0', + 1 => 'itop-request-template/2.0.0', + 2 => 'itop-request-template-portal/1.0.0', + ), + '2.0.1' => + array ( + 0 => 'templates-base/3.0.1', + 1 => 'itop-request-template/2.0.0', + 2 => 'itop-request-template-portal/1.0.0', + ), + '2.0.2' => + array ( + 0 => 'templates-base/3.0.2', + 1 => 'itop-request-template/2.0.0', + 2 => 'itop-request-template-portal/1.0.0', + ), + '2.0.3' => + array ( + 0 => 'templates-base/3.0.4', + 1 => 'itop-request-template/2.0.0', + 2 => 'itop-request-template-portal/1.0.0', + ), + '2.0.4' => + array ( + 0 => 'templates-base/3.0.5', + 1 => 'itop-request-template/2.0.0', + 2 => 'itop-request-template-portal/1.0.0', + ), + '2.0.5' => + array ( + 0 => 'templates-base/3.0.6', + 1 => 'itop-request-template/2.0.0', + 2 => 'itop-request-template-portal/1.0.0', + ), + '2.0.6' => + array ( + 0 => 'templates-base/3.0.8', + 1 => 'itop-request-template/2.0.0', + 2 => 'itop-request-template-portal/1.0.0', + ), + '2.0.7' => + array ( + 0 => 'templates-base/3.0.9', + 1 => 'itop-request-template/2.0.0', + 2 => 'itop-request-template-portal/1.0.0', + ), + '2.0.8' => + array ( + 0 => 'templates-base/3.0.12', + 1 => 'itop-request-template/2.0.0', + 2 => 'itop-request-template-portal/1.0.0', + ), + ), + ), + 'combodo-sla-considering-business-hours' => + array ( + 'label' => 'SLA considering business hours', + 'description' => 'Compute SLAs taking into account service coverage window and holidays', + 'versions' => + array ( + '2.0.1' => + array ( + 0 => 'combodo-sla-computation/2.0.1', + 1 => 'combodo-coverage-windows-computation/2.0.0', + ), + '2.1.0' => + array ( + 0 => 'combodo-sla-computation/2.1.0', + 1 => 'combodo-coverage-windows-computation/2.0.0', + ), + '2.1.1' => + array ( + 0 => 'combodo-sla-computation/2.1.1', + 1 => 'combodo-coverage-windows-computation/2.0.0', + ), + '2.1.2' => + array ( + 0 => 'combodo-sla-computation/2.1.2', + 1 => 'combodo-coverage-windows-computation/2.0.0', + ), + '2.1.3' => + array ( + 0 => 'combodo-sla-computation/2.1.2', + 1 => 'combodo-coverage-windows-computation/2.0.1', + ), + '2.0.2' => + array ( + 0 => 'combodo-sla-computation/2.0.1', + 1 => 'combodo-coverage-windows-computation/2.0.1', + ), + '2.1.4' => + array ( + 0 => 'combodo-sla-computation/2.1.3', + 1 => 'combodo-coverage-windows-computation/2.0.1', + ), + '2.1.5' => + array ( + 0 => 'combodo-sla-computation/2.1.5', + 1 => 'combodo-coverage-windows-computation/2.0.1', + ), + '2.1.6' => + array ( + 0 => 'combodo-sla-computation/2.1.5', + 1 => 'combodo-coverage-windows-computation/2.0.2', + ), + '2.1.7' => + array ( + 0 => 'combodo-sla-computation/2.1.6', + 1 => 'combodo-coverage-windows-computation/2.0.2', + ), + '2.1.8' => + array ( + 0 => 'combodo-sla-computation/2.1.7', + 1 => 'combodo-coverage-windows-computation/2.0.2', + ), + '2.1.9' => + array ( + 0 => 'combodo-sla-computation/2.1.8', + 1 => 'combodo-coverage-windows-computation/2.0.2', + ), + ), + ), + 'combodo-mail-to-ticket-automation' => + array ( + 'label' => 'Mail to ticket automation', + 'description' => 'Scan several mailboxes to create or update tickets.', + 'versions' => + array ( + '2.6.0' => + array ( + 0 => 'combodo-email-synchro/2.6.0', + 1 => 'itop-standard-email-synchro/2.6.0', + ), + '2.6.1' => + array ( + 0 => 'combodo-email-synchro/2.6.1', + 1 => 'itop-standard-email-synchro/2.6.0', + ), + '2.6.2' => + array ( + 0 => 'combodo-email-synchro/2.6.2', + 1 => 'itop-standard-email-synchro/2.6.0', + ), + '2.6.3' => + array ( + 0 => 'combodo-email-synchro/2.6.2', + 1 => 'itop-standard-email-synchro/2.6.1', + ), + '2.6.4' => + array ( + 0 => 'combodo-email-synchro/2.6.3', + 1 => 'itop-standard-email-synchro/2.6.2', + ), + '2.6.5' => + array ( + 0 => 'combodo-email-synchro/2.6.4', + 1 => 'itop-standard-email-synchro/2.6.2', + ), + '2.6.6' => + array ( + 0 => 'combodo-email-synchro/2.6.5', + 1 => 'itop-standard-email-synchro/2.6.3', + ), + '2.6.7' => + array ( + 0 => 'combodo-email-synchro/2.6.6', + 1 => 'itop-standard-email-synchro/2.6.4', + ), + '2.6.8' => + array ( + 0 => 'combodo-email-synchro/2.6.7', + 1 => 'itop-standard-email-synchro/2.6.4', + ), + '2.6.9' => + array ( + 0 => 'combodo-email-synchro/2.6.8', + 1 => 'itop-standard-email-synchro/2.6.5', + ), + '2.6.10' => + array ( + 0 => 'combodo-email-synchro/2.6.9', + 1 => 'itop-standard-email-synchro/2.6.6', + ), + '2.6.11' => + array ( + 0 => 'combodo-email-synchro/2.6.10', + 1 => 'itop-standard-email-synchro/2.6.6', + ), + '2.6.12' => + array ( + 0 => 'combodo-email-synchro/2.6.11', + 1 => 'itop-standard-email-synchro/2.6.6', + ), + '3.0.0' => + array ( + 0 => 'combodo-email-synchro/3.0.0', + 1 => 'itop-standard-email-synchro/3.0.0', + ), + '3.0.1' => + array ( + 0 => 'combodo-email-synchro/3.0.1', + 1 => 'itop-standard-email-synchro/3.0.1', + ), + '3.0.2' => + array ( + 0 => 'combodo-email-synchro/3.0.2', + 1 => 'itop-standard-email-synchro/3.0.1', + ), + '3.0.3' => + array ( + 0 => 'combodo-email-synchro/3.0.3', + 1 => 'itop-standard-email-synchro/3.0.3', + ), + '3.0.4' => + array ( + 0 => 'combodo-email-synchro/3.0.3', + 1 => 'itop-standard-email-synchro/3.0.4', + ), + '3.0.5' => + array ( + 0 => 'combodo-email-synchro/3.0.4', + 1 => 'itop-standard-email-synchro/3.0.4', + ), + '3.0.6' => + array ( + 0 => 'combodo-email-synchro/3.0.5', + 1 => 'itop-standard-email-synchro/3.0.4', + ), + '3.0.7' => + array ( + 0 => 'combodo-email-synchro/3.0.5', + 1 => 'itop-standard-email-synchro/3.0.5', + ), + ), + ), + 'combodo-configurator-for-automatic-object-creation' => + array ( + 'label' => 'Configurator for automatic object creation', + 'description' => 'Templating based on existing objects.', + 'versions' => + array ( + '1.0.13' => + array ( + 1 => 'itop-stencils/1.0.6', + ), + ), + ), + 'combodo-user-actions-configurator' => + array ( + 'label' => 'User actions configurator', + 'description' => 'Configure user actions to simplify and automate processes (e.g. create an incident from a CI).', + 'versions' => + array ( + '1.0.0' => + array ( + 0 => 'itop-object-copier/1.0.0', + ), + '1.0.1' => + array ( + 0 => 'itop-object-copier/1.0.1', + ), + '1.0.2' => + array ( + 0 => 'itop-object-copier/1.0.2', + ), + '1.0.3' => + array ( + 0 => 'itop-object-copier/1.0.3', + ), + '1.1.0' => + array ( + 0 => 'itop-object-copier/1.1.0', + ), + '1.1.1' => + array ( + 0 => 'itop-object-copier/1.1.1', + ), + '1.1.2' => + array ( + 0 => 'itop-object-copier/1.1.2', + ), + '1.1.3' => + array ( + 0 => 'itop-object-copier/1.1.3', + ), + '1.1.4' => + array ( + 0 => 'itop-object-copier/1.1.4', + ), + '1.1.5' => + array ( + 0 => 'itop-object-copier/1.1.5', + ), + '1.1.6' => + array ( + 0 => 'itop-object-copier/1.1.6', + ), + '1.1.7' => + array ( + 0 => 'itop-object-copier/1.1.7', + ), + '1.1.8' => + array ( + 0 => 'itop-object-copier/1.1.8', + ), + ), + ), + 'combodo-send-updates-by-email' => + array ( + 'label' => 'Send updates by email', + 'description' => 'Send an email to pre-configured contacts when a ticket log is updated.', + 'versions' => + array ( + '1.0.1' => + array ( + 0 => 'email-reply/1.0.1', + ), + '1.0.3' => + array ( + 0 => 'email-reply/1.0.3', + ), + '1.1.1' => + array ( + 0 => 'email-reply/1.1.1', + ), + '1.1.2' => + array ( + 0 => 'email-reply/1.1.2', + ), + '1.1.3' => + array ( + 0 => 'email-reply/1.1.3', + ), + '1.1.4' => + array ( + 0 => 'email-reply/1.1.4', + ), + '1.1.5' => + array ( + 0 => 'email-reply/1.1.5', + ), + '1.1.6' => + array ( + 0 => 'email-reply/1.1.6', + ), + '1.1.7' => + array ( + 0 => 'email-reply/1.1.7', + ), + // 1.1.8 was never released + '1.1.9' => + array ( + 0 => 'email-reply/1.1.9', + ), + '1.1.10' => + array ( + 0 => 'email-reply/1.1.10', + ), + ), + ), + ); } } diff --git a/setup/modulediscovery.class.inc.php b/setup/modulediscovery.class.inc.php index 96822a982d..16afbb38ca 100644 --- a/setup/modulediscovery.class.inc.php +++ b/setup/modulediscovery.class.inc.php @@ -19,6 +19,10 @@ * */ +use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator; + +require_once(APPROOT.'setup/modulediscovery/ModuleFileReader.php'); + class MissingDependencyException extends CoreException { /** @@ -91,6 +95,9 @@ class ModuleDiscovery // ModulePath is used by AddModule to get the path of the module being included (in ListModuleFiles) protected static $m_sModulePath = null; + + private static PhpExpressionEvaluator $oPhpExpressionEvaluator; + protected static function SetModulePath($sModulePath) { self::$m_sModulePath = $sModulePath; @@ -105,6 +112,9 @@ protected static function SetModulePath($sModulePath) */ public static function AddModule($sFilePath, $sId, $aArgs) { + if (is_null($aArgs)||! is_array($aArgs)){ + throw new ModuleFileReaderException("Error parsing module file args", 0, null, $sFilePath); + } if (!array_key_exists('itop_version', $aArgs)) { // Assume 1.0.2 @@ -115,7 +125,7 @@ public static function AddModule($sFilePath, $sId, $aArgs) if (!array_key_exists($sArgName, $aArgs)) { throw new Exception("Module '$sId': missing argument '$sArgName'"); - } + } } $aArgs['root_dir'] = dirname($sFilePath); @@ -218,7 +228,7 @@ protected static function GetModules($bAbortOnMissingDependency = false, $aModul * @param array $aModulesToLoad List of modules to search for, defaults to all if omitted * @return array * @throws \MissingDependencyException -*/ + */ public static function OrderModulesByDependencies($aModules, $bAbortOnMissingDependency = false, $aModulesToLoad = null) { // Order the modules to take into account their inter-dependencies @@ -303,6 +313,15 @@ public static function RemoveDuplicateModules($aModules) return $aModules; } + private static function GetPhpExpressionEvaluator(): PhpExpressionEvaluator + { + if (!isset(static::$oPhpExpressionEvaluator)) { + static::$oPhpExpressionEvaluator = new PhpExpressionEvaluator([], RunTimeEnvironment::STATIC_CALL_AUTOSELECT_WHITELIST); + } + + return static::$oPhpExpressionEvaluator; + } + protected static function DependencyIsResolved($sDepString, $aOrderedModules, $aSelectedModules) { $bResult = false; @@ -349,19 +368,19 @@ protected static function DependencyIsResolved($sDepString, $aOrderedModules, $a if (version_compare($sCurrentVersion, $sExpectedVersion, $sOperator)) { $aReplacements[$sModuleId] = '(true)'; // Add parentheses to protect against invalid condition causing - // a function call that results in a runtime fatal error + // a function call that results in a runtime fatal error } else { $aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing - // a function call that results in a runtime fatal error + // a function call that results in a runtime fatal error } } else { // module is not present $aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing - // a function call that results in a runtime fatal error + // a function call that results in a runtime fatal error } } } @@ -385,10 +404,10 @@ protected static function DependencyIsResolved($sDepString, $aOrderedModules, $a else { $sBooleanExpr = str_replace(array_keys($aReplacements), array_values($aReplacements), $sDepString); - $bOk = @eval('$bResult = '.$sBooleanExpr.'; return true;'); - if ($bOk == false) - { - SetupLog::Warning("Eval of '$sBooleanExpr' returned false"); + try{ + $bResult = self::GetPhpExpressionEvaluator()->ParseAndEvaluateBooleanExpression($sBooleanExpr); + } catch(ModuleFileReaderException $e){ + //logged already echo "Failed to parse the boolean Expression = '$sBooleanExpr'
"; } } @@ -496,42 +515,12 @@ protected static function ListModuleFiles($sRelDir, $sRootDir) else if (preg_match('/^module\.(.*).php$/i', $sFile, $aMatches)) { self::SetModulePath($sRelDir); - try - { - $sModuleFileContents = file_get_contents($sDirectory.'/'.$sFile); - $sModuleFileContents = str_replace(array(''), '', $sModuleFileContents); - $sModuleFileContents = str_replace('__FILE__', "'".addslashes($sDirectory.'/'.$sFile)."'", $sModuleFileContents); - preg_match_all('/class ([A-Za-z0-9_]+) extends ([A-Za-z0-9_]+)/', $sModuleFileContents, $aMatches); - //print_r($aMatches); - $idx = 0; - foreach($aMatches[1] as $sClassName) - { - if (class_exists($sClassName)) - { - // rename the class inside the code to prevent a "duplicate class" declaration - // and change its parent class as well so that nobody will find it and try to execute it - $sModuleFileContents = str_replace($sClassName.' extends '.$aMatches[2][$idx], $sClassName.'_'.($iDummyClassIndex++).' extends DummyHandler', $sModuleFileContents); - } - $idx++; - } - $bRet = eval($sModuleFileContents); - - if ($bRet === false) - { - SetupLog::Warning("Eval of $sRelDir/$sFile returned false"); - } - - //echo "

Done.

\n"; - } - catch(ParseError $e) - { - // PHP 7 - SetupLog::Warning("Eval of $sRelDir/$sFile caused an exception: ".$e->getMessage()." at line ".$e->getLine()); - } - catch(Exception $e) - { - // Continue... - SetupLog::Warning("Eval of $sRelDir/$sFile caused an exception: ".$e->getMessage()); + $sModuleFilePath = $sDirectory.'/'.$sFile; + try { + $aModuleInfo = ModuleFileReader::GetInstance()->ReadModuleFileInformation($sDirectory.'/'.$sFile); + SetupWebPage::AddModule($sModuleFilePath, $aModuleInfo[1], $aModuleInfo[2]); + } catch(ModuleFileReaderException $e){ + continue; } } } diff --git a/setup/modulediscovery/ModuleFileReader.php b/setup/modulediscovery/ModuleFileReader.php new file mode 100644 index 0000000000..be281403a2 --- /dev/null +++ b/setup/modulediscovery/ModuleFileReader.php @@ -0,0 +1,302 @@ +oPhpExpressionEvaluator = new PhpExpressionEvaluator(static::FUNC_CALL_WHITELIST, static::STATIC_CALLWHITELIST); + } + + final public static function GetInstance(): ModuleFileReader { + if (!isset(static::$oInstance)) { + static::$oInstance = new static(); + } + + return static::$oInstance; + } + + final public static function SetInstance(?ModuleFileReader $oInstance): void { + static::$oInstance = $oInstance; + } + + /** + * Read the information from a module file (module.xxx.php) + * @param string $sModuleFile + * @return array + * @throws ModuleFileReaderException + */ + public function ReadModuleFileInformation(string $sModuleFilePath) : array + { + try + { + $oParser = (new \PhpParser\ParserFactory())->createForNewestSupportedVersion(); + $aNodes = $oParser->parse(file_get_contents($sModuleFilePath)); + } + catch (PhpParser\Error $e) { + throw new \ModuleFileReaderException($e->getMessage(), 0, $e, $sModuleFilePath); + } + + try { + foreach ($aNodes as $sKey => $oNode) { + if ($oNode instanceof \PhpParser\Node\Stmt\Expression) { + $aModuleInfo = $this->GetModuleInformationFromAddModuleCall($sModuleFilePath, $oNode); + if (! is_null($aModuleInfo)){ + $this->CompleteModuleInfoWithFilePath($aModuleInfo); + return $aModuleInfo; + } + } + + if ($oNode instanceof PhpParser\Node\Stmt\If_) { + $aModuleInfo = $this->GetModuleInformationFromIf($sModuleFilePath, $oNode); + if (! is_null($aModuleInfo)){ + $this->CompleteModuleInfoWithFilePath($aModuleInfo); + return $aModuleInfo; + } + } + } + } catch(ModuleFileReaderException $e) { + // Continue... + throw $e; + } catch(Exception $e) { + // Continue... + throw new ModuleFileReaderException("Eval of $sModuleFilePath caused an exception: ".$e->getMessage(), 0, $e, $sModuleFilePath); + } + + throw new ModuleFileReaderException("No proper call to SetupWebPage::AddModule found in module file", 0, null, $sModuleFilePath); + } + + /** + * Read the information from a module file (module.xxx.php) + * Warning: this method is using eval() function to load the ModuleInstallerAPI classes. + * Current method is never called at design/runtime. It is acceptable to use it during setup only. + * @param string $sModuleFile + * @return array + * @throws ModuleFileReaderException + */ + public function ReadModuleFileInformationUnsafe(string $sModuleFilePath) : array + { + $aModuleInfo = []; // will be filled by the "eval" line below... + try + { + $aMatches = []; + $sModuleFileContents = file_get_contents($sModuleFilePath); + $sModuleFileContents = str_replace([''], '', $sModuleFileContents); + $sModuleFileContents = str_replace('__FILE__', "'".addslashes($sModuleFilePath)."'", $sModuleFileContents); + preg_match_all('/class ([A-Za-z0-9_]+) extends ([A-Za-z0-9_]+)/', $sModuleFileContents, $aMatches); + //print_r($aMatches); + $idx = 0; + foreach($aMatches[1] as $sClassName) + { + if (class_exists($sClassName)) + { + // rename any class declaration inside the code to prevent a "duplicate class" declaration + // and change its parent class as well so that nobody will find it and try to execute it + // Note: don't use the same naming scheme as ModuleDiscovery otherwise you 'll have the duplicate class error again !! + $sModuleFileContents = str_replace($sClassName.' extends '.$aMatches[2][$idx], $sClassName.'_Ext_'.(ModuleFileReader::$iDummyClassIndex++).' extends DummyHandler', $sModuleFileContents); + } + $idx++; + } + // Replace the main function call by an assignment to a variable, as an array... + $sModuleFileContents = str_replace(['SetupWebPage::AddModule', 'ModuleDiscovery::AddModule'], '$aModuleInfo = array', $sModuleFileContents); + eval($sModuleFileContents); // Assigns $aModuleInfo + + if (count($aModuleInfo) === 0) + { + throw new ModuleFileReaderException("Eval of $sModuleFilePath did not return the expected information..."); + } + + $this->CompleteModuleInfoWithFilePath($aModuleInfo); + } + catch(ModuleFileReaderException $e) + { + // Continue... + throw $e; + } + catch(ParseError $e) + { + // Continue... + throw new ModuleFileReaderException("Eval of $sModuleFilePath caused a parse error: ".$e->getMessage()." at line ".$e->getLine()); + } + catch(Exception $e) + { + // Continue... + throw new ModuleFileReaderException("Eval of $sModuleFilePath caused an exception: ".$e->getMessage(), 0, $e); + } + return $aModuleInfo; + } + + /** + * + * Internal trick: additional path is added into the module info structure to handle ModuleInstallerAPI execution during setup + * @param array &$aModuleInfo + * + * @return void + */ + private function CompleteModuleInfoWithFilePath(array &$aModuleInfo) + { + if (count($aModuleInfo)==3) { + $aModuleInfo[2]['module_file_path'] = $aModuleInfo[0]; + } + } + + public function GetAndCheckModuleInstallerClass($aModuleInfo) : ?string + { + if (! isset($aModuleInfo['installer'])){ + return null; + } + + $sModuleInstallerClass = $aModuleInfo['installer']; + if (!class_exists($sModuleInstallerClass)) { + $sModuleFilePath = $aModuleInfo['module_file_path']; + $this->ReadModuleFileInformationUnsafe($sModuleFilePath); + } + + if (!class_exists($sModuleInstallerClass)) + { + throw new CoreException("Wrong installer class: '$sModuleInstallerClass' is not a PHP class - Module: ".$aModuleInfo['label']); + } + if (!is_subclass_of($sModuleInstallerClass, 'ModuleInstallerAPI')) + { + throw new CoreException("Wrong installer class: '$sModuleInstallerClass' is not derived from 'ModuleInstallerAPI' - Module: ".$aModuleInfo['label']); + } + + return $sModuleInstallerClass; + } + + /** + * @param string $sModuleFilePath + * @param \PhpParser\Node\Expr\Assign $oAssignation + * + * @return array|null + * @throws \ModuleFileReaderException + */ + private function GetModuleInformationFromAddModuleCall(string $sModuleFilePath, \PhpParser\Node\Stmt\Expression $oExpression) : ?array + { + /** @var Assign $oAssignation */ + $oAssignation = $oExpression->expr; + if (false === ($oAssignation instanceof PhpParser\Node\Expr\StaticCall)) { + return null; + } + + /** @var PhpParser\Node\Expr\StaticCall $oAssignation */ + + if ("SetupWebPage" !== $oAssignation?->class?->name) { + return null; + } + + if ("AddModule" !== $oAssignation?->name?->name) { + return null; + } + + $aArgs = $oAssignation?->args; + if (count($aArgs) != 3) { + throw new ModuleFileReaderException("Not enough parameters when calling SetupWebPage::AddModule", 0, null, $sModuleFilePath); + } + + $oModuleId = $aArgs[1]; + if (false === ($oModuleId instanceof PhpParser\Node\Arg)) { + throw new ModuleFileReaderException("2nd parameter to SetupWebPage::AddModule call issue: " . get_class($oModuleId), 0, null, $sModuleFilePath); + } + + /** @var PhpParser\Node\Arg $oModuleId */ + if (false === ($oModuleId->value instanceof PhpParser\Node\Scalar\String_)) { + throw new ModuleFileReaderException("2nd parameter to SetupWebPage::AddModule not a string: " . get_class($oModuleId->value), 0, null, $sModuleFilePath); + } + + $sModuleId = $this->oPhpExpressionEvaluator->EvaluateExpression($oModuleId->value); + + $oModuleConfigInfo = $aArgs[2]; + if (false === ($oModuleConfigInfo instanceof PhpParser\Node\Arg)) { + throw new ModuleFileReaderException("3rd parameter to SetupWebPage::AddModule call issue: " . get_class($oModuleConfigInfo), 0, null, $sModuleFilePath); + } + + /** @var PhpParser\Node\Arg $oModuleConfigInfo */ + if (false === ($oModuleConfigInfo->value instanceof PhpParser\Node\Expr\Array_)) { + throw new ModuleFileReaderException("3rd parameter to SetupWebPage::AddModule not an array: " . get_class($oModuleConfigInfo->value), 0, null, $sModuleFilePath); + } + + $aModuleConfig = $this->oPhpExpressionEvaluator->EvaluateExpression($oModuleConfigInfo->value); + + if (! is_array($aModuleConfig)){ + throw new ModuleFileReaderException("3rd parameter to SetupWebPage::AddModule not an array: " . get_class($oModuleConfigInfo->value), 0, null, $sModuleFilePath); + } + + return [ + $sModuleFilePath, + $sModuleId, + $aModuleConfig, + ]; + } + + /** + * @param string $sModuleFilePath + * @param \PhpParser\Node\Stmt\If_ $oNode + * + * @return array|null + * @throws \ModuleFileReaderException + */ + private function GetModuleInformationFromIf(string $sModuleFilePath, \PhpParser\Node\Stmt\If_ $oNode) : ?array + { + $bCondition = $this->oPhpExpressionEvaluator->EvaluateExpression($oNode->cond); + if ($bCondition) { + foreach ($oNode->stmts as $oSubNode) { + if ($oSubNode instanceof \PhpParser\Node\Stmt\Expression) { + $aModuleConfig = $this->GetModuleInformationFromAddModuleCall($sModuleFilePath, $oSubNode); + if (!is_null($aModuleConfig)) { + return $aModuleConfig; + } + } + } + + return null; + } + + if (! is_null($oNode->elseifs)) { + foreach ($oNode->elseifs as $oElseIfSubNode) { + /** @var \PhpParser\Node\Stmt\ElseIf_ $oElseIfSubNode */ + $bCondition = $this->oPhpExpressionEvaluator->EvaluateExpression($oElseIfSubNode->cond); + if ($bCondition) { + return $this->GetModuleConfigurationFromStatement($sModuleFilePath, $oElseIfSubNode->stmts); + } + } + } + + if (! is_null($oNode->else)) { + return $this->GetModuleConfigurationFromStatement($sModuleFilePath, $oNode->else->stmts); + } + + return null; + } + + private function GetModuleConfigurationFromStatement(string $sModuleFilePath, array $aStmts) : ?array + { + foreach ($aStmts as $oSubNode) { + if ($oSubNode instanceof \PhpParser\Node\Stmt\Expression) { + $aModuleConfig = $this->GetModuleInformationFromAddModuleCall($sModuleFilePath, $oSubNode); + if (!is_null($aModuleConfig)) { + return $aModuleConfig; + } + } + } + + return null; + } +} diff --git a/setup/modulediscovery/ModuleFileReaderException.php b/setup/modulediscovery/ModuleFileReaderException.php new file mode 100644 index 0000000000..00f074819a --- /dev/null +++ b/setup/modulediscovery/ModuleFileReaderException.php @@ -0,0 +1,23 @@ + $oPrevious?->getMessage(), 'stack' => $e->getTraceAsString()]; + if (!is_null($sModuleFile)) { + $aContext['module_file'] = $sModuleFile; + } + SetupLog::Warning($sMessage, null, $aContext); + parent::__construct($sMessage, $iHttpCode, $oPrevious); + } +} \ No newline at end of file diff --git a/setup/runtimeenv.class.inc.php b/setup/runtimeenv.class.inc.php index bf5cc50c65..fb28ec2475 100644 --- a/setup/runtimeenv.class.inc.php +++ b/setup/runtimeenv.class.inc.php @@ -24,6 +24,8 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ +use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator; + require_once APPROOT."setup/modulediscovery.class.inc.php"; require_once APPROOT.'setup/modelfactory.class.inc.php'; require_once APPROOT.'setup/compiler.class.inc.php'; @@ -39,6 +41,10 @@ class RunTimeEnvironment { + const STATIC_CALL_AUTOSELECT_WHITELIST=[ + "SetupInfo::ModuleIsSelected" + ]; + /** * The name of the environment that the caller wants to build * @var string sFinalEnv @@ -202,10 +208,10 @@ public function AnalyzeInstallation($oConfig, $modulesPath, $bAbortOnMissingDepe if (!in_array($sModuleAppVersion, array('1.0.0', '1.0.1', '1.0.2'))) { // This module is NOT compatible with the current version - $aModuleInfo['install'] = array( - 'flag' => MODULE_ACTION_IMPOSSIBLE, - 'message' => 'the module is not compatible with the current version of the application' - ); + $aModuleInfo['install'] = array( + 'flag' => MODULE_ACTION_IMPOSSIBLE, + 'message' => 'the module is not compatible with the current version of the application' + ); } elseif ($aModuleInfo['mandatory']) { @@ -448,6 +454,8 @@ protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir) } } + $oPhpExpressionEvaluator = new PhpExpressionEvaluator([], RunTimeEnvironment::STATIC_CALL_AUTOSELECT_WHITELIST); + // Now process the 'AutoSelect' modules do { @@ -457,20 +465,16 @@ protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir) { if (!array_key_exists($oModule->GetName(), $aRet) && $oModule->IsAutoSelect()) { - try - { - $bSelected = false; - SetupInfo::SetSelectedModules($aRet); - eval('$bSelected = ('.$oModule->GetAutoSelect().');'); - } - catch(Exception $e) - { - $bSelected = false; - } - if ($bSelected) - { - $aRet[$oModule->GetName()] = $oModule; // store the Id of the selected module - $bModuleAdded = true; + SetupInfo::SetSelectedModules($aRet); + try{ + $bSelected = $oPhpExpressionEvaluator->ParseAndEvaluateBooleanExpression($oModule->GetAutoSelect()); + if ($bSelected) + { + $aRet[$oModule->GetName()] = $oModule; // store the Id of the selected module + $bModuleAdded = true; + } + } catch(ModuleFileReaderException $e){ + //do nothing. logged already } } } @@ -977,8 +981,8 @@ public function Commit() $this->CommitDir( APPROOT.'env-'.$this->sTargetEnv, APPROOT.'env-'.$this->sFinalEnv, - true, - false + true, + false ); // Move the config file @@ -1045,7 +1049,7 @@ protected function CommitFile($sSource, $sDest, $bSourceMustExist = true) * @param $sSource * @param $sDest * @param boolean $bSourceMustExist - * @param boolean $bRemoveSource If true $sSource will be removed, otherwise $sSource will just be emptied + * @param boolean $bRemoveSource If true $sSource will be removed, otherwise $sSource will just be emptied * @throws Exception */ protected function CommitDir($sSource, $sDest, $bSourceMustExist = true, $bRemoveSource = true) @@ -1080,41 +1084,59 @@ public function Rollback() } } - /** - * Call the given handler method for all selected modules having an installation handler - * @param array[] $aAvailableModules - * @param string[] $aSelectedModules - * @param string $sHandlerName - * @throws CoreException - */ + /** + * Call the given handler method for all selected modules having an installation handler + * @param array[] $aAvailableModules + * @param string[] $aSelectedModules + * @param string $sHandlerName + * @throws CoreException + */ public function CallInstallerHandlers($aAvailableModules, $aSelectedModules, $sHandlerName) { - foreach($aAvailableModules as $sModuleId => $aModule) - { - if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules) && - isset($aAvailableModules[$sModuleId]['installer']) ) - { - $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; - SetupLog::Info("Calling Module Handler: $sModuleInstallerClass::$sHandlerName(oConfig, {$aModule['version_db']}, {$aModule['version_code']})"); - $aCallSpec = array($sModuleInstallerClass, $sHandlerName); - if (is_callable($aCallSpec)) - { - try { - call_user_func_array($aCallSpec, array(MetaModel::GetConfig(), $aModule['version_db'], $aModule['version_code'])); - } catch (Exception $e) { - $sErrorMessage = "Module $sModuleId : error when calling module installer class $sModuleInstallerClass for $sHandlerName handler"; - $aExceptionContextData = [ - 'ModulelId' => $sModuleId, - 'ModuleInstallerClass' => $sModuleInstallerClass, - 'ModuleInstallerHandler' => $sHandlerName, - 'ExceptionClass' => get_class($e), - 'ExceptionMessage' => $e->getMessage(), - ]; - throw new CoreException($sErrorMessage, $aExceptionContextData, '', $e); - } - } - } - } + foreach($aAvailableModules as $sModuleId => $aModule) + { + if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules)) + { + $aArgs = [MetaModel::GetConfig(), $aModule['version_db'], $aModule['version_code']]; + RunTimeEnvironment::CallInstallerHandler($aAvailableModules[$sModuleId], $sHandlerName, $aArgs); + } + } + } + + /** + * Call the given handler method for all selected modules having an installation handler + * + * @param array $aModuleInfo + * @param string $sHandlerName + * @param array $aArgs + * + * @throws CoreException + */ + public static function CallInstallerHandler(array $aModuleInfo, $sHandlerName, array $aArgs) + { + $sModuleInstallerClass = ModuleFileReader::GetInstance()->GetAndCheckModuleInstallerClass($aModuleInfo); + if (is_null($sModuleInstallerClass)){ + return; + } + + SetupLog::Info("Calling Module Handler: $sModuleInstallerClass::$sHandlerName", null, $aArgs); + $aCallSpec = [$sModuleInstallerClass, $sHandlerName]; + if (is_callable($aCallSpec)) + { + try { + call_user_func_array($aCallSpec, $aArgs); + } catch (Exception $e) { + $sErrorMessage = "Module $sModuleId : error when calling module installer class $sModuleInstallerClass for $sHandlerName handler"; + $aExceptionContextData = [ + 'ModulelId' => $sModuleId, + 'ModuleInstallerClass' => $sModuleInstallerClass, + 'ModuleInstallerHandler' => $sHandlerName, + 'ExceptionClass' => get_class($e), + 'ExceptionMessage' => $e->getMessage(), + ]; + throw new CoreException($sErrorMessage, $aExceptionContextData, '', $e); + } + } } /** @@ -1143,64 +1165,64 @@ public function LoadData($aAvailableModules, $aSelectedModules, $bSampleData) if ($aModule['version_db'] != '') { // Simulate the load of the previously loaded XML files to get the mapping of the keys if ($bSampleData) { - $aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); - $aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']); - } - else - { - // Load only structural data - $aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); - } - } - else - { - if ($bSampleData) - { - $aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); - $aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']); - } - else - { - // Load only structural data - $aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); - } - } - } - } - } - - // Simulate the load of the previously loaded files, in order to initialize - // the mapping between the identifiers in the XML and the actual identifiers - // in the current database - foreach($aPreviouslyLoadedFiles as $sFileRelativePath) - { - $sFileName = APPROOT.$sFileRelativePath; - SetupLog::Info("Loading file: $sFileName (just to get the keys mapping)"); - if (empty($sFileName) || !file_exists($sFileName)) - { - throw(new Exception("File $sFileName does not exist")); - } - - $oDataLoader->LoadFile($sFileName, true); - $sResult = sprintf("loading of %s done.", basename($sFileName)); - SetupLog::Info($sResult); - } - - foreach($aFiles as $sFileRelativePath) - { - $sFileName = APPROOT.$sFileRelativePath; - SetupLog::Info("Loading file: $sFileName"); - if (empty($sFileName) || !file_exists($sFileName)) - { - throw(new Exception("File $sFileName does not exist")); - } - - $oDataLoader->LoadFile($sFileName); - $sResult = sprintf("loading of %s done.", basename($sFileName)); - SetupLog::Info($sResult); - } - - $oDataLoader->EndSession(); + $aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); + $aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']); + } + else + { + // Load only structural data + $aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); + } + } + else + { + if ($bSampleData) + { + $aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); + $aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']); + } + else + { + // Load only structural data + $aFiles = static::MergeWithRelativeDir($aFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); + } + } + } + } + } + + // Simulate the load of the previously loaded files, in order to initialize + // the mapping between the identifiers in the XML and the actual identifiers + // in the current database + foreach($aPreviouslyLoadedFiles as $sFileRelativePath) + { + $sFileName = APPROOT.$sFileRelativePath; + SetupLog::Info("Loading file: $sFileName (just to get the keys mapping)"); + if (empty($sFileName) || !file_exists($sFileName)) + { + throw(new Exception("File $sFileName does not exist")); + } + + $oDataLoader->LoadFile($sFileName, true); + $sResult = sprintf("loading of %s done.", basename($sFileName)); + SetupLog::Info($sResult); + } + + foreach($aFiles as $sFileRelativePath) + { + $sFileName = APPROOT.$sFileRelativePath; + SetupLog::Info("Loading file: $sFileName"); + if (empty($sFileName) || !file_exists($sFileName)) + { + throw(new Exception("File $sFileName does not exist")); + } + + $oDataLoader->LoadFile($sFileName); + $sResult = sprintf("loading of %s done.", basename($sFileName)); + SetupLog::Info($sResult); + } + + $oDataLoader->EndSession(); SetupLog::Info("ending data load session"); } @@ -1213,12 +1235,12 @@ public function LoadData($aAvailableModules, $aSelectedModules, $bSampleData) */ protected static function MergeWithRelativeDir($aSourceArray, $sBaseDir, $aFilesToMerge) { - $aToMerge = array(); - foreach($aFilesToMerge as $sFile) - { - $aToMerge[] = $sBaseDir.'/'.$sFile; - } - return array_merge($aSourceArray, $aToMerge); + $aToMerge = array(); + foreach($aFilesToMerge as $sFile) + { + $aToMerge[] = $sBaseDir.'/'.$sFile; + } + return array_merge($aSourceArray, $aToMerge); } /** @@ -1227,40 +1249,40 @@ protected static function MergeWithRelativeDir($aSourceArray, $sBaseDir, $aFiles * @throws Exception * @return string */ - public function CheckMetaModel() - { - $iCount = 0; - $fStart = microtime(true); - foreach(MetaModel::GetClasses() as $sClass) - { - if (false == MetaModel::HasTable($sClass) && MetaModel::IsAbstract($sClass)) - { - //if a class is not persisted and is abstract, the code below would crash. Needed by the class AbstractRessource. This is tolerable to skip this because we check the setup process integrity, not the datamodel integrity. - continue; - } - - $oSearch = new DBObjectSearch($sClass); - $oSearch->SetShowObsoleteData(false); - $oSQLQuery = $oSearch->GetSQLQueryStructure(null, false); - $sViewName = MetaModel::DBGetView($sClass); - if (strlen($sViewName) > 64) - { - throw new Exception("Class name too long for class: '$sClass'. The name of the corresponding view ($sViewName) would exceed MySQL's limit for the name of a table (64 characters)."); - } - $sTableName = MetaModel::DBGetTable($sClass); - if (strlen($sTableName) > 64) - { - throw new Exception("Table name too long for class: '$sClass'. The name of the corresponding MySQL table ($sTableName) would exceed MySQL's limit for the name of a table (64 characters)."); - } - $iTableCount = $oSQLQuery->CountTables(); - if ($iTableCount > 61) - { - throw new Exception("Class requiring too many tables: '$sClass'. The structure of the class ($sClass) would require a query with more than 61 JOINS (MySQL's limitation)."); - } - $iCount++; - } - $fDuration = microtime(true) - $fStart; - - return sprintf("Checked %d classes in %.1f ms. No error found.\n", $iCount, $fDuration*1000.0); - } + public function CheckMetaModel() + { + $iCount = 0; + $fStart = microtime(true); + foreach(MetaModel::GetClasses() as $sClass) + { + if (false == MetaModel::HasTable($sClass) && MetaModel::IsAbstract($sClass)) + { + //if a class is not persisted and is abstract, the code below would crash. Needed by the class AbstractRessource. This is tolerable to skip this because we check the setup process integrity, not the datamodel integrity. + continue; + } + + $oSearch = new DBObjectSearch($sClass); + $oSearch->SetShowObsoleteData(false); + $oSQLQuery = $oSearch->GetSQLQueryStructure(null, false); + $sViewName = MetaModel::DBGetView($sClass); + if (strlen($sViewName) > 64) + { + throw new Exception("Class name too long for class: '$sClass'. The name of the corresponding view ($sViewName) would exceed MySQL's limit for the name of a table (64 characters)."); + } + $sTableName = MetaModel::DBGetTable($sClass); + if (strlen($sTableName) > 64) + { + throw new Exception("Table name too long for class: '$sClass'. The name of the corresponding MySQL table ($sTableName) would exceed MySQL's limit for the name of a table (64 characters)."); + } + $iTableCount = $oSQLQuery->CountTables(); + if ($iTableCount > 61) + { + throw new Exception("Class requiring too many tables: '$sClass'. The structure of the class ($sClass) would require a query with more than 61 JOINS (MySQL's limitation)."); + } + $iCount++; + } + $fDuration = microtime(true) - $fStart; + + return sprintf("Checked %d classes in %.1f ms. No error found.\n", $iCount, $fDuration*1000.0); + } } // End of class diff --git a/setup/unattended-install/InstallationFileService.php b/setup/unattended-install/InstallationFileService.php index e44a8d0460..700a1cc0a6 100644 --- a/setup/unattended-install/InstallationFileService.php +++ b/setup/unattended-install/InstallationFileService.php @@ -1,5 +1,7 @@ GetAutoSelectModules() as $sModuleId => $aModule) { try { - $bSelected = false; SetupInfo::SetSelectedModules($this->aSelectedModules); - eval('$bSelected = ('.$aModule['auto_select'].');'); + + $bSelected = $oPhpExpressionEvaluator->ParseAndEvaluateBooleanExpression($aModule['auto_select']); if ($bSelected) { // Modules in data/production-modules/ are considered as mandatory and always installed $this->aSelectedModules[$sModuleId] = true; } } - catch (Exception $e) { + catch (ModuleFileReaderException $e) { + //logged already } } } diff --git a/setup/wizardsteps.class.inc.php b/setup/wizardsteps.class.inc.php index a6bbd1012f..bdcdc69057 100644 --- a/setup/wizardsteps.class.inc.php +++ b/setup/wizardsteps.class.inc.php @@ -40,6 +40,7 @@ */ use Combodo\iTop\Application\WebPage\WebPage; +use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator; require_once(APPROOT.'setup/setuputils.class.inc.php'); require_once(APPROOT.'setup/parameters.class.inc.php'); @@ -90,7 +91,7 @@ public function Display(WebPage $oPage) $oPage->add(""); $oPage->add_ready_script( -<< 0) { alert("Internet Explorer version 10 or older is NOT supported! (Check that IE is not running in compatibility mode)"); @@ -144,7 +145,7 @@ public function Display(WebPage $oPage) $sH2Class = 'text-valid'; } $oPage->add( -<<Prerequisites validation: $sTitle
HTML @@ -271,12 +272,12 @@ public function Display(WebPage $oPage) } $oPage->add('
What do you want to do?
'); $sChecked = ($sInstallMode == 'install') ? ' checked ' : ''; - $oPage->p(''); + $oPage->p(''); $sChecked = ($sInstallMode == 'upgrade') ? ' checked ' : ''; $sDisabled = (($sInstallMode == 'install') && (empty($sPreviousVersionDir))) ? ' disabled' : ''; - $oPage->p(''); + $oPage->p(''); - $sUpgradeDir = utils::HtmlEntities($sPreviousVersionDir); + $sUpgradeDir = utils::HtmlEntities($sPreviousVersionDir); $oPage->add(<<
Location on the disk: @@ -319,7 +320,7 @@ public function Display(WebPage $oPage) $oPage->add(''); //$oPage->add(''); $oPage->add_ready_script( -<<oWizard->SetParameter('source_dir', $this->oWizard->GetParameter('previous_version_dir').'/'.$sSourceDir); - $this->oWizard->SetParameter('datamodel_version', utils::ReadParam('datamodel_previous_version', '', false, 'raw_data')); - break; + $sSourceDir = utils::ReadParam('relative_source_dir', '', false, 'raw_data'); + $this->oWizard->SetParameter('source_dir', $this->oWizard->GetParameter('previous_version_dir').'/'.$sSourceDir); + $this->oWizard->SetParameter('datamodel_version', utils::ReadParam('datamodel_previous_version', '', false, 'raw_data')); + break; case 'use-compatible': - $sDataModelPath = utils::ReadParam('datamodel_path', '', false, 'raw_data'); - $this->oWizard->SetParameter('source_dir', $sDataModelPath); - $this->oWizard->SaveParameter('datamodel_version', ''); - break; + $sDataModelPath = utils::ReadParam('datamodel_path', '', false, 'raw_data'); + $this->oWizard->SetParameter('source_dir', $sDataModelPath); + $this->oWizard->SaveParameter('datamodel_version', ''); + break; default: - // Do nothing, maybe the user pressed the Back button + // Do nothing, maybe the user pressed the Back button } if ($bDisplayLicense) { @@ -469,7 +470,7 @@ public function ProcessParams($bMoveForward = true) public function Display(WebPage $oPage) { $oPage->add_style( -<<add( -<<The datamodel will be upgraded from version $sInstalledDataModelVersion to version $sUpgradeDMVersion.
@@ -617,7 +618,7 @@ public function Display(WebPage $oPage) } $oPage->add_ready_script( -<< 0); return bRet; EOF - ; + ; } } @@ -695,55 +696,55 @@ private function NeedsGdprConsent() return (($sMode === 'install') && SetupUtils::IsConnectableToITopHub($aModules)); } - /** - * @param WebPage $oPage - */ - public function Display(WebPage $oPage) - { - $aLicenses = SetupUtils::GetLicenses(); - $oPage->add_style( - <<add_style( + <<add('

Licenses agreements for the components of '.ITOP_APPLICATION.'

'); - $oPage->add_style('div a.no-arrow { background:transparent; padding-left:0;}'); - $oPage->add_style('.toggle { cursor:pointer; text-decoration:underline; color:#1C94C4; }'); - $oPage->add('
'); - $oPage->add('Components of '.ITOP_APPLICATION.''); - $oPage->add('
    '); - $index = 0; - foreach ($aLicenses as $oLicense) { - $oPage->add('
  • '.$oLicense->product.', © '.$oLicense->author.' is licensed under the '.$oLicense->license_type.' license. (Details)'); - $oPage->add(''); - $oPage->add_ready_script('$(".license_text a").attr("target", "_blank").addClass("no-arrow");'); - $oPage->add_ready_script('$("#toggle_'.$index.'").on("click", function() { $("#license_'.$index.'").toggle(); } );'); - $index++; - } - $oPage->add('
'); - $oPage->add('
'); - $sChecked = ($this->oWizard->GetParameter('accept_license', 'no') == 'yes') ? ' checked ' : ''; - $oPage->add('
'); - if ($this->NeedsGdprConsent()) { - $oPage->add('
'); - $oPage->add('
'); - $oPage->add('European General Data Protection Regulation'); - $oPage->add('
'.ITOP_APPLICATION.' software is compliant with the processing of personal data according to the European General Data Protection Regulation (GDPR).

+ ); + + $oPage->add('

Licenses agreements for the components of '.ITOP_APPLICATION.'

'); + $oPage->add_style('div a.no-arrow { background:transparent; padding-left:0;}'); + $oPage->add_style('.toggle { cursor:pointer; text-decoration:underline; color:#1C94C4; }'); + $oPage->add('
'); + $oPage->add('Components of '.ITOP_APPLICATION.''); + $oPage->add('
    '); + $index = 0; + foreach ($aLicenses as $oLicense) { + $oPage->add('
  • '.$oLicense->product.', © '.$oLicense->author.' is licensed under the '.$oLicense->license_type.' license. (Details)'); + $oPage->add(''); + $oPage->add_ready_script('$(".license_text a").attr("target", "_blank").addClass("no-arrow");'); + $oPage->add_ready_script('$("#toggle_'.$index.'").on("click", function() { $("#license_'.$index.'").toggle(); } );'); + $index++; + } + $oPage->add('
'); + $oPage->add('
'); + $sChecked = ($this->oWizard->GetParameter('accept_license', 'no') == 'yes') ? ' checked ' : ''; + $oPage->add('
'); + if ($this->NeedsGdprConsent()) { + $oPage->add('
'); + $oPage->add('
'); + $oPage->add('European General Data Protection Regulation'); + $oPage->add('
'.ITOP_APPLICATION.' software is compliant with the processing of personal data according to the European General Data Protection Regulation (GDPR).

By installing '.ITOP_APPLICATION.' you agree that some information will be collected by Combodo to help you manage your instances and for statistical purposes. This data remains anonymous until it is associated to a user account on iTop Hub.

List of collected data available in our Data privacy section.


'); - $oPage->add(''); - $oPage->add(''); - $oPage->add('
'); - } - $oPage->add_ready_script('$(".check_select").on("click change", function() { WizardUpdateButtons(); });'); - - $oPage->add_script( - <<add(''); + $oPage->add(''); + $oPage->add('
'); + } + $oPage->add_ready_script('$(".check_select").on("click change", function() { WizardUpdateButtons(); });'); + + $oPage->add_script( + <<sLabel; - $sMessage = json_encode('
'.$sErrorExplanation.'
'); - break; + $sStatus = 'ko'; + $sErrorExplanation = $oCheck->sLabel; + $sMessage = json_encode('
'.$sErrorExplanation.'
'); + break; } if ($oCheck->iSeverity !== CheckResult::TRACE) { @@ -1134,7 +1135,7 @@ public function AsyncAction(WebPage $oPage, $sCode, $aParameters) { public function JSCanMoveForward() { return -<<add('
'); $oPage->add_script( -<<add_ready_script( -<<oPhpExpressionEvaluator)) { + $this->oPhpExpressionEvaluator = new PhpExpressionEvaluator([], RunTimeEnvironment::STATIC_CALL_AUTOSELECT_WHITELIST); + } + + return $this->oPhpExpressionEvaluator; + } + /** * Converts the list of selected "choices" into a list of "modules": take into account the selected and the mandatory modules * @@ -1786,11 +1798,11 @@ protected function GetSelectedModules($aInfo, $aSelectedChoices, &$aModules, $sP if (isset($aInfo['auto_select'])) { // Check the module selection try { - $bSelected = false; SetupInfo::SetSelectedModules($aModules); - eval('$bSelected = ('.$aInfo['auto_select'].');'); + $bSelected = $this->GetPhpExpressionEvaluator()->ParseAndEvaluateBooleanExpression($aInfo['auto_select']); } - catch (Exception $e) { + catch (ModuleFileReaderException $e) { + //logged already $bSelected = false; } } @@ -1825,7 +1837,7 @@ protected function GetSelectedModules($aInfo, $aSelectedChoices, &$aModules, $sP $sChoiceName = $sChoiceId; } if ( (isset($aChoice['mandatory']) && $aChoice['mandatory']) || - (isset($aSelectedChoices[$sChoiceName]) && ($aSelectedChoices[$sChoiceName] == $sChoiceId)) ) + (isset($aSelectedChoices[$sChoiceName]) && ($aSelectedChoices[$sChoiceName] == $sChoiceId)) ) { $sDisplayChoices .= '
  • '.$aChoice['title'].'
  • '; if ($aSelectedExtensions !== null) @@ -1864,20 +1876,19 @@ protected function GetSelectedModules($aInfo, $aSelectedChoices, &$aModules, $sP { try { - $bSelected = false; SetupInfo::SetSelectedModules($aModules); - eval('$bSelected = ('.$aModule['auto_select'].');'); + $bSelected = $this->GetPhpExpressionEvaluator()->ParseAndEvaluateBooleanExpression($aModule['auto_select']); + if ($bSelected) + { + $aModules[$sModuleId] = true; // store the Id of the selected module + $sDisplayChoices .= '
  • '.$aModule['label'].' (auto_select)
  • '; + $bModuleAdded = true; + } } - catch(Exception $e) + catch(ModuleFileReaderException $e) { + //logged already $sDisplayChoices .= '
  • Warning: auto_select failed with exception ('.$e->getMessage().') for module "'.$sModuleId.'"
  • '; - $bSelected = false; - } - if ($bSelected) - { - $aModules[$sModuleId] = true; // store the Id of the selected module - $sDisplayChoices .= '
  • '.$aModule['label'].' (auto_select)
  • '; - $bModuleAdded = true; } } } @@ -1894,11 +1905,11 @@ protected function GetStepIndex() { case 'start_install': case 'start_upgrade': - $index = 0; - break; + $index = 0; + break; default: - $index = (integer)$this->sCurrentState; + $index = (integer)$this->sCurrentState; } return $index; } @@ -1925,10 +1936,10 @@ protected function GetStepInfo($idx = null) // Additional step for the "extensions" $aStepDefinition = array( - 'title' => 'Extensions', - 'description' => '

    Select additional extensions to install. You can launch the installation again to install new extensions, but you cannot remove already installed extensions.

    ', - 'banner' => '/images/icons/icons8-puzzle.svg', - 'options' => array() + 'title' => 'Extensions', + 'description' => '

    Select additional extensions to install. You can launch the installation again to install new extensions, but you cannot remove already installed extensions.

    ', + 'banner' => '/images/icons/icons8-puzzle.svg', + 'options' => array() ); foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension) @@ -1936,14 +1947,14 @@ protected function GetStepInfo($idx = null) if (($oExtension->sSource !== iTopExtension::SOURCE_WIZARD) && ($oExtension->bVisible) && (count($oExtension->aMissingDependencies) == 0)) { $aStepDefinition['options'][] = array( - 'extension_code' => $oExtension->sCode, - 'title' => $oExtension->sLabel, - 'description' => $oExtension->sDescription, - 'more_info' => $oExtension->sMoreInfoUrl, - 'default' => true, // by default offer to install all modules - 'modules' => $oExtension->aModules, - 'mandatory' => $oExtension->bMandatory || ($oExtension->sSource === iTopExtension::SOURCE_REMOTE), - 'source_label' => $this->GetExtensionSourceLabel($oExtension->sSource), + 'extension_code' => $oExtension->sCode, + 'title' => $oExtension->sLabel, + 'description' => $oExtension->sDescription, + 'more_info' => $oExtension->sMoreInfoUrl, + 'default' => true, // by default offer to install all modules + 'modules' => $oExtension->aModules, + 'mandatory' => $oExtension->bMandatory || ($oExtension->sSource === iTopExtension::SOURCE_REMOTE), + 'source_label' => $this->GetExtensionSourceLabel($oExtension->sSource), ); } } @@ -1958,24 +1969,24 @@ protected function GetStepInfo($idx = null) { // No wizard configuration provided, build a standard one with just one big list $aStepDefinition = array( - 'title' => 'Modules Selection', - 'description' => '

    Select the modules to install. You can launch the installation again to install new modules, but you cannot remove already installed modules.

    ', - 'banner' => '/images/icons/icons8-apps-tab.svg', - 'options' => array() + 'title' => 'Modules Selection', + 'description' => '

    Select the modules to install. You can launch the installation again to install new modules, but you cannot remove already installed modules.

    ', + 'banner' => '/images/icons/icons8-apps-tab.svg', + 'options' => array() ); foreach($this->oExtensionsMap->GetAllExtensions() as $oExtension) { if (($oExtension->bVisible) && (count($oExtension->aMissingDependencies) == 0)) { $aStepDefinition['options'][] = array( - 'extension_code' => $oExtension->sCode, - 'title' => $oExtension->sLabel, - 'description' => $oExtension->sDescription, - 'more_info' => $oExtension->sMoreInfoUrl, - 'default' => true, // by default offer to install all modules - 'modules' => $oExtension->aModules, - 'mandatory' => $oExtension->bMandatory || ($oExtension->sSource !== iTopExtension::SOURCE_REMOTE), - 'source_label' => $this->GetExtensionSourceLabel($oExtension->sSource), + 'extension_code' => $oExtension->sCode, + 'title' => $oExtension->sLabel, + 'description' => $oExtension->sDescription, + 'more_info' => $oExtension->sMoreInfoUrl, + 'default' => true, // by default offer to install all modules + 'modules' => $oExtension->aModules, + 'mandatory' => $oExtension->bMandatory || ($oExtension->sSource !== iTopExtension::SOURCE_REMOTE), + 'source_label' => $this->GetExtensionSourceLabel($oExtension->sSource), ); } } @@ -1996,17 +2007,17 @@ protected function GetExtensionSourceLabel($sSource) switch($sSource) { case iTopExtension::SOURCE_MANUAL: - $sResult = 'Local extensions folder'; - $sDecorationClass = 'fas fa-folder'; - break; + $sResult = 'Local extensions folder'; + $sDecorationClass = 'fas fa-folder'; + break; case iTopExtension::SOURCE_REMOTE: - $sResult = (ITOP_APPLICATION == 'iTop') ? 'iTop Hub' : 'ITSM Designer'; - $sDecorationClass = (ITOP_APPLICATION == 'iTop') ? 'fc fc-chameleon-icon' : 'fa pencil-ruler'; - break; + $sResult = (ITOP_APPLICATION == 'iTop') ? 'iTop Hub' : 'ITSM Designer'; + $sDecorationClass = (ITOP_APPLICATION == 'iTop') ? 'fc fc-chameleon-icon' : 'fa pencil-ruler'; + break; default: - $sResult = ''; + $sResult = ''; } if ($sResult == '') { @@ -2591,10 +2602,10 @@ public function Display(WebPage $oPage) $oProductionEnv->InitDataModel($oConfig, true); $sIframeUrl = $oConfig->GetModuleSetting('itop-hub-connector', 'setup_url', ''); - $sSetupTokenFile = APPROOT.'data/.setup'; - $sSetupToken = bin2hex(random_bytes(12)); - file_put_contents($sSetupTokenFile, $sSetupToken); - $sIframeUrl.= "&setup_token=$sSetupToken"; + $sSetupTokenFile = APPROOT.'data/.setup'; + $sSetupToken = bin2hex(random_bytes(12)); + file_put_contents($sSetupTokenFile, $sSetupToken); + $sIframeUrl.= "&setup_token=$sSetupToken"; if ($sIframeUrl != '') { diff --git a/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php b/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php new file mode 100644 index 0000000000..80eb6c4db5 --- /dev/null +++ b/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php @@ -0,0 +1,56 @@ +oConstExprEvaluator = new ConstExprEvaluator(); + $this->oConstExprEvaluator->setStaticcallsWhitelist($staticCallsWhitelist); + $this->oConstExprEvaluator->setFunctionsWhitelist($functionsWhiteList); + } + + public function EvaluateExpression(Expr $oExpression) : mixed + { + return $this->oConstExprEvaluator->evaluateDirectly($oExpression); + } + + /** + * @param string $sBooleanExpr + * + * @return bool + * @throws \ModuleFileReaderException + */ + public function ParseAndEvaluateBooleanExpression(string $sBooleanExpr) : bool + { + return $this->ParseAndEvaluateExpression($sBooleanExpr); + } + + public function ParseAndEvaluateExpression(string $sExpr) : mixed + { + $sPhpContent = <<createForNewestSupportedVersion(); + $aNodes = $oParser->parse($sPhpContent); + $oExpr = $aNodes[0]; + return $this->EvaluateExpression($oExpr->expr); + } catch (\Throwable $t) { + throw new ModuleFileReaderException("Eval of '$sExpr' caused an error:".$t->getMessage()); + } + } +} \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/setup/modulediscovery/ModuleFileReaderTest.php b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/ModuleFileReaderTest.php new file mode 100644 index 0000000000..622a74c00e --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/ModuleFileReaderTest.php @@ -0,0 +1,220 @@ +RequireOnceItopFile('setup/modulediscovery/ModuleFileReader.php'); + } + + public function testReadModuleFileInformationUnsafe() + { + $sModuleFilePath = __DIR__.'/resources/module.itop-full-itil.php'; + $aRes = ModuleFileReader::GetInstance()->ReadModuleFileInformationUnsafe($sModuleFilePath); + + $this->assertCount(3, $aRes); + $this->assertEquals($sModuleFilePath, $aRes[0]); + $this->assertEquals('itop-full-itil/3.3.0', $aRes[1]); + $this->assertIsArray($aRes[2]); + $this->assertArrayHasKey('label', $aRes[2]); + $this->assertEquals('Bridge - Request management ITIL + Incident management ITIL', $aRes[2]['label'] ?? null); + } + + public static function ReadModuleFileConfigurationFileNameProvider() + { + $aUsecases=[]; + foreach (glob(__DIR__.'/resources/*.php') as $sModuleFilePath){ + if (false !== strpos($sModuleFilePath, "module.__MODULE__.php")){ + continue; + } + $aUsecases[basename($sModuleFilePath)]=[$sModuleFilePath]; + } + + return $aUsecases; + } + + /** + * @dataProvider ReadModuleFileConfigurationFileNameProvider + */ + public function testReadModuleFileConfigurationVsLegacyMethod(string $sModuleFilePath) + { + $_SERVER=[ + 'SERVER_NAME' => 'titi' + ]; + + $aRes = ModuleFileReader::GetInstance()->ReadModuleFileInformation($sModuleFilePath); + $aExpected = ModuleFileReader::GetInstance()->ReadModuleFileInformationUnsafe($sModuleFilePath); + + $this->assertEquals($aExpected, $aRes); + } + + public function testReadModuleFileConfigurationParsingIssue() + { + $sModuleFilePath = __DIR__.'/resources/module.__MODULE__.php'; + + $this->expectException(\ModuleFileReaderException::class); + $this->expectExceptionMessage("Syntax error, unexpected T_CONSTANT_ENCAPSED_STRING, expecting ',' or ']' or ')' on line 31"); + + ModuleFileReader::GetInstance()->ReadModuleFileInformation($sModuleFilePath); + } + + /** + * local tool function + */ + private function CallReadModuleFileConfiguration($sPHpCode) + { + $this->sTempModuleFilePath = tempnam(__DIR__, "test"); + file_put_contents($this->sTempModuleFilePath, $sPHpCode); + try { + return ModuleFileReader::GetInstance()->ReadModuleFileInformation($this->sTempModuleFilePath); + } + finally { + @unlink($this->sTempModuleFilePath); + } + } + + public function testReadModuleFileConfigurationCheckBasicStatementWithoutIf() + { + $sPHP = << "d"]); +\$b=2; +PHP; + $val = $this->CallReadModuleFileConfiguration($sPHP); + $this->assertEquals([$this->sTempModuleFilePath, "noif", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val); + } + + public function testReadModuleFileConfigurationCheckBasicStatement_IfConditionVerified() + { + $sPHP = << "d"]); +} elseif (true){ + SetupWebPage::AddModule("a", "elseif1", ["c" => "d"]); +} elseif (true){ + SetupWebPage::AddModule("a", "elseif2", ["c" => "d"]); +} else { + SetupWebPage::AddModule("a", "else", ["c" => "d"]); +} +SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]); +\$b=2; +PHP; + $val = $this->CallReadModuleFileConfiguration($sPHP); + $this->assertEquals([$this->sTempModuleFilePath, "if", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val); + } + + public function testReadModuleFileConfigurationCheckBasicStatement_IfNoConditionVerifiedAndNoElse() + { + $sPHP = << "d"]); +} elseif (false){ + SetupWebPage::AddModule("a", "elseif1", ["c" => "d"]); +} elseif (false){ + SetupWebPage::AddModule("a", "elseif2", ["c" => "d"]); +} +SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]); +\$b=2; +PHP; + $val = $this->CallReadModuleFileConfiguration($sPHP); + $this->assertEquals([$this->sTempModuleFilePath, "outsideif", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val); + } + + public function testReadModuleFileConfigurationCheckBasicStatement_ElseApplied() + { + $sPHP = << "d"]); +} elseif (false){ + SetupWebPage::AddModule("a", "elseif1", ["c" => "d"]); +} elseif (false){ + SetupWebPage::AddModule("a", "elseif2", ["c" => "d"]); +} else { + SetupWebPage::AddModule("a", "else", ["c" => "d"]); +} +SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]); +\$b=2; +PHP; + $val = $this->CallReadModuleFileConfiguration($sPHP); + $this->assertEquals([$this->sTempModuleFilePath, "else", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val); + } + + public function testReadModuleFileConfigurationCheckBasicStatement_FirstElseIfApplied() + { + $sPHP = << "d"]); +} elseif (true){ + SetupWebPage::AddModule("a", "elseif1", ["c" => "d"]); +} elseif (true){ + SetupWebPage::AddModule("a", "elseif2", ["c" => "d"]); +} else { + SetupWebPage::AddModule("a", "else", ["c" => "d"]); +} +SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]); +\$b=2; +PHP; + $val = $this->CallReadModuleFileConfiguration($sPHP); + $this->assertEquals([$this->sTempModuleFilePath, "elseif1", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val); + } + + public function testReadModuleFileConfigurationCheckBasicStatement_LastElseIfApplied() + { + $sPHP = << "d"]); +} elseif (false){ + SetupWebPage::AddModule("a", "elseif1", ["c" => "d"]); +} elseif (true){ + SetupWebPage::AddModule("a", "elseif2", ["c" => "d"]); +} else { + SetupWebPage::AddModule("a", "else", ["c" => "d"]); +} +SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]); +\$b=2; +PHP; + $val = $this->CallReadModuleFileConfiguration($sPHP); + $this->assertEquals([$this->sTempModuleFilePath, "elseif2", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val); + } + + public function testGetAndCheckModuleInstallerClass() + { + $sModuleInstallerClass = "TicketsInstaller" . uniqid(); + $sPHpCode = file_get_contents(__DIR__.'/resources/module.itop-tickets.php'); + $sPHpCode = str_replace("TicketsInstaller", $sModuleInstallerClass, $sPHpCode); + $this->sTempModuleFilePath = tempnam(__DIR__, "test"); + file_put_contents($this->sTempModuleFilePath, $sPHpCode); + var_dump($sPHpCode); + + try { + $this->assertFalse(class_exists($sModuleInstallerClass)); + $aModuleInfo = ModuleFileReader::GetInstance()->ReadModuleFileInformation($this->sTempModuleFilePath); + $this->assertFalse(class_exists($sModuleInstallerClass)); + + $this->assertEquals($sModuleInstallerClass, ModuleFileReader::GetInstance()->GetAndCheckModuleInstallerClass($aModuleInfo[2])); + } + finally { + @unlink($this->sTempModuleFilePath); + } + + $this->assertTrue(class_exists($sModuleInstallerClass)); + } +} \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.__MODULE__.php b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.__MODULE__.php new file mode 100644 index 0000000000..ae77342eb0 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.__MODULE__.php @@ -0,0 +1,56 @@ + '__module_label__', + 'category' => '__module_category__', + + // Setup + // + 'dependencies' => [ + __module_dependencies__ + ], + 'mandatory' => __module_mandatory__, + 'visible' => __module_visible__, + __module_setup_handler_class__ + + // Components + // + 'datamodel' => [ + 'vendor/autoload.php', + __module_data_model__, // Contains the PHP code generated by the "compilation" of datamodel.__module_name__.xml + ], + 'webservice' => [], + 'data.struct' => [ + // add your 'structure' definition XML files here, + ], + 'data.sample' => [ + // add your sample data XML files here, + ], + + // Documentation + // + 'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any + 'doc.more_information' => '', // hyperlink to more information, if any + + // Default settings + // + 'settings' => [ + // Module specific settings go here, if any + ], + ] +); + +__module_setup_handler__ diff --git a/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.authent-ldap.php b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.authent-ldap.php new file mode 100644 index 0000000000..6124cd2f8b --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.authent-ldap.php @@ -0,0 +1,80 @@ + 'User authentication based on LDAP', + 'category' => 'authentication', + + // Setup + // + 'dependencies' => array( + ), + 'mandatory' => false, + 'visible' => true, + 'installer' => 'AuthentLDAPInstaller', + + // Components + // + 'datamodel' => array( + ), + 'data.struct' => array( + //'data.struct.authent-ldap.xml', + ), + 'data.sample' => array( + //'data.sample.authent-ldap.xml', + ), + + // Documentation + // + 'doc.manual_setup' => '', + 'doc.more_information' => '', + + // Default settings + // + 'settings' => array( + 'uri' => 'ldap://localhost', // URI with host or IP address of your LDAP server + 'default_user' => '', // User and password used for initial "Anonymous" bind to LDAP + 'default_pwd' => '', // Leave both blank, if anonymous (read-only) bind is allowed + 'base_dn' => 'dc=yourcompany,dc=com', // Base DN for User queries, adjust it to your LDAP schema + 'user_query' => '(&(uid=%1$s)(inetuserstatus=ACTIVE))', // Query used to retrieve each user %1$s => iTop login + // For Windows AD use (samaccountname=%1$s) or (userprincipalname=%1$s) + + // Some extra LDAP options, refer to: http://www.php.net/manual/en/function.ldap-set-option.php for more info + 'options' => array( + LDAP_OPT_PROTOCOL_VERSION => 3, + LDAP_OPT_REFERRALS => 0, + ), + 'start_tls' => false, + 'debug' => false, + 'servers' => array(), + ), + ) +); + +// Module installation handler +// +class AuthentLDAPInstaller extends ModuleInstallerAPI +{ + public static function AfterDataLoad(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion) + { + + } + + public static function BeforeWritingConfig(Config $oConfiguration) + { + + } +} + +} // if (function_exists('ldap_connect')) diff --git a/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.combodo-email-synchro.php b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.combodo-email-synchro.php new file mode 100644 index 0000000000..c4d0675a88 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.combodo-email-synchro.php @@ -0,0 +1,123 @@ + 'Tickets synchronization via e-mail', + 'category' => 'business', + // Setup + 'dependencies' => array( + 'itop-profiles-itil/3.0.0', + ), + 'mandatory' => false, + 'visible' => true, + 'installer' => 'EmailSynchroInstaller', + // Components + 'datamodel' => array( + 'classes/autoload.php', + 'model.combodo-email-synchro.php', + ), + 'dictionary' => array(), + 'data.struct' => array( + ), + 'data.sample' => array( + ), + // Documentation + 'doc.manual_setup' => '', // No manual installation required + 'doc.more_information' => '', // None + // Default settings + 'settings' => array( + 'notify_errors_to' => '', // mandatory to track errors not handled by the email processing module + 'notify_errors_from' => '', // mandatory as well (can be set at the same value as notify_errors_to) + 'debug' => false, // Set to true to turn on debugging + 'periodicity' => 30, // interval at which to check for incoming emails (in s) + 'retention_period' => 1, // number of hour we keep the replica + 'body_parts_order' => 'text/html,text/plain', // Order in which to read the parts of the incoming emails + 'pop3_auth_option' => 'USER', + 'imap_options' => array('imap'), + 'imap_open_options' => array(), + 'maximum_email_size' => '10M', // Maximum allowed size for incoming emails + 'big_files_dir' => '', + 'exclude_attachment_types' => array('application/exe'), // Example: 'application/exe', 'application/x-winexe', 'application/msdos-windows' + // Lines to be removed just above the 'new part' in a reply-to message... add your own patterns below + 'introductory-patterns' => array( + '/^le .+ a écrit :$/i', // Thunderbird French + '/^on .+ wrote:$/i', // Thunderbird English + '|^[0-9]{4}/[0-9]{1,2}/[0-9]{1,2} .+:$|', // Gmail style + ), + // Some patterns which delimit the previous message in case of a Reply + // The "new" part of the message is the text before the pattern + // Add your own multi-line patterns (use \\R for a line break) + // These patterns depend on the mail client/server used... feel free to add your own discoveries to the list + 'multiline-delimiter-patterns' => array( + '/\\RFrom: .+\\RSent: .+\\R/m', // Outlook English + '/\\R_+\\R/m', // A whole line made only of underscore characters + '/\\RDe : .+\\R\\R?Envoyé : /m', // Outlook French, HTML and rich text + '/\\RDe : .+\\RDate d\'envoi : .+\\R/m', // Outlook French, plain text + '/\\R-----Message d\'origine-----\\R/m', + ), + 'use_message_id_as_uid' => false, // Do NOT change this unless you known what you are doing!! + 'images_minimum_size' => '100x20', // Images smaller that these dimensions will be ignored (signatures...) + 'images_maximum_size' => '', // Images bigger that these dimensions will be resized before uploading into iTop + 'recommended_max_allowed_packet' => 10*1024*1024, // MySQL parameter for attachments + ), + ) +); + +if (!class_exists('EmailSynchroInstaller')) +{ + + // Module installation handler + // + class EmailSynchroInstaller extends ModuleInstallerAPI + { + + /** + * Handler called after the creation/update of the database schema + * + * @param $oConfiguration Config The new configuration of the application + * @param $sPreviousVersion string Previous version number of the module (empty string in case of first install) + * @param $sCurrentVersion string Current version number of the module + * + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \DictExceptionMissingString + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + */ + public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion) + { + // For each email sources, update email replicas by setting mailbox_path to source.mailbox where mailbox_path is null + SetupLog::Info("Updating email replicas to set their mailbox path."); + + // Preparing mailboxes search + $oSearch = new DBObjectSearch('MailInboxBase'); + + // Retrieving definition of attribute to update + $sTableName = MetaModel::DBGetTable('EmailReplica'); + + $UidlAttDef = MetaModel::GetAttributeDef('EmailReplica', 'uidl'); + $sUidlColName = $UidlAttDef->Get('sql'); + + $oMailboxAttDef = MetaModel::GetAttributeDef('EmailReplica', 'mailbox_path'); + $sMailboxColName = $oMailboxAttDef->Get('sql'); + + $sFrienlynameAttCode = MetaModel::GetFriendlyNameAttributeCode('EmailReplica'); + + // Looping on inboxes to update + $oSet = new DBObjectSet($oSearch); + while ($oInbox = $oSet->Fetch()) + { + $sUpdateQuery = "UPDATE $sTableName SET $sMailboxColName = " . CMDBSource::Quote($oInbox->Get('mailbox')) . " WHERE $sUidlColName LIKE " . CMDBSource::Quote($oInbox->Get('login') . '_%') . " AND $sMailboxColName IS NULL"; + SetupLog::Info("Executing query: " . $sUpdateQuery); + $iRet = CMDBSource::Query($sUpdateQuery); // Throws an exception in case of error + SetupLog::Info("Updated $iRet rows."); + } + } + + } + +} diff --git a/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-admin-delegation-profiles-bridge-for-combodo-email-synchro.php b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-admin-delegation-profiles-bridge-for-combodo-email-synchro.php new file mode 100644 index 0000000000..e94a8d770e --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-admin-delegation-profiles-bridge-for-combodo-email-synchro.php @@ -0,0 +1,51 @@ + 'Profiles per admin fonction: Mail inboxes and messages', + 'category' => 'Datamodel', + + // Setup + // + 'dependencies' => array( + 'itop-admin-delegation-profiles/1.0.0', + 'itop-admin-delegation-profiles/1.0.0 || combodo-email-synchro/3.7.2 || itop-oauth-client/2.7.7', // Optional dependency to silence the setup to not display a warning if the other module is not present + ), + 'mandatory' => false, + 'visible' => false, + 'auto_select' => 'SetupInfo::ModuleIsSelected("itop-admin-delegation-profiles") && SetupInfo::ModuleIsSelected("combodo-email-synchro") && SetupInfo::ModuleIsSelected("itop-oauth-client")', + + // Components + // + 'datamodel' => array( + 'model.itop-admin-delegation-profiles-bridge-for-combodo-email-synchro.php' + ), + 'webservice' => array( + + ), + 'data.struct' => array( + // add your 'structure' definition XML files here, + ), + 'data.sample' => array( + // add your sample data XML files here, + ), + + // Documentation + // + 'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any + 'doc.more_information' => '', // hyperlink to more information, if any + + // Default settings + // + 'settings' => array( + // Module specific settings go here, if any + ), + ) +); diff --git a/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-admin-delegation-profiles.php b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-admin-delegation-profiles.php new file mode 100644 index 0000000000..fa29d1ff82 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-admin-delegation-profiles.php @@ -0,0 +1,52 @@ + 'Profiles per admin fonction', + 'category' => 'Datamodel', + + // Setup + // + 'dependencies' => array( + 'itop-config-mgmt/2.7.0' || 'itop-structure/3.0.0', + // itop-profiles-itil is here to ensure that the /itop_design/groups/group[@id="History"] alteration comes after those from that module. + // This allows to define the missing "History" group in iTop 2.7 / 3.0, while merging smoothly with iTop 3.1+ + 'itop-profiles-itil/2.7.0', + ), + 'mandatory' => false, + 'visible' => true, + + // Components + // + 'datamodel' => array( + 'model.itop-admin-delegation-profiles.php' + ), + 'webservice' => array( + + ), + 'data.struct' => array( + // add your 'structure' definition XML files here, + ), + 'data.sample' => array( + // add your sample data XML files here, + ), + + // Documentation + // + 'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any + 'doc.more_information' => '', // hyperlink to more information, if any + + // Default settings + // + 'settings' => array( + // Module specific settings go here, if any + ), + ) +); diff --git a/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-full-itil.php b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-full-itil.php new file mode 100644 index 0000000000..daa46e6910 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-full-itil.php @@ -0,0 +1,41 @@ + 'Bridge - Request management ITIL + Incident management ITIL', + 'category' => 'business', + // Setup + // + 'dependencies' => array( + 'itop-request-mgmt-itil/2.3.0', + 'itop-incident-mgmt-itil/2.3.0', + ), + 'mandatory' => false, + 'visible' => false, + 'auto_select' => 'SetupInfo::ModuleIsSelected("itop-request-mgmt-itil") && SetupInfo::ModuleIsSelected("itop-incident-mgmt-itil")', + // Components + // + 'datamodel' => array(), + 'webservice' => array(), + 'data.struct' => array(// add your 'structure' definition XML files here, + ), + 'data.sample' => array(// add your sample data XML files here, + ), + // Documentation + // + 'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any + 'doc.more_information' => '', // hyperlink to more information, if any + // Default settings + // + 'settings' => array(// Module specific settings go here, if any + ), + ) +); diff --git a/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-global-requests-mgmt.php b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-global-requests-mgmt.php new file mode 100644 index 0000000000..023b1046f3 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-global-requests-mgmt.php @@ -0,0 +1,75 @@ + 'iTop Global Requests Management', + 'category' => 'business', + + // Setup + // + 'dependencies' => array( + 'itop-portal-base/3.2.0', + ), + 'mandatory' => false, + 'visible' => true, + 'installer' => GlobalRequestInstaller::class, + + // Components + // + 'datamodel' => array( + 'vendor/autoload.php', + ), + 'webservice' => array(), + 'data.struct' => array(// add your 'structure' definition XML files here, + ), + 'data.sample' => array(// add your sample data XML files here, + ), + + // Documentation + // + 'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any + 'doc.more_information' => '', // hyperlink to more information, if any + + // Default settings + // + 'settings' => array( + 'target_state' => 'new', + 'bypass_profiles' => 'Administrator, Service Manager', + 'reuse_previous_answers' => true, + ), + ) +); + + +class GlobalRequestInstaller extends ModuleInstallerAPI +{ + /** + * Handler called before creating or upgrading the database schema + * + * @param $oConfiguration Config The new configuration of the application + * @param $sPreviousVersion string Previous version number of the module (empty string in case of first install) + * @param $sCurrentVersion string Current version number of the module + * + * @throws \CoreException + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + */ + public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion) + { + //code + } +} + + diff --git a/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-tickets.php b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-tickets.php new file mode 100644 index 0000000000..f46fc1f231 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.itop-tickets.php @@ -0,0 +1,107 @@ + 'Tickets Management', + 'category' => 'business', + + // Setup + // + 'dependencies' => array( + 'itop-structure/2.7.1', + ), + 'mandatory' => false, + 'visible' => true, + 'installer' => 'TicketsInstaller', + + // Components + // + 'datamodel' => array( + 'main.itop-tickets.php', + ), + 'data.struct' => array( + // 'data.struct.ta-actions.xml', + ), + 'data.sample' => array( + ), + + // Documentation + // + 'doc.manual_setup' => '', + 'doc.more_information' => '', + + // Default settings + // + 'settings' => array( + ), + ) +); + +// Module installation handler +// +class TicketsInstaller extends ModuleInstallerAPI +{ + public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion) + { + // Delete all Triggers corresponding to a no more valid class + CMDBObject::SetTrackInfo('Uninstallation'); + $oSearch = new DBObjectSearch('TriggerOnObject'); + $oSet = new DBObjectSet($oSearch); + while($oTrigger = $oSet->Fetch()) + { + try + { + if (!MetaModel::IsValidClass($oTrigger->Get('target_class'))) + { + $oTrigger->DBDelete(); + } + } + catch(Exception $e) + { + utils::EnrichRaisedException($oTrigger, $e); + } + } + // It's not very clear if it make sense to test a particular version, + // as the loading mechanism checks object existence using reconc_keys + // and do not recreate them, nor update existing. + // Without test, new entries added to the data files, would be automatically loaded + if (($sPreviousVersion === '') || + (version_compare($sPreviousVersion, $sCurrentVersion, '<') + && version_compare($sPreviousVersion, '3.0.0', '<'))) { + $oDataLoader = new XMLDataLoader(); + + CMDBObject::SetTrackInfo("Initialization TicketsInstaller"); + $oMyChange = CMDBObject::GetCurrentChange(); + + $sLang = null; + // - Try to get app. language from configuration fil (app. upgrade) + $sConfigFileName = APPCONF.'production/'.ITOP_CONFIG_FILE; + if (file_exists($sConfigFileName)) { + $oFileConfig = new Config($sConfigFileName); + if (is_object($oFileConfig)) { + $sLang = str_replace(' ', '_', strtolower($oFileConfig->GetDefaultLanguage())); + } + } + + // - I still no language, get the default one + if (null === $sLang) { + $sLang = str_replace(' ', '_', strtolower($oConfiguration->GetDefaultLanguage())); + } + + $sFileName = dirname(__FILE__)."/data/{$sLang}.data.itop-tickets.xml"; + SetupLog::Info("Searching file: $sFileName"); + if (!file_exists($sFileName)) { + $sFileName = dirname(__FILE__)."/data/en_us.data.itop-tickets.xml"; + } + SetupLog::Info("Loading file: $sFileName"); + $oDataLoader->StartSession($oMyChange); + $oDataLoader->LoadFile($sFileName, false, true); + $oDataLoader->EndSession(); + } + } +} diff --git a/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.servername-datamodel-ticket.php b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.servername-datamodel-ticket.php new file mode 100644 index 0000000000..bf29b89064 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/resources/module.servername-datamodel-ticket.php @@ -0,0 +1,79 @@ + 'I3S Datamodel tickets', + 'category' => 'business', + + // Setup + // + 'dependencies' => array( + 'itop-attachments/2.5.0', + ), + 'mandatory' => false, + 'visible' => true, + + // Components + // + 'datamodel' => array( + 'model.servername-datamodel-ticket.php', + 'main.servername-datamodel-ticket.php' + ), + 'webservice' => array( + + ), + 'data.struct' => array( + // add your 'structure' definition XML files here, + ), + 'data.sample' => array( + // add your sample data XML files here, + ), + + // Documentation + // + 'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any + 'doc.more_information' => '', // hyperlink to more information, if any + + // Default settings + // + 'settings' => array( + // url d'accès au répertoire des traces applicatives liées aux tickets + 'traces_base_url' => 'file://'.$_SERVER['SERVER_NAME'].'/traces', + // répertoire des faqi liées aux tickets + 'traces_base_dir_faqi' => '/data/i3s-gsit-tt/faqi', + // url d'accès au répertoire des faqi liées aux tickets + // ce serveur est-il le serveur de consolidation ? + 'consolidation_server' => false, + // restriction des franchissements + 'max_allowed_transitions' => array( + // Les noms des transitions sont visibles dans Outils d'admin => Modèle de données => Incident => Cycle de vie + // Les transitions qui ne sont pas présentes dans ce tableau sont considérées comme étant à 0 + /* 'nom technique de la transition' => nombre maximal autorisé */ + 'ev_askinfo' => 0, // Demander des informations (au demandeur) + 'ev_assign' => 0, // Assigner + 'ev_cancel_by_user' => 0, // Annuler (par le demandeur) + 'ev_cancel' => 0, // Annuler + 'ev_close' => 0, // Clore + 'ev_escalate' => 0, // Escalader + 'ev_giveinfo' => 0, // Envoyer les informations + 'ev_monitor' => 0, // Surveiller + 'ev_pending' => 0, // En attente + 'ev_reassign' => 0, // Ré-assigner + 'ev_refuse_reject' => 0, // Refuser le rejet + 'ev_refuse_solution' => 0, // Refuser la solution + 'ev_reject' => 0, // Rejeter + 'ev_resolve' => 0, // Marquer comme résolu + 'ev_suspend' => 0, // Suspendre + 'ev_terminate' => 0, // Solder + 'ev_verify' => 0, // Accepter la solution / Confirmer la résolution + ), + ), + ) +); diff --git a/tests/php-unit-tests/unitary-tests/sources/PhpParser/Evaluation/PhpExpressionEvaluatorTest.php b/tests/php-unit-tests/unitary-tests/sources/PhpParser/Evaluation/PhpExpressionEvaluatorTest.php new file mode 100644 index 0000000000..52ef423554 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/sources/PhpParser/Evaluation/PhpExpressionEvaluatorTest.php @@ -0,0 +1,243 @@ + "a"]' => ['sExpression' => '[1000 => "a"]'], + 'Array: ["a"]' => ['sExpression' => '["a"]'], + 'Array dict: ["a"=>"b"]' => ['sExpression' => '["a"=>"b"]'], + 'ArrayDimFetch: $_SERVER[\'toto\']' => ['sExpression' => '$_SERVER[\'toto\']'], + 'BinaryOperator: false|true' => [ 'sExpression' => 'false|true'], + 'BinaryOperator: false||true' => [ 'sExpression' => 'false||true'], + 'BinaryOperator: false&&true' => [ 'sExpression' => 'false&&true'], + 'BinaryOperator: true&&true&&true&&false' => [ 'sExpression' => 'true && true && true && false'], + 'BinaryOperator: false&true' => [ 'sExpression' => 'false&true'], + 'BinaryOperator: ! true' => [ 'sExpression' => '! true'], + 'BinaryOperator: 10 * 5' => [ 'sExpression' => '10 * 5'], + 'BinaryOperator: 1 > 2' => [ 'sExpression' => '1 > 2'], + 'BinaryOperator: 1 >= 1' => [ 'sExpression' => '1 >= 1'], + 'BinaryOperator: 1 <= 1' => [ 'sExpression' => '1 <= 1'], + 'BinaryOperator: PHP_VERSION_ID == PHP_VERSION_ID' => [ 'sExpression' => 'PHP_VERSION_ID == PHP_VERSION_ID'], + 'BinaryOperator: PHP_VERSION_ID != PHP_VERSION_ID' => [ 'sExpression' => 'PHP_VERSION_ID != PHP_VERSION_ID'], + 'BitwiseNot: ~3' => ['sExpression' => '~3'], + 'BitwiseXor: 3^2' => ['sExpression' => '3^2'], + 'BooleanAnd: true && false' => ['sExpression' => 'true && false'], + 'Cast: (array)3' => ['sExpression' => '(array)3'], + 'Cast: (bool)1' => ['sExpression' => '(bool)1'], + 'Cast: (bool)0' => ['sExpression' => '(bool)0'], + 'Cast: (double)3' => ['sExpression' => '(double)3'], + 'Cast: (float)3' => ['sExpression' => '(float)3'], + 'Cast: (int)3' => ['sExpression' => '(int)3'], + 'Cast: (object)3' => ['sExpression' => '(object)3'], + 'Cast: (string) $oEvaluationFakeClass' => ['sExpression' => '(string) $oEvaluationFakeClass', "toString"], + 'ClassConstFetch: public existing constant' => [ 'sExpression' => 'SetupUtils::PHP_MIN_VERSION'], + 'ClassConstFetch: unknown class:class' => [ 'sExpression' => 'GabuZomeuUnknownClass::class'], + 'Coalesce: $oNullVar ?? 1' => ['sExpression' => '$oNullVar ?? 1', 1], + 'Coalesce: $oNonNullVar ?? 1' => ['sExpression' => '$oNonNullVar ?? 1', 1], + 'Coalesce: $_SERVER["toto"] ?? 1' => ['sExpression' => '$_SERVER["toto"] ?? 1', "titi"], + 'Coalesce: $_SERVER["unknown_key"] ?? 1' => ['sExpression' => '$_SERVER["unknown_key"] ?? 1', 1], + 'Coalesce: $oGlobalNonNullVar ?? 1' => ['sExpression' => '$oGlobalNonNullVar ?? 1', "a"], + 'Coalesce: $oGlobalNullVar ?? 1' => ['sExpression' => '$oGlobalNullVar ?? 1', 1], + 'Concat: "a"."b"' => ['sExpression' => '"a"."b"'], + 'ConstFetch: false' => [ 'sExpression' => 'false'], + 'ConstFetch: (false)' => [ 'sExpression' => 'false'], + 'ConstFetch: true' => [ 'sExpression' => 'true'], + 'ConstFetch: (true)' => [ 'sExpression' => 'true'], + 'Equal: 1 == true' => [ 'sExpression' => '1 == true', true], + 'Equal: 1 == false' => [ 'sExpression' => '1 == false', false], + 'FuncCall: function_exists(\'ldap_connect\')' => [ 'sExpression' => 'function_exists(\'ldap_connect\')'], + 'FuncCall: function_exists(\'gabuzomeushouldnotexist\')' => [ 'sExpression' => 'function_exists(\'gabuzomeushouldnotexist\')'], + 'Identical: 1==="1"' => ['sExpression' => '1==="1"', false], + 'Identical: "1"==="1"' => ['sExpression' => '"1"==="1"', true], + 'Isset: isset($oNonNullVar)' => ['sExpression' => 'isset($oNonNullVar)', false], + 'Isset: isset($oGlobalNonNullVar)' => ['sExpression' => 'isset($oGlobalNonNullVar)', true], + 'Isset: isset($a, $_SERVER)' => ['sExpression' => 'isset($a, $_SERVER)', false], + 'Isset: isset($_SERVER)' => ['sExpression' => 'isset($_SERVER)', true], + 'Isset: isset($_SERVER, $a)' => ['sExpression' => 'isset($_SERVER, $a)', false], + 'Isset: isset($oGlobalNonNullVar, $_SERVER)' => ['sExpression' => 'isset($oGlobalNonNullVar, $_SERVER)', true], + 'MethodCall: $oEvaluationFakeClass->GetName()' => ['sExpression' => '$oEvaluationFakeClass->GetName()', "gabuzomeu"], + 'MethodCall: $oEvaluationFakeClass->GetLongName("aa")' => ['sExpression' => '$oEvaluationFakeClass->GetLongName("aa")', "gabuzomeu_aa"], + 'Mod: 3%2' => ['sExpression' => '3%2'], + 'NullsafeMethodCall: $oNullVar?->GetName()' => ['sExpression' => '$oNullVar?->GetName()', null], + 'NullsafeMethodCall: $oNullVar?->GetLongName("aa")' => ['sExpression' => '$oNullVar?->GetLongName("aa")', null], + 'NullsafeMethodCall: $oEvaluationFakeClass?->GetName()' => ['sExpression' => '$oEvaluationFakeClass?->GetName()', "gabuzomeu"], + 'NullsafeMethodCall: $oEvaluationFakeClass?->GetLongName("aa")' => ['sExpression' => '$oEvaluationFakeClass?->GetLongName("aa")', "gabuzomeu_aa"], + 'NullsafePropertyFetch: $oNullVar?->b' => ['sExpression' => '$oNullVar?->b', null], + 'NullsafePropertyFetch: $oEvaluationFakeClass?->iIsOk' => ['sExpression' => '$oEvaluationFakeClass?->iIsOk', "IsOkValue"], + 'PropertyFetch: $oEvaluationFakeClass->iIsOk' => ['sExpression' => '$oEvaluationFakeClass->iIsOk', "IsOkValue"], + 'StaticCall utils::GetItopVersionWikiSyntax()' => ['sExpression' => 'utils::GetItopVersionWikiSyntax()'], + 'StaticProperty: public existing constant' => [ 'sExpression' => 'Combodo\iTop\Test\UnitTest\Sources\PhpParser\Evaluation\PhpExpressionEvaluatorTest::$STATIC_PROPERTY'], + + 'Ternary: (true) ? 1 : 2' => ['sExpression' => '(true) ? 1 : 2'], + 'Ternary: (false) ? 1 : 2' => ['sExpression' => '(false) ? 1 : 2'], + 'UnaryMinus: -1' => ['sExpression' => '-1'], + 'UnaryPlus: +1' => ['sExpression' => '+1'], + 'Variable: $_SERVER' => ['sExpression' => '$_SERVER', ['toto' => 'titi']], + 'Variable: $oGlobalNonNullVar' => ['sExpression' => '$oGlobalNonNullVar', "a"], + 'Variable: $oEvaluationFakeClass' => ['sExpression' => '$oEvaluationFakeClass', new EvaluationFakeClass()], + ]; + } + + /** + * @dataProvider EvaluateExpressionProvider + */ + public function testEvaluateExpression($sExpression, $forced_expected="NOTPROVIDED") + { + global $oGlobalNonNullVar; + $oGlobalNonNullVar="a"; + + global $oGlobalNullVar; + $oGlobalNullVar=null; + + $oNonNullVar="a"; + + $oNullVar=null; + $_SERVER=[ + 'toto' => 'titi', + ]; + + global $oEvaluationFakeClass; + $oEvaluationFakeClass = new EvaluationFakeClass(); + + $oPhpExpressionEvaluator = new PhpExpressionEvaluator(ModuleFileReader::FUNC_CALL_WHITELIST, ModuleFileReader::STATIC_CALLWHITELIST); + $res = $oPhpExpressionEvaluator->ParseAndEvaluateExpression($sExpression); + if ($forced_expected === "NOTPROVIDED"){ + $this->assertEquals($this->UnprotectedComputeExpression($sExpression), $res, $sExpression); + } else { + $this->assertEquals($forced_expected, $res, $sExpression); + } + } + + public static function EvaluateExpressionThrowsExceptionProvider() + { + return [ + 'StaticProperty: private existing constant' => [ + 'sExpression' => 'Combodo\iTop\Test\UnitTest\Setup\ModuleDiscovery\PhpExpressionEvaluatorTest::$PRIVATE_STATIC_PROPERTY', + 'forced_expected' => null, + ], + 'ClassConstFetch: unknown constant' => [ 'sExpression' => 'SetupUtils::UNKNOWN_CONSTANT'], + 'ClassConstFetch: unknown class:constant' => [ 'sExpression' => 'GabuZomeuUnknownClass::UNKNOWN_CONSTANT'], + 'ClassConstFetch: private existing constant' => [ + 'sExpression' => 'Combodo\iTop\Test\UnitTest\Setup\ModuleDiscovery\PhpExpressionEvaluatorTest::PRIVATE_CONSTANT', + 'forced_expected' => null, + ], + 'Variable: $oNonNullVar' => ['sExpression' => '$oNonNullVar', null], + 'FuncCall: function_exists(\'ldap_connect\')' => [ 'sExpression' => 'function_exists(\'ldap_connect\')'], + 'StaticCall utils::GetItopVersionWikiSyntax()' => ['sExpression' => 'utils::GetItopVersionWikiSyntax()'], + ]; + } + /** + * @dataProvider EvaluateExpressionThrowsExceptionProvider + */ + public function testEvaluateExpressionThrowsException($sExpression) + { + global $oGlobalNonNullVar; + $oGlobalNonNullVar="a"; + + global $oGlobalNullVar; + $oGlobalNullVar=null; + + $oNonNullVar="a"; + + $oNullVar=null; + $_SERVER=[ + 'toto' => 'titi', + ]; + + global $oEvaluationFakeClass; + $oEvaluationFakeClass = new EvaluationFakeClass(); + + $this->expectException(\ModuleFileReaderException::class); + $oPhpExpressionEvaluator = new PhpExpressionEvaluator(); + $oPhpExpressionEvaluator->ParseAndEvaluateExpression($sExpression); + } + + + /** + * @param string $sBooleanExpr + * + * @return mixed + * @throws \ModuleFileReaderException + */ + private function UnprotectedComputeExpression(string $sExpr) : mixed + { + try { + $bResult = null; + @eval('$bResult = '.$sExpr.';'); + + return $bResult; + } catch (\Throwable $t){ + return null; + } + } + + public static function ParseAndEvaluateBooleanExpression_AutoselectProvider() + { + $sSimpleCallToModuleIsSelected = "SetupInfo::ModuleIsSelected(\"itop-storage-mgmt\")"; + $sSimpleCallToModuleIsSelected2 = "SetupInfo::ModuleIsSelected(\"itop-storage-mgmt-notselected\")"; + $sCallToModuleIsSelectedCombinedWithAndOperator = "SetupInfo::ModuleIsSelected(\"itop-storage-mgmt\") || SetupInfo::ModuleIsSelected(\"itop-virtualization-mgmt\")"; + $sCallToModuleIsSelectedCombinedWithAndOperator2 = "SetupInfo::ModuleIsSelected(\"itop-storage-mgmt-notselected\") || SetupInfo::ModuleIsSelected(\"itop-virtualization-mgmt\")"; + + return [ + "simple call to SetupInfo::ModuleIsSelected SELECTED" => [ + "expr" => $sSimpleCallToModuleIsSelected, + "expected" => true, + ], + "simple call to SetupInfo::ModuleIsSelected NOT SELECTED" => [ + "expr" => $sSimpleCallToModuleIsSelected2, + "expected" => false, + ], + "call to SetupInfo::ModuleIsSelected + OR => SELECTED" => [ + "expr" => $sCallToModuleIsSelectedCombinedWithAndOperator, + "expected" => true, + ], + "simple call to SetupInfo::ModuleIsSelected + OR => NOT SELECTED" => [ + "expr" => $sCallToModuleIsSelectedCombinedWithAndOperator2, + "expected" => false, + ], + ]; + } + + /** + * @dataProvider ParseAndEvaluateBooleanExpression_AutoselectProvider + */ + public function testEvaluateBooleanExpression_Autoselect(string $sBooleanExpression, bool $expected){ + \SetupInfo::SetSelectedModules(["itop-storage-mgmt" => "123"]); + $oPhpExpressionEvaluator = new PhpExpressionEvaluator([], ["SetupInfo::ModuleIsSelected"]); + $this->assertEquals($expected, $oPhpExpressionEvaluator->ParseAndEvaluateBooleanExpression($sBooleanExpression), $sBooleanExpression); + } +} + +class EvaluationFakeClass { + public string $iIsOk="IsOkValue"; + + public function GetName() + { + return "gabuzomeu"; + } + + public function GetLongName($suffix) + { + return "gabuzomeu_" . $suffix; + } + + public function __toString(): string + { + return "toString"; + } +} \ No newline at end of file