diff --git a/.gitignore b/.gitignore index bc4aef17f..3f995a7f3 100644 --- a/.gitignore +++ b/.gitignore @@ -139,4 +139,3 @@ local.properties .cache-main .scala_dependencies .worksheet - diff --git a/.jenkins/bin/archive/gather_external_files.sh b/.jenkins/bin/archive/gather_external_files.sh deleted file mode 100755 index e69de29bb..000000000 diff --git a/.jenkins/bin/init/append_files.sh b/.jenkins/bin/init/append_files.sh deleted file mode 100755 index 56226a792..000000000 --- a/.jenkins/bin/init/append_files.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -set -x - -# create target dirs -mkdir -p var -mkdir -p toolkit - -# cleanup target dirs -rm -rf toolkit/* - -# fill target dirs -curl https://www.combodo.com/documentation/iTopDataModelToolkit-2.3.zip > toolkit.zip -unzip toolkit.zip -rm toolkit.zip -cp -r .jenkins/configuration/default-environment/unattended_install/* toolkit diff --git a/.jenkins/bin/init/composer_install.sh b/.jenkins/bin/init/composer_install.sh deleted file mode 100755 index 8d74fe861..000000000 --- a/.jenkins/bin/init/composer_install.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -x - -# on the root dir -# composer install -a # => Not needed anymore (libs were added to git with N°2435) - - -# under the test dir -cd test -composer install diff --git a/.jenkins/bin/init/debug.sh b/.jenkins/bin/init/debug.sh deleted file mode 100755 index 583a0aed6..000000000 --- a/.jenkins/bin/init/debug.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -set -x - - - -whoami -pwd -ls - -echo "$BRANCH_NAME:${BRANCH_NAME}" - -echo "printenv :" -printenv - diff --git a/.jenkins/bin/tests/phpunit.sh b/.jenkins/bin/tests/phpunit.sh deleted file mode 100755 index 17fa52827..000000000 --- a/.jenkins/bin/tests/phpunit.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash -set -x - -cd test - -export DEBUG_UNIT_TEST=0 -RUN_NONREG_TESTS=0 - -#USAGE ${debugMode} ${runNonRegOQLTests} "${coverture}" "${testFile}" - -if [ $# -ge 1 -a "x$1" == "xtrue" ] -then - export DEBUG_UNIT_TEST=1 -else - export DEBUG_UNIT_TEST=0 -fi - -set -x -OPTION="" -if [ $# -ge 3 -a "x$3" == "xtrue" ] -then - ##coverture - OPTION="-dxdebug.coverage_enable=1 --coverage-clover ../var/test/coverage.xml" -fi - -TESTFILE="$4" -if [ "x$TESTFILE" != "x" ] -then - # shellcheck disable=SC2001 - TESTFILE=$(echo "$TESTFILE" | sed 's|test/||1') - php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml $OPTION $TESTFILE --teamcity - exit 0 -fi - -if [ $# -ge 2 -a "x$2" == "xtrue" ] -then - php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml $OPTION --teamcity -else - #echo php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml --teamcity - php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml $OPTION --exclude-group OQL --teamcity -fi diff --git a/.jenkins/bin/unattended_install/default_env.sh b/.jenkins/bin/unattended_install/default_env.sh deleted file mode 100755 index e02b87fd6..000000000 --- a/.jenkins/bin/unattended_install/default_env.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -x - -chmod 666 conf/production/config-itop.php - -cd toolkit -php unattended_install.php --response_file=default-params.xml --clean=true diff --git a/.jenkins/configuration/default-environment/unattended_install/default-config-itop.php b/.jenkins/configuration/default-environment/unattended_install/default-config-itop.php deleted file mode 100644 index 7ff9d1494..000000000 --- a/.jenkins/configuration/default-environment/unattended_install/default-config-itop.php +++ /dev/null @@ -1,284 +0,0 @@ - 'iTop is temporarily frozen, please wait... (the admin team)', - - // access_mode: Access mode: ACCESS_READONLY = 0, ACCESS_ADMIN_WRITE = 2, ACCESS_FULL = 3 - // default: 3 - 'access_mode' => 3, - - 'allowed_login_types' => 'form|basic|external', - - // apc_cache.enabled: If set, the APC cache is allowed (the PHP extension must also be active) - // default: true - 'apc_cache.enabled' => true, - - // apc_cache.query_ttl: Time to live set in APC for the prepared queries (seconds - 0 means no timeout) - // default: 3600 - 'apc_cache.query_ttl' => 3600, - - // app_root_url: Root URL used for navigating within the application, or from an email to the application (you can put $SERVER_NAME$ as a placeholder for the server's name) - // default: '' - 'app_root_url' => 'http://127.0.0.1/itop/svn/trunk/', - - // buttons_position: Position of the forms buttons: bottom | top | both - // default: 'both' - 'buttons_position' => 'both', - - // cas_include_path: The path where to find the phpCAS library - // default: '/usr/share/php' - 'cas_include_path' => '/usr/share/php', - - // cron_max_execution_time: Duration (seconds) of the page cron.php, must be shorter than php setting max_execution_time and shorter than the web server response timeout - // default: 600 - 'cron_max_execution_time' => 600, - - // csv_file_default_charset: Character set used by default for downloading and uploading data as a CSV file. Warning: it is case sensitive (uppercase is preferable). - // default: 'ISO-8859-1' - 'csv_file_default_charset' => 'ISO-8859-1', - - 'csv_import_charsets' => array ( - ), - - // csv_import_history_display: Display the history tab in the import wizard - // default: false - 'csv_import_history_display' => false, - - // date_and_time_format: Format for date and time display (per language) - // default: array ( - // 'default' => - // array ( - // 'date' => 'Y-m-d', - // 'time' => 'H:i:s', - // 'date_time' => '$date $time', - // ), - // ) - 'date_and_time_format' => array ( - 'default' => - array ( - 'date' => 'Y-m-d', - 'time' => 'H:i:s', - 'date_time' => '$date $time', - ), - 'FR FR' => - array ( - 'date' => 'd/m/Y', - 'time' => 'H:i:s', - 'date_time' => '$date $time', - ), - ), - - 'db_host' => '', - - 'db_name' => 'itop_ci', - - 'db_pwd' => 'IKnowYouSeeMeInJenkinsConf', - - 'db_subname' => '', - - 'db_user' => 'jenkins_itop', - - // deadline_format: The format used for displaying "deadline" attributes: any string with the following placeholders: $date$, $difference$ - // default: '$difference$' - 'deadline_format' => '$difference$', - - 'default_language' => 'EN US', - - // draft_attachments_lifetime: Lifetime (in seconds) of drafts' attachments and inline images: after this duration, the garbage collector will delete them. - // default: 3600 - 'draft_attachments_lifetime' => 3600, - - // email_asynchronous: If set, the emails are sent off line, which requires cron.php to be activated. Exception: some features like the email test utility will force the serialized mode - // default: false - 'email_asynchronous' => false, - - // email_default_sender_address: Default address provided in the email from header field. - // default: '' - 'email_default_sender_address' => '', - - // email_default_sender_label: Default label provided in the email from header field. - // default: '' - 'email_default_sender_label' => '', - - // email_transport: Mean to send emails: PHPMail (uses the function mail()) or SMTP (implements the client protocole) - // default: 'PHPMail' - 'email_transport' => 'SMTP', - - // email_transport_smtp.host: host name or IP address (optional) - // default: 'localhost' - 'email_transport_smtp.host' => 'smtp.combodo.com', - - // email_transport_smtp.password: Authentication password (optional) - // default: '' - 'email_transport_smtp.password' => 'IDoNotWork', - - // email_transport_smtp.port: port number (optional) - // default: 25 - 'email_transport_smtp.port' => 25, - - // email_transport_smtp.username: Authentication user (optional) - // default: '' - 'email_transport_smtp.username' => 'test2@combodo.com', - - // email_validation_pattern: Regular expression to validate/detect the format of an eMail address - // default: '[a-zA-Z0-9._&\'-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z0-9-]{2,}' - 'email_validation_pattern' => '[a-zA-Z0-9._&\'-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z0-9-]{2,}', - - 'encryption_key' => '@iT0pEncr1pti0n!', - - 'ext_auth_variable' => '$_SERVER[\'REMOTE_USER\']', - - 'fast_reload_interval' => '60', - - // graphviz_path: Path to the Graphviz "dot" executable for graphing objects lifecycle - // default: '/usr/bin/dot' - 'graphviz_path' => '/usr/bin/dot', - - // inline_image_max_display_width: The maximum width (in pixels) when displaying images inside an HTML formatted attribute. Images will be displayed using this this maximum width. - // default: '250' - 'inline_image_max_display_width' => 250, - - // inline_image_max_storage_width: The maximum width (in pixels) when uploading images to be used inside an HTML formatted attribute. Images larger than the given size will be downsampled before storing them in the database. - // default: '1600' - 'inline_image_max_storage_width' => 1600, - - // link_set_attribute_qualifier: Link set from string: attribute qualifier (encloses both the attcode and the value) - // default: '\'' - 'link_set_attribute_qualifier' => '\'', - - // link_set_attribute_separator: Link set from string: attribute separator - // default: ';' - 'link_set_attribute_separator' => ';', - - // link_set_item_separator: Link set from string: line separator - // default: '|' - 'link_set_item_separator' => '|', - - // link_set_value_separator: Link set from string: value separator (between the attcode and the value itself - // default: ':' - 'link_set_value_separator' => ':', - - 'log_global' => true, - - 'log_issue' => true, - - 'log_notification' => true, - - 'log_web_service' => true, - - // max_combo_length: The maximum number of elements in a drop-down list. If more then an autocomplete will be used - // default: 50 - 'max_combo_length' => 50, - - 'max_display_limit' => '15', - - // max_linkset_output: Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit. - // default: 100 - 'max_linkset_output' => 100, - - 'min_display_limit' => '10', - - // online_help: Hyperlink to the online-help web page - // default: 'http://www.combodo.com/itop-help' - 'online_help' => 'http://www.combodo.com/itop-help', - - // php_path: Path to the php executable in CLI mode - // default: 'php' - 'php_path' => 'php', - - // portal_tickets: CSV list of classes supported in the portal - // default: 'UserRequest' - 'portal_tickets' => 'UserRequest', - - 'query_cache_enabled' => true, - - // search_manual_submit: Force manual submit of search requests (class => true) - // default: false - 'search_manual_submit' => array ( - 'Person' => true, - ), - - 'secure_connection_required' => false, - - // session_name: The name of the cookie used to store the PHP session id - // default: 'iTop' - 'session_name' => 'iTop', - - // shortcut_actions: Actions that are available as direct buttons next to the "Actions" menu - // default: 'UI:Menu:Modify,UI:Menu:New' - 'shortcut_actions' => 'UI:Menu:Modify,UI:Menu:New', - - // source_dir: Source directory for the datamodel files. (which gets compiled to env-production). - // default: '' - 'source_dir' => 'datamodels/2.x/', - - 'standard_reload_interval' => '300', - - // synchro_trace: Synchronization details: none, display, save (includes 'display') - // default: 'none' - 'synchro_trace' => 'none', - - // timezone: Timezone (reference: http://php.net/manual/en/timezones.php). If empty, it will be left unchanged and MUST be explicitely configured in PHP - // default: 'Europe/Paris' - 'timezone' => 'Europe/Paris', - - // tracking_level_linked_set_default: Default tracking level if not explicitely set at the attribute level, for AttributeLinkedSet (defaults to NONE in case of a fresh install, LIST otherwise - this to preserve backward compatibility while upgrading from a version older than 2.0.3 - see TRAC #936) - // default: 1 - 'tracking_level_linked_set_default' => 0, - - // url_validation_pattern: Regular expression to validate/detect the format of an URL (URL attributes and Wiki formatting for Text attributes) - // default: '(https?|ftp)\\://([a-zA-Z0-9+!*(),;?&=\\$_.-]+(\\:[a-zA-Z0-9+!*(),;?&=\\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\\:[0-9]{2,5})?(/([a-zA-Z0-9%+\\$_-]\\.?)+)*/?(\\?[a-zA-Z+&\\$_.-][a-zA-Z0-9;:[\\]@&%=+/\\$_.-]*)?(#[a-zA-Z_.-][a-zA-Z0-9+\\$_.-]*)?' - 'url_validation_pattern' => '(https?|ftp)\\://([a-zA-Z0-9+!*(),;?&=\\$_.-]+(\\:[a-zA-Z0-9+!*(),;?&=\\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\\:[0-9]{2,5})?(/([a-zA-Z0-9%+\\$_-]\\.?)+)*/?(\\?[a-zA-Z+&\\$_.-][a-zA-Z0-9;:[\\]@&%=+/\\$_.-]*)?(#[a-zA-Z_.-][a-zA-Z0-9+\\$_.-]*)?', -); - -/** - * - * Modules specific settings - * - */ -$MyModuleSettings = array( - 'authent-local' => array ( - 'password_validation.pattern' => '', - ), - 'itop-attachments' => array ( - 'allowed_classes' => array ( - 0 => 'Ticket', - ), - 'position' => 'relations', - 'preview_max_width' => 290, - ), - 'itop-backup' => array ( - 'mysql_bindir' => '', - 'week_days' => 'monday, tuesday, wednesday, thursday, friday', - 'time' => '23:30', - 'retention_count' => 5, - 'enabled' => true, - 'debug' => false, - ), - 'molkobain-console-tooltips' => array ( - 'decoration_class' => 'fas fa-question', - 'enabled' => true, - ), -); - -/** - * - * Data model modules to be loaded. Names are specified as relative paths - * - */ -$MyModules = array( - 'addons' => array ( - 'user rights' => 'addons/userrights/userrightsprofile.class.inc.php', - ), -); -?> diff --git a/.jenkins/configuration/default-environment/unattended_install/unattended_install.php b/.jenkins/configuration/default-environment/unattended_install/unattended_install.php deleted file mode 100644 index 804d206db..000000000 --- a/.jenkins/configuration/default-environment/unattended_install/unattended_install.php +++ /dev/null @@ -1,208 +0,0 @@ -Get('mode'); - -if ($sMode == 'install') -{ - echo "Installation mode detected.\n"; - $bClean = utils::ReadParam('clean', false, true /* CLI allowed */); - if ($bClean) - { - echo "Cleanup mode detected.\n"; - $sTargetEnvironment = $oParams->Get('target_env', ''); - if ($sTargetEnvironment == '') - { - $sTargetEnvironment = 'production'; - } - $sTargetDir = APPROOT.'env-'.$sTargetEnvironment; - - // Configuration file - $sConfigFile = APPCONF.$sTargetEnvironment.'/'.ITOP_CONFIG_FILE; - if (file_exists($sConfigFile)) - { - echo "Trying to delete the configuration file: '$sConfigFile'.\n"; - @chmod($sConfigFile, 0770); // RWX for owner and group, nothing for others - unlink($sConfigFile); - } - else - { - echo "No config file to delete ($sConfigFile does not exist).\n"; - } - - // env-xxx directory - if (file_exists($sTargetDir)) - { - if (is_dir($sTargetDir)) - { - echo "Emptying the target directory '$sTargetDir'.\n"; - SetupUtils::tidydir($sTargetDir); - } - else - { - die("ERROR the target dir '$sTargetDir' exists, but is NOT a directory !!!\nExiting.\n"); - } - } - else - { - echo "No target directory to delete ($sTargetDir does not exist).\n"; - } - - // Database - $aDBSettings = $oParams->Get('database', array()); - $sDBServer = $aDBSettings['server']; - $sDBUser = $aDBSettings['user']; - $sDBPwd = $aDBSettings['pwd']; - $sDBName = $aDBSettings['name']; - $sDBPrefix = $aDBSettings['prefix']; - - if ($sDBPrefix != '') - { - die("Cleanup not implemented for a partial database (prefix= '$sDBPrefix')\nExiting."); - } - - $oMysqli = new mysqli($sDBServer, $sDBUser, $sDBPwd); - if ($oMysqli->connect_errno) - { - die("Cannot connect to the MySQL server (".$oMysqli->connect_errno . ") ".$oMysqli->connect_error."\nExiting"); - } - else - { - if ($oMysqli->select_db($sDBName)) - { - echo "Deleting database '$sDBName'\n"; - $oMysqli->query("DROP DATABASE `$sDBName`"); - } - else - { - echo "The database '$sDBName' does not seem to exist. Nothing to cleanup.\n"; - } - } - } -} - -$bHasErrors = false; -$aChecks = SetupUtils::CheckBackupPrerequisites(APPROOT.'data'); // mmm should be the backup destination dir - -$aSelectedModules = $oParams->Get('selected_modules'); -$sSourceDir = $oParams->Get('source_dir', 'datamodels/latest'); -$sExtensionDir = $oParams->Get('extensions_dir', 'extensions'); -$aChecks = array_merge($aChecks, SetupUtils::CheckSelectedModules($sSourceDir, $sExtensionDir, $aSelectedModules)); - - -foreach($aChecks as $oCheckResult) -{ - switch($oCheckResult->iSeverity) - { - case CheckResult::ERROR: - $bHasErrors = true; - $sHeader = "Error"; - break; - - case CheckResult::WARNING: - $sHeader = "Warning"; - break; - - case CheckResult::INFO: - default: - $sHeader = "Info"; - break; - } - echo $sHeader.": ".$oCheckResult->sLabel; - if (strlen($oCheckResult->sDescription)) - { - echo ' - '.$oCheckResult->sDescription; - } - echo "\n"; -} - -if ($bHasErrors) -{ - echo "Encountered stopper issues. Aborting...\n"; - die; -} - -$bFoundIssues = false; - -$bInstall = utils::ReadParam('install', true, true /* CLI allowed */); -if ($bInstall) -{ - echo "Starting the unattended installation...\n"; - $oWizard = new ApplicationInstaller($oParams); - $bRes = $oWizard->ExecuteAllSteps(); - if (!$bRes) - { - echo "\nencountered installation issues!"; - $bFoundIssues = true; - } -} -else -{ - echo "No installation requested.\n"; -} -if (!$bFoundIssues && $bCheckConsistency) -{ - echo "Checking data model consistency.\n"; - ob_start(); - $sCheckRes = ''; - try - { - MetaModel::CheckDefinitions(false); - $sCheckRes = ob_get_clean(); - } - catch(Exception $e) - { - $sCheckRes = ob_get_clean()."\nException: ".$e->getMessage(); - } - if (strlen($sCheckRes) > 0) - { - echo $sCheckRes; - echo "\nfound consistency issues!"; - $bFoundIssues = true; - } -} - -if (!$bFoundIssues) -{ - // last line: used to check the install - // the only way to track issues in case of Fatal error or even parsing error! - echo "\ninstalled!"; - exit; -} diff --git a/.make/build/afterBuild.php b/.make/build/afterBuild.php new file mode 100644 index 000000000..cecdaf9e9 --- /dev/null +++ b/.make/build/afterBuild.php @@ -0,0 +1,90 @@ + array("pipe", "r"), // stdin + 1 => array("pipe", "w"), // stdout + 2 => array("pipe", "w"), // stderr + ); + $process = proc_open($cmd, $descriptorspec, $pipes, __DIR__ . '/..', null); + + $stdout = stream_get_contents($pipes[1]); + fclose($pipes[1]); + + $stderr = stream_get_contents($pipes[2]); + fclose($pipes[2]); + + $iCode = proc_close($process); + $bSuccess = (0 === $iCode); + + $iElapsed = time() - $iBeginTime; + if (!$bSuccess) { + fwrite(STDERR, sprintf( + "\nCOMMAND FAILED! (%s) \n - status:%s \n - stderr:%s \n - stdout: %s\n - elapsed:%ss\n\n", + $cmd, + $iCode, + rtrim($stderr), + rtrim($stdout), + $iElapsed + )); + } + else + { + echo "| elapsed:${iElapsed}s \n"; + } + + if (!empty($stderr)) + { + fwrite(STDERR, "$stderr\n"); + } + if (!empty($stdout)) + { + echo "stdout :$stdout\n\n"; + } + + return $bSuccess; +} diff --git a/.make/build/commands/setupCssCompiler.php b/.make/build/commands/setupCssCompiler.php new file mode 100644 index 000000000..b8178c95e --- /dev/null +++ b/.make/build/commands/setupCssCompiler.php @@ -0,0 +1,51 @@ + + * + */ + +use Combodo\iTop\Composer\iTopComposer; + +$iTopFolder = __DIR__."/../../../"; + +require_once("$iTopFolder/approot.inc.php"); +require_once(APPROOT."/application/utils.inc.php"); + +if (php_sapi_name() !== 'cli') +{ + throw new \Exception('This script can only run from CLI'); +} + +$sCssFile = APPROOT.'/css/setup.css'; +if (file_exists($sCssFile)) +{ + fwrite(STDERR, "$sCssFile already exists (it should not), removing it."); + if (!unlink($sCssFile)) + { + fwrite(STDERR, "Failed to remove $sCssFile, exiting."); + exit(1); + } +} +$sCssRelPath = utils::GetCSSFromSASS('css/setup.scss'); + +if (!file_exists($sCssFile)) +{ + fwrite(STDERR, "Failed to compile $sCssFile, exiting."); + exit(1); +} \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index 32c49f9b7..56f298717 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,73 +1,11 @@ -pipeline { - agent any - parameters { - booleanParam(name: 'debugMode', defaultValue: 'false', description: 'Debug mode?') - string(name: 'testFile', defaultValue: '', description: 'Provide test file to execute. Example: test/core/LogAPITest.php') - booleanParam(name: 'coverture', defaultValue: 'false', description: 'Test coverture?') - booleanParam(name: 'runNonRegOQLTests', defaultValue: 'false', description: 'Do You want to run legacy OQL regression tests?') - } - stages { +def infra - stage('init') { - parallel { - stage('debug') { - steps { - sh './.jenkins/bin/init/debug.sh' - } - } - stage('append files to project') { - steps { - sh './.jenkins/bin/init/append_files.sh' - } - } - stage('composer install') { - steps { - sh './.jenkins/bin/init/composer_install.sh' - } - } - } - } +node(){ + checkout scm - stage('unattended_install') { - parallel { - stage('unattended_install default env') { - steps { - sh './.jenkins/bin/unattended_install/default_env.sh' - } - } - } - } - - stage('test') { - parallel { - stage('phpunit') { - steps { - sh './.jenkins/bin/tests/phpunit.sh ${debugMode} ${runNonRegOQLTests} "${coverture}" "${testFile}"' - } - } - } - } - - } - - post { - always { - archiveArtifacts allowEmptyArchive:true, excludes: '.gitkeep', artifacts: 'var/test/*.xml' - junit 'var/test/phpunit-log.junit.xml' - } - failure { - slackSend(channel: "#jenkins-itop", color: '#FF0000', message: "Ho no! Build failed! (${currentBuild.result}), Job '${env.JOB_NAME_UNESCAPED} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})") - } - fixed { - slackSend(channel: "#jenkins-itop", color: '#FFa500', message: "Yes! Build repaired! (${currentBuild.result}), Job '${env.JOB_NAME_UNESCAPED} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})") - } - } - - environment { - DEBUG_UNIT_TEST = '0' - JOB_NAME_UNESCAPED = env.JOB_NAME.replaceAll("%2F", "/") - } - options { - timeout(time: 20, unit: 'MINUTES') - } + infra = load '/var/lib/jenkins/workspace/itop-test-infra_master/src/Infra.groovy' } + + +infra.call() + diff --git a/application/startup.inc.php b/application/startup.inc.php index 4082d44b7..15514807a 100644 --- a/application/startup.inc.php +++ b/application/startup.inc.php @@ -35,7 +35,17 @@ register_shutdown_function(function() $sReservedMemory = null; if (!is_null($err = error_get_last()) && ($err['type'] == E_ERROR)) { - IssueLog::error($err['message']); + // Remove stack trace from MySQLException + $sMessage = $err['message']; + if (strpos($sMessage, 'MySQLException') !== false) + { + $iStackTracePos = strpos($sMessage, 'Stack trace:'); + if ($iStackTracePos !== false) + { + $sMessage = substr($sMessage, 0, $iStackTracePos); + } + } + IssueLog::error($sMessage); if (strpos($err['message'], 'Allowed memory size of') !== false) { $sLimit = ini_get('memory_limit'); diff --git a/application/utils.inc.php b/application/utils.inc.php index 28e1c5a9b..60dae40d0 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -2090,6 +2090,41 @@ class utils return COMPILATION_TIMESTAMP; } + /** + * @return string eg : '2_7_0' ITOP_VERSION is '2.7.1-dev' + */ + public static function GetItopVersionWikiSyntax() + { + $sMinorVersion = self::GetItopMinorVersion(); + return str_replace('.', '_', $sMinorVersion).'_0'; + } + + /** + * @return string eg 2.7 if ITOP_VERSION is '2.7.0-dev' + * @throws \Exception + */ + public static function GetItopMinorVersion() + { + $sPatchVersion = self::GetItopPatchVersion(); + $aExplodedVersion = explode('.', $sPatchVersion); + + if (empty($aExplodedVersion[0]) || empty($aExplodedVersion[1])) + { + throw new Exception('iTop version is wrongfully configured!'); + } + + return sprintf('%d.%d', $aExplodedVersion[0], $aExplodedVersion[1]); + } + + /** + * @return string eg '2.7.0' if ITOP_VERSION is '2.7.0-dev' + */ + public static function GetItopPatchVersion() + { + $aExplodedVersion = explode('-', ITOP_VERSION); + return $aExplodedVersion[0]; + } + /** * Check if the given class if configured as a high cardinality class. * diff --git a/core/bulkexport.class.inc.php b/core/bulkexport.class.inc.php index bc6f9031e..dec6973e9 100644 --- a/core/bulkexport.class.inc.php +++ b/core/bulkexport.class.inc.php @@ -345,10 +345,10 @@ abstract class BulkExport $this->oBulkExportResult->Set('format', $this->sFormatCode); $this->oBulkExportResult->Set('search', $this->oSearch->serialize()); $this->oBulkExportResult->Set('chunk_size', $this->iChunkSize); - $this->oBulkExportResult->Set('temp_file_path', $this->sTmpFile); $this->oBulkExportResult->Set('localize_output', $this->bLocalizeOutput); } $this->oBulkExportResult->Set('status_info', json_encode($this->GetStatusInfo())); + $this->oBulkExportResult->Set('temp_file_path', $this->sTmpFile); utils::PushArchiveMode(false); $ret = $this->oBulkExportResult->DBWrite(); utils::PopArchiveMode(); @@ -420,6 +420,11 @@ abstract class BulkExport public function GetStatistics() { + } + + public function SetFields($sFields) + { + } public function GetDownloadFileName() diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index dbdab2d58..bee7d7a41 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -95,11 +95,17 @@ abstract class CMDBObject extends DBObject protected static $m_oCurrChange = null; protected static $m_sInfo = null; // null => the information is built in a standard way protected static $m_sOrigin = null; // null => the origin is 'interactive' - + /** - * Specify another change (this is mainly for backward compatibility) + * Specify the change to be used by the API to attach any CMDBChangeOp* object created + * + * @see SetTrackInfo if CurrentChange is null, then a new one will be create using trackinfo + * + * @param CMDBChange|null $oChange use null so that the API will recreate a new CMDBChange using TrackInfo & TrackOrigin + * + * @since 2.7.2 N°3219 can now reset CMDBChange by passing null */ - public static function SetCurrentChange(CMDBChange $oChange) + public static function SetCurrentChange($oChange) { self::$m_oCurrChange = $oChange; } @@ -126,11 +132,15 @@ abstract class CMDBObject extends DBObject /** * Override the additional information (defaulting to user name) * A call to this verb should replace every occurence of - * $oMyChange = MetaModel::NewObject("CMDBChange"); + * $oMyChange = MetaModel::NewObject("CMDBChange"); * $oMyChange->Set("date", time()); * $oMyChange->Set("userinfo", 'this is done by ... for ...'); * $iChangeId = $oMyChange->DBInsert(); - */ + * + * @see SetCurrentChange to specify a CMDBObject instance instead + * + * @param string $sInfo + */ public static function SetTrackInfo($sInfo) { self::$m_sInfo = $sInfo; @@ -138,8 +148,13 @@ abstract class CMDBObject extends DBObject /** * Provides information about the origin of the change - * @param $sOrigin String: one of: interactive, csv-interactive, csv-import.php, webservice-soap, webservice-rest, syncho-data-source, email-processing, custom-extension - */ + * + * @see SetTrackInfo + * @see SetCurrentChange to specify a CMDBObject instance instead + * + * @param $sOrigin String: one of: interactive, csv-interactive, csv-import.php, webservice-soap, webservice-rest, syncho-data-source, + * email-processing, custom-extension + */ public static function SetTrackOrigin($sOrigin) { self::$m_sOrigin = $sOrigin; diff --git a/core/config.class.inc.php b/core/config.class.inc.php index c007c5548..bcbf9e22c 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -22,7 +22,7 @@ define('ITOP_APPLICATION', 'iTop'); define('ITOP_APPLICATION_SHORT', 'iTop'); -define('ITOP_VERSION', '2.8.0-dev'); +define('ITOP_VERSION', '2.8.0-dev'); // @see utils::GetItopVersionShort() and utils::GetItopVersionWikiSyntax() define('ITOP_REVISION', 'svn'); define('ITOP_BUILD_DATE', '$WCNOW$'); define('ITOP_VERSION_FULL', ITOP_VERSION.'-'.ITOP_REVISION); diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index ed54e4f5a..ea527ca52 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -792,10 +792,11 @@ class DBObjectSearch extends DBSearch * Helper to * - convert a translation table (format optimized for the translation in an expression tree) into simple hash * - compile over an eventually existing map + * - accept multiple translations for the same alias for unions * * @param array $aRealiasingMap Map to update * @param array $aAliasTranslation Translation table resulting from calls to MergeWith_InNamespace - * @return void of => + * @return void of [old-alias][] => new-alias (@since 2.7.2) */ protected function UpdateRealiasingMap(&$aRealiasingMap, $aAliasTranslation) { @@ -803,17 +804,33 @@ class DBObjectSearch extends DBSearch { foreach ($aAliasTranslation as $sPrevAlias => $aRules) { - if (isset($aRules['*'])) + if (!isset($aRules['*'])) { - $sNewAlias = $aRules['*']; - $sOriginalAlias = array_search($sPrevAlias, $aRealiasingMap); - if ($sOriginalAlias !== false) + continue; + } + + $sNewAlias = $aRules['*']; + $bOriginalFound = false; + $iIndex = 0; + foreach ($aRealiasingMap as $sOriginalAlias => $aAliases) + { + $iIndex = array_search($sPrevAlias, $aAliases); + if ($iIndex !== false) { - $aRealiasingMap[$sOriginalAlias] = $sNewAlias; + $bOriginalFound = true; + break; } - else + + } + if ($bOriginalFound) + { + $aRealiasingMap[$sOriginalAlias][$iIndex] = $sNewAlias; + } + else + { + if (!isset($aRealiasingMap[$sPrevAlias]) || !in_array($sNewAlias, $aRealiasingMap[$sPrevAlias])) { - $aRealiasingMap[$sPrevAlias] = $sNewAlias; + $aRealiasingMap[$sPrevAlias][] = $sNewAlias; } } } @@ -861,7 +878,7 @@ class DBObjectSearch extends DBSearch /** * Add a link to another filter, using an extkey already present in current filter * - * @param DBObjectSearch $oFilter filter to join to + * @param DBObjectSearch $oFilter filter to join to (can be modified) * @param string $sExtKeyAttCode extkey present in current filter, that allows to points to $oFilter * @param int $iOperatorCode * @param array $aRealiasingMap array of => , for each alias that has changed. @@ -955,7 +972,7 @@ class DBObjectSearch extends DBSearch } /** - * @param DBObjectSearch $oFilter + * @param DBObjectSearch $oFilter (can be modified) * @param $sForeignExtKeyAttCode * @param int $iOperatorCode * @param null $aRealiasingMap array of => , for each alias that has changed diff --git a/core/dbsearch.class.php b/core/dbsearch.class.php index c4e9d6a12..d4f5ea4e9 100644 --- a/core/dbsearch.class.php +++ b/core/dbsearch.class.php @@ -1112,6 +1112,8 @@ abstract class DBSearch * @throws \CoreException * @throws \CoreUnexpectedValue * @throws \MySQLException + * + * @since 2.7.0 N°2555 */ public function GetFirstResult($bMustHaveOneResultMax = true, $aOrderBy = array(), $aSearchParams = array()) { diff --git a/core/dbunionsearch.class.php b/core/dbunionsearch.class.php index 3d659bbf8..28347a580 100644 --- a/core/dbunionsearch.class.php +++ b/core/dbunionsearch.class.php @@ -380,15 +380,14 @@ class DBUnionSearch extends DBSearch * @param DBObjectSearch $oFilter * @param $sExtKeyAttCode * @param int $iOperatorCode - * @param null $aRealiasingMap array of => , for each alias that has changed - * @throws CoreException - * @throws CoreWarning + * @param null $aRealiasingMap array of [old-alias][] => , for each alias that has changed (@since 2.7.2) */ public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null) { foreach ($this->aSearches as $oSearch) { - $oSearch->AddCondition_PointingTo($oFilter, $sExtKeyAttCode, $iOperatorCode, $aRealiasingMap); + $oConditionFilter = $oFilter->DeepClone(); + $oSearch->AddCondition_PointingTo($oConditionFilter, $sExtKeyAttCode, $iOperatorCode, $aRealiasingMap); } } @@ -396,13 +395,14 @@ class DBUnionSearch extends DBSearch * @param DBObjectSearch $oFilter * @param $sForeignExtKeyAttCode * @param int $iOperatorCode - * @param null $aRealiasingMap array of => , for each alias that has changed + * @param null $aRealiasingMap array of [old-alias][] => , for each alias that has changed (@since 2.7.2) */ public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null) { foreach ($this->aSearches as $oSearch) { - $oSearch->AddCondition_ReferencedBy($oFilter, $sForeignExtKeyAttCode, $iOperatorCode, $aRealiasingMap); + $oConditionFilter = $oFilter->DeepClone(); + $oSearch->AddCondition_ReferencedBy($oConditionFilter, $sForeignExtKeyAttCode, $iOperatorCode, $aRealiasingMap); } } diff --git a/core/excelbulkexport.class.inc.php b/core/excelbulkexport.class.inc.php index 6717adfbb..1d4569397 100644 --- a/core/excelbulkexport.class.inc.php +++ b/core/excelbulkexport.class.inc.php @@ -353,7 +353,8 @@ EOF $fStartExcel = microtime(true); $writer = new XLSXWriter(); - $oDateTimeFormat = new DateTimeFormat($this->aStatusInfo['date_format']); + $sDateFormat = isset($this->aStatusInfo['date_format']) ? $this->aStatusInfo['date_format'] : (string)AttributeDateTime::GetFormat(); + $oDateTimeFormat = new DateTimeFormat($sDateFormat); $writer->setDateTimeFormat($oDateTimeFormat->ToExcel()); $oDateFormat = new DateTimeFormat($oDateTimeFormat->ToDateFormat()); $writer->setDateFormat($oDateFormat->ToExcel()); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index d58840701..0fd59f580 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -5500,7 +5500,7 @@ abstract class MetaModel { $sIndexName = $sField; $sColumns = '`'.$sField.'`'; - if (!is_null($aLength[0])) + if (isset($aLength[0])) { $sColumns .= ' ('.$aLength[0].')'; } diff --git a/core/oql/expression.class.inc.php b/core/oql/expression.class.inc.php index 30e97553a..d67928a97 100644 --- a/core/oql/expression.class.inc.php +++ b/core/oql/expression.class.inc.php @@ -2235,7 +2235,15 @@ class ListExpression extends Expression { if ($oExpr instanceof VariableExpression) { - $this->m_aExpressions[$idx] = $oExpr->GetAsScalar($aArgs); + $oVarExpr = $oExpr->GetAsScalar($aArgs); + if ($oVarExpr instanceof ListExpression) + { + $this->m_aExpressions = $oVarExpr->GetItems(); + } + else + { + $this->m_aExpressions[$idx] = $oVarExpr; + } } else { diff --git a/core/ormlinkset.class.inc.php b/core/ormlinkset.class.inc.php index 36ca251db..8bc982930 100644 --- a/core/ormlinkset.class.inc.php +++ b/core/ormlinkset.class.inc.php @@ -812,6 +812,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator // the pointed class is always present in the search, as generated by \AttributeLinkedSet::GetDefaultValue $sTargetClass = $oLinkingAttDef->GetTargetClass(); $oRemoteClassSearch = new DBObjectSearch($sTargetClass); + if (!$bShowObsolete && MetaModel::IsObsoletable($sTargetClass)) { $oNotObsolete = new BinaryExpression( @@ -821,6 +822,18 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator ); $oRemoteClassSearch->AddConditionExpression($oNotObsolete); } + + if (!utils::IsArchiveMode() && MetaModel::IsArchivable($sTargetClass)) + { + $oNotArchived = new BinaryExpression( + new FieldExpression('archive_flag', $sTargetClass), + '=', + new ScalarExpression(0) + ); + + $oRemoteClassSearch->AddConditionExpression($oNotArchived); + } + $oLinkSearch->AddCondition_PointingTo($oRemoteClassSearch, $sExtKeyToRemote); $oLinkSearch->RenameAlias($oLinkSearch->GetClassAlias(), self::LINK_ALIAS); $oLinkSearch->RenameAlias($sTargetClass, self::REMOTE_ALIAS); @@ -835,4 +848,4 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator return $oLinkSet; } -} \ No newline at end of file +} diff --git a/core/tabularbulkexport.class.inc.php b/core/tabularbulkexport.class.inc.php index 1013474f0..0ec74c594 100644 --- a/core/tabularbulkexport.class.inc.php +++ b/core/tabularbulkexport.class.inc.php @@ -365,29 +365,37 @@ EOF { throw new BulkExportMissingParameterException('fields'); } - else if(($sQueryId !== null) && ($sQueryId !== null)) + else { - $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', array('query_id' => $sQueryId)); - $oQueries = new DBObjectSet($oSearch); - if ($oQueries->Count() > 0) + if (($sQueryId !== null) && ($sQueryId !== null)) { - $oQuery = $oQueries->Fetch(); - if (($sFields === null) || ($sFields === '')) + $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', array('query_id' => $sQueryId)); + $oQueries = new DBObjectSet($oSearch); + if ($oQueries->Count() > 0) { - // No 'fields' parameter supplied, take the fields from the query phrasebook definition - $sFields = trim($oQuery->Get('fields')); - if ($sFields === '') + $oQuery = $oQueries->Fetch(); + if (($sFields === null) || ($sFields === '')) { - throw new BulkExportMissingParameterException('fields'); + // No 'fields' parameter supplied, take the fields from the query phrasebook definition + $sFields = trim($oQuery->Get('fields')); + if ($sFields === '') + { + throw new BulkExportMissingParameterException('fields'); + } } } - } - else - { - throw BulkExportException('Invalid value for the parameter: query. There is no Query Phrasebook with id = '.$sQueryId, Dict::Format('Core:BulkExport:InvalidParameter_Query', $sQueryId)); + else + { + throw BulkExportException('Invalid value for the parameter: query. There is no Query Phrasebook with id = '.$sQueryId, Dict::Format('Core:BulkExport:InvalidParameter_Query', $sQueryId)); + } } } + $this->SetFields($sFields); + } + + public function SetFields($sFields) + { // Interpret (and check) the list of fields // $aSelectedClasses = $this->oSearch->GetSelectedClasses(); diff --git a/css/setup.css b/css/setup.css index 750e89cde..a6cbbcd12 100644 --- a/css/setup.css +++ b/css/setup.css @@ -265,3 +265,4 @@ fieldset > legend { font-weight: bold; color: #e60000b8; } + diff --git a/css/setup.scss b/css/setup.scss index acec8a053..82a2ff81f 100644 --- a/css/setup.scss +++ b/css/setup.scss @@ -16,6 +16,8 @@ * You should have received a copy of the GNU Affero General Public License */ +/* integrityCheck: begin (do not remove/edit) */ + ///////// // Colors $content-border-color: #CBD2D9 !default; @@ -314,3 +316,4 @@ fieldset{ } } +/* integrityCheck: end (do not remove/edit) */ \ No newline at end of file diff --git a/datamodels/2.x/combodo-db-tools/bin/report.php b/datamodels/2.x/combodo-db-tools/bin/report.php new file mode 100644 index 000000000..2428ce779 --- /dev/null +++ b/datamodels/2.x/combodo-db-tools/bin/report.php @@ -0,0 +1,29 @@ +CheckIntegrity([]); + +if (empty($aResults)) +{ + echo "Database OK\n"; + exit(0); +} + +$sReportFile = DBAnalyzerUtils::GenerateReport($aResults); + +echo "Report generated: {$sReportFile}.log\n"; diff --git a/datamodels/2.x/combodo-db-tools/dbtools.php b/datamodels/2.x/combodo-db-tools/dbtools.php index 9a80d2f55..869c4d337 100644 --- a/datamodels/2.x/combodo-db-tools/dbtools.php +++ b/datamodels/2.x/combodo-db-tools/dbtools.php @@ -17,6 +17,8 @@ * You should have received a copy of the GNU Affero General Public License */ +use Combodo\iTop\DBTools\Service\DBAnalyzerUtils; + @include_once('../../approot.inc.php'); require_once(APPROOT.'application/startup.inc.php'); @@ -53,7 +55,7 @@ function DisplayDBInconsistencies(iTopWebPage &$oP, ApplicationContext &$oAppCon $bRunAnalysis = intval(utils::ReadParam('run_analysis', '0')); if ($bRunAnalysis) { - $oDBAnalyzer = new DatabaseAnalyzer(); + $oDBAnalyzer = new DatabaseAnalyzer(0); $aResults = $oDBAnalyzer->CheckIntegrity($aClassSelection); if (empty($aResults)) { @@ -199,88 +201,23 @@ function DisplayDBInconsistencies(iTopWebPage &$oP, ApplicationContext &$oAppCon */ function DisplayInconsistenciesReport($aResults) { - $sDBToolsFolder = str_replace("\\", '/', APPROOT.'log/'); - $sReportFile = 'dbtools-report-'.date('Y-m-d-H-i-s'); - $sCleanupFile = 'dbtools-cleanup-'.date('Y-m-d-H-i-s'); - - $fReport = fopen($sDBToolsFolder.$sReportFile.'.txt', 'w'); - $fCleanUp = fopen($sDBToolsFolder.$sCleanupFile.'.txt', 'w'); - fwrite($fReport, 'Database Maintenance tools: '.date('Y-m-d H:i:s')."\r\n"); - foreach($aResults as $sClass => $aErrorList) - { - fwrite($fReport, ''); - foreach($aErrorList as $sErrorLabel => $aError) - { - fwrite($fReport, "\r\n----------\r\n"); - fwrite($fReport, 'Class: '.MetaModel::GetName($sClass).' ('.$sClass.")\r\n"); - $iCount = $aError['count']; - fwrite($fReport, 'Count: '.$iCount."\r\n"); - fwrite($fReport, 'Error: '.$sErrorLabel."\r\n"); - $sQuery = $aError['query']; - fwrite($fReport, 'Query: '.$sQuery."\r\n"); - - if (isset($aError['fixit'])) - { - fwrite($fReport, "\r\nFix it (indication):\r\n\r\n"); - $aFixitQueries = $aError['fixit']; - foreach($aFixitQueries as $sFixitQuery) - { - fwrite($fReport, "$sFixitQuery\r\n"); - } - fwrite($fReport, "\r\n"); - } - - if (isset($aError['cleanup'])) - { - $aQueries = $aError['cleanup']; - foreach($aQueries as $sQuery) - { - fwrite($fCleanUp, "$sQuery;\r\n"); - } - } - - $sQueryResult = ''; - $aIdList = array(); - foreach($aError['res'] as $aRes) - { - foreach($aRes as $sKey => $sValue) - { - $sQueryResult .= "'$sKey'='$sValue' "; - if ($sKey == 'id') - { - $aIdList[] = $sValue; - } - } - $sQueryResult .= "\r\n"; - - } - fwrite($fReport, "Result: \r\n".$sQueryResult); - $sIdList = '('.implode(',', $aIdList).')'; - fwrite($fReport, 'Ids: '.$sIdList."\r\n"); - } - } - fclose($fReport); - fclose($fCleanUp); + $sReportFile = DBAnalyzerUtils::GenerateReport($aResults); + $sZipReport = "{$sReportFile}.zip"; $oArchive = new ZipArchive(); - $oArchive->open($sDBToolsFolder.$sReportFile.'.zip', ZipArchive::CREATE); - $oArchive->addFile($sDBToolsFolder.$sReportFile.'.txt', $sReportFile.'.txt'); - $oArchive->addFile($sDBToolsFolder.$sCleanupFile.'.txt', $sCleanupFile.'.txt'); + $oArchive->open($sZipReport, ZipArchive::CREATE); + $oArchive->addFile($sReportFile.'.log', basename($sReportFile.'.log')); $oArchive->close(); - unlink($sDBToolsFolder.$sReportFile.'.txt'); - unlink($sDBToolsFolder.$sCleanupFile.'.txt'); - $sReportFile = $sDBToolsFolder.$sReportFile.'.zip'; - header('Content-Description: File Transfer'); header('Content-Type: multipart/x-zip'); - header('Content-Disposition: inline; filename="'.basename($sReportFile).'"'); + header('Content-Disposition: inline; filename="'.basename($sZipReport).'"'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); - header('Content-Length: '.filesize($sReportFile)); - readfile($sReportFile); - unlink($sReportFile); + header('Content-Length: '.filesize($sZipReport)); + readfile($sZipReport); + unlink($sZipReport); exit(0); } diff --git a/datamodels/2.x/combodo-db-tools/module.combodo-db-tools.php b/datamodels/2.x/combodo-db-tools/module.combodo-db-tools.php index c95e85d9c..c123b4204 100644 --- a/datamodels/2.x/combodo-db-tools/module.combodo-db-tools.php +++ b/datamodels/2.x/combodo-db-tools/module.combodo-db-tools.php @@ -42,7 +42,8 @@ SetupWebPage::AddModule( // 'datamodel' => array( 'model.combodo-db-tools.php', - 'src/Service/DBToolsUtils.php' + 'src/Service/DBToolsUtils.php', + 'src/Service/DBAnalyzerUtils.php', ), 'webservice' => array(), 'data.struct' => array(), diff --git a/datamodels/2.x/combodo-db-tools/src/Service/DBAnalyzerUtils.php b/datamodels/2.x/combodo-db-tools/src/Service/DBAnalyzerUtils.php new file mode 100644 index 000000000..725b45d55 --- /dev/null +++ b/datamodels/2.x/combodo-db-tools/src/Service/DBAnalyzerUtils.php @@ -0,0 +1,81 @@ + $aErrorList) + { + fwrite($fReport, ''); + foreach ($aErrorList as $sErrorLabel => $aError) + { + fwrite($fReport, "\r\n----------\r\n"); + fwrite($fReport, 'Class: '.MetaModel::GetName($sClass).' ('.$sClass.")\r\n"); + $iCount = $aError['count']; + fwrite($fReport, 'Count: '.$iCount."\r\n"); + fwrite($fReport, 'Error: '.$sErrorLabel."\r\n"); + $sQuery = $aError['query']; + fwrite($fReport, 'Query: '.$sQuery."\r\n"); + + if (isset($aError['fixit'])) + { + fwrite($fReport, "\r\nFix it (indication):\r\n\r\n"); + $aFixitQueries = $aError['fixit']; + foreach ($aFixitQueries as $sFixitQuery) + { + fwrite($fReport, "$sFixitQuery\r\n"); + } + fwrite($fReport, "\r\n"); + } + + $sQueryResult = ''; + $aIdList = array(); + foreach ($aError['res'] as $aRes) + { + foreach ($aRes as $sKey => $sValue) + { + $sQueryResult .= "'$sKey'='$sValue' "; + if ($sKey == 'id') + { + $aIdList[] = $sValue; + } + } + $sQueryResult .= "\r\n"; + + } + fwrite($fReport, "Result: \r\n".$sQueryResult); + $sIdList = '('.implode(',', $aIdList).')'; + fwrite($fReport, 'Ids: '.$sIdList."\r\n"); + } + } + fclose($fReport); + + + $sReportFile = $sDBToolsFolder.$sReportFile; + + return $sReportFile; + } +} diff --git a/datamodels/2.x/itop-portal-base/portal/public/js/export.js b/datamodels/2.x/itop-portal-base/portal/public/js/export.js index d08762600..5334ac363 100644 --- a/datamodels/2.x/itop-portal-base/portal/public/js/export.js +++ b/datamodels/2.x/itop-portal-base/portal/public/js/export.js @@ -20,8 +20,8 @@ function ExportStartExport() { var oParams = {}; oParams.operation = 'export_build'; oParams.format = sFormat; - oParams.expression = sOQL; - oParams.fields = sFields; + oParams.token = sToken; + oParams.start = 1; $.post(GetAbsoluteUrlAppRoot() + 'pages/ajax.render.php', oParams, function (data) { if (data == null) { ExportError('Export failed (no data provided), please contact your administrator'); diff --git a/datamodels/2.x/itop-portal-base/portal/src/Controller/BrowseBrickController.php b/datamodels/2.x/itop-portal-base/portal/src/Controller/BrowseBrickController.php index ae8956c65..8498486f8 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Controller/BrowseBrickController.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Controller/BrowseBrickController.php @@ -20,22 +20,22 @@ namespace Combodo\iTop\Portal\Controller; +use AttributeExternalKey; use AttributeLinkedSetIndirect; +use BinaryExpression; +use Combodo\iTop\Portal\Brick\AbstractBrick; +use Combodo\iTop\Portal\Brick\BrowseBrick; use Combodo\iTop\Portal\Helper\BrowseBrickHelper; use DBObjectSearch; +use DBObjectSet; +use DBSearch; +use FieldExpression; +use MetaModel; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\HttpException; -use MetaModel; -use DBSearch; -use DBObjectSet; -use BinaryExpression; -use FieldExpression; use VariableExpression; -use AttributeExternalKey; -use Combodo\iTop\Portal\Brick\AbstractBrick; -use Combodo\iTop\Portal\Brick\BrowseBrick; /** * Class BrowseBrickController @@ -156,8 +156,11 @@ class BrowseBrickController extends BrickController { if (array_key_exists($sLevelAlias, $aRealiasingMap)) { - $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->RenameAlias($aRealiasingMap[$sLevelAlias], - $sLevelAlias); + /** @since 2.7.2 */ + foreach ($aRealiasingMap[$sLevelAlias] as $sAliasToChange) + { + $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->RenameAlias($sAliasToChange, $sLevelAlias); + } } } } diff --git a/datamodels/2.x/itop-portal-base/portal/src/Controller/ManageBrickController.php b/datamodels/2.x/itop-portal-base/portal/src/Controller/ManageBrickController.php index ab8921327..3786612b3 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Controller/ManageBrickController.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Controller/ManageBrickController.php @@ -28,6 +28,7 @@ use AttributeImage; use AttributeSet; use AttributeTagSet; use BinaryExpression; +use BulkExport; use CMDBSource; use Combodo\iTop\Portal\Brick\AbstractBrick; use Combodo\iTop\Portal\Brick\ManageBrick; @@ -245,11 +246,18 @@ class ManageBrickController extends BrickController } $sFields = implode(',', $aFields); + $sFormat = 'xlsx'; + $oSearch->UpdateContextFromUser(); + $oExporter = BulkExport::FindExporter($sFormat, $oSearch); + $oExporter->SetObjectList($oSearch); + $oExporter->SetFormat($sFormat); + $oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE); + $oExporter->SetFields($sFields); + $aData = array( 'oBrick' => $oBrick, 'sBrickId' => $sBrickId, - 'sFields' => $sFields, - 'sOQL' => $oSearch->ToOQL(), + 'sToken' => $oExporter->SaveState(), ); return $this->render(static::EXCEL_EXPORT_TEMPLATE_PATH, $aData); diff --git a/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/popup-export-excel.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/popup-export-excel.html.twig index 4194af16c..32417e391 100644 --- a/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/popup-export-excel.html.twig +++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/manage/popup-export-excel.html.twig @@ -27,9 +27,8 @@