diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 5e100852e..d0298dc2c 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -239,8 +239,11 @@ EOF foreach($_SESSION['obj_messages'][$sMessageKey] as $sMessageId => $aMessageData) { $sMsgClass = 'message_'.$aMessageData['severity']; - $aMessages[] = "
".$aMessageData['message']."
"; - $aRanks[] = $aMessageData['rank']; + if(!in_array("
".$aMessageData['message']."
",$aMessages)) + { + $aMessages[] = "
".$aMessageData['message']."
"; + $aRanks[] = $aMessageData['rank']; + } } unset($_SESSION['obj_messages'][$sMessageKey]); } @@ -3012,10 +3015,38 @@ HTML $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $this->Get($sAttCode), $this->GetEditValue($sAttCode), 'att_'.$iFieldIndex, '', $iExpectCode, $aArgs); - $aDetails[] = array( + $aAttrib = array( 'label' => ''.$oAttDef->GetLabel().'', 'value' => "$sHTMLValue", ); + + //add attrib for data-attribute + // Prepare metadata attributes + $sAttCode = $oAttDef->GetCode(); + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $sAttDefClass = get_class($oAttDef); + $sAttLabel = MetaModel::GetLabel($sClass, $sAttCode); + + $aAttrib['attcode'] = $sAttCode; + $aAttrib['atttype'] = $sAttDefClass; + $aAttrib['attlabel'] = $sAttLabel; + // - Attribute flags + $aAttrib['attflags'] = $this->GetFormAttributeFlags($sAttCode) ; + // - How the field should be rendered + $aAttrib['layout'] = (in_array($oAttDef->GetEditClass(), static::GetAttEditClassesToRenderAsLargeField())) ? 'large' : 'small'; + // - For simple fields, we get the raw (stored) value as well + $bExcludeRawValue = false; + foreach (static::GetAttDefClassesToExcludeFromMarkupMetadataRawValue() as $sAttDefClassToExclude) + { + if (is_a($sAttDefClass, $sAttDefClassToExclude, true)) + { + $bExcludeRawValue = true; + break; + } + } + $aAttrib['value_raw'] = ($bExcludeRawValue === false) ? $this->Get($sAttCode) : ''; + + $aDetails[] = $aAttrib; $aFieldsMap[$sAttCode] = 'att_'.$iFieldIndex; $iFieldIndex++; } diff --git a/application/dashlet.class.inc.php b/application/dashlet.class.inc.php index 1e0e76f25..f43f232bb 100644 --- a/application/dashlet.class.inc.php +++ b/application/dashlet.class.inc.php @@ -1121,7 +1121,7 @@ abstract class DashletGroupBy extends Dashlet $this->sFunction = null; } - if (empty($this->aProperties['order_direction'])) + if ((!is_null($this->sClass)) && empty($this->aProperties['order_direction'])) { $aAttributeTypes = $this->oModelReflection->ListAttributes($this->sClass); if (isset($aAttributeTypes[$this->sGroupByAttCode])) diff --git a/application/loginbasic.class.inc.php b/application/loginbasic.class.inc.php index 9f56aaf88..660b45cba 100644 --- a/application/loginbasic.class.inc.php +++ b/application/loginbasic.class.inc.php @@ -40,7 +40,7 @@ class LoginBasic extends AbstractLoginFSMExtension protected function OnReadCredentials(&$iErrorCode) { - if ($_SESSION['login_mode'] == 'basic') + if (!isset($_SESSION['login_mode']) || $_SESSION['login_mode'] == 'basic') { list($sAuthUser, $sAuthPwd) = $this->GetAuthUserAndPassword(); $_SESSION['login_temp_auth_user'] = $sAuthUser; diff --git a/application/loginexternal.class.inc.php b/application/loginexternal.class.inc.php index 04ed7f1d4..d4fcb7182 100644 --- a/application/loginexternal.class.inc.php +++ b/application/loginexternal.class.inc.php @@ -67,6 +67,15 @@ class LoginExternal extends AbstractLoginFSMExtension return LoginWebPage::LOGIN_FSM_CONTINUE; } + protected function OnError(&$iErrorCode) + { + if ($_SESSION['login_mode'] == 'external') + { + LoginWebPage::HTTP401Error(); + } + return LoginWebPage::LOGIN_FSM_CONTINUE; + } + /** * @return bool */ diff --git a/composer.json b/composer.json index 240f68395..e363d44da 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ "ext-json": "*", "ext-mysqli": "*", "ext-soap": "*", - "combodo/tcpdf": "6.3.4", + "combodo/tcpdf": "6.3.5", "nikic/php-parser": "^3.1", "pear/archive_tar": "1.4.9", "pelago/emogrifier": "2.1.0", diff --git a/composer.lock b/composer.lock index 40179fbc2..3a08b7f7d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b29eb2767d269b9ec2cf4d148dc083bc", + "content-hash": "ad359769d05acd25a9fc31d69acbe43a", "packages": [ { "name": "combodo/tcpdf", - "version": "6.3.4", + "version": "6.3.5", "source": { "type": "git", "url": "https://github.com/combodo-itop-libs/TCPDF.git", - "reference": "fe1c625d33e8f7d872d6fb69fb0255fd0e5cee2d" + "reference": "abbfedb8ca59843dec11c97ca3f308742265c3fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/combodo-itop-libs/TCPDF/zipball/fe1c625d33e8f7d872d6fb69fb0255fd0e5cee2d", - "reference": "fe1c625d33e8f7d872d6fb69fb0255fd0e5cee2d", + "url": "https://api.github.com/repos/combodo-itop-libs/TCPDF/zipball/abbfedb8ca59843dec11c97ca3f308742265c3fc", + "reference": "abbfedb8ca59843dec11c97ca3f308742265c3fc", "shasum": "" }, "require": { @@ -64,7 +64,7 @@ ], "description": "TCPDF fork adding requirements for iTop: Specific fonts.", "homepage": "https://github.com/combodo-itop-libs/TCPDF", - "time": "2020-02-12T14:16:56+00:00" + "time": "2020-06-05T13:06:44+00:00" }, { "name": "nikic/php-parser", diff --git a/core/backgroundprocess.inc.php b/core/backgroundprocess.inc.php index 8b25c70b3..05ec4572e 100644 --- a/core/backgroundprocess.inc.php +++ b/core/backgroundprocess.inc.php @@ -1,29 +1,23 @@ - - /** - * interface iProcess - * Something that can be executed + * Copyright (C) 2010-2020 Combodo SARL * - * @copyright Copyright (C) 2010-2012 Combodo SARL - * @license http://opensource.org/licenses/AGPL-3.0 + * This file is part of iTop. + * + * iTop is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * iTop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License */ + + interface iProcess { /** @@ -94,6 +88,11 @@ abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess const DEFAULT_MODULE_SETTING_WEEKDAYS = 'monday, tuesday, wednesday, thursday, friday, saturday, sunday'; const MODULE_SETTING_TIME = 'time'; + /** + * @var Config can be used to mock config for tests + */ + protected $oConfig; + /** * Module must be declared in each implementation * @@ -107,6 +106,20 @@ abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess */ abstract protected function GetDefaultModuleSettingTime(); + /** + * @return \Config + */ + public function getOConfig() + { + if (!isset($this->oConfig)) + { + $this->oConfig = MetaModel::GetConfig(); + } + + return $this->oConfig; + } + + /** * Interpret current setting for the week days * @@ -125,7 +138,7 @@ abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess 'sunday' => 7, ); $aDays = array(); - $sWeekDays = MetaModel::GetConfig()->GetModuleSetting( + $sWeekDays = $this->getOConfig()->GetModuleSetting( $this->GetModuleName(), static::MODULE_SETTING_WEEKDAYS, static::DEFAULT_MODULE_SETTING_WEEKDAYS @@ -158,21 +171,26 @@ abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess } /** - * Gives the exact time at which the process must be run next time + * @param string $sCurrentTime Date string to extract time dependency + * this parameter is not present in the interface but as it is optional it's ok * - * @return DateTime - * @throws Exception + * @return DateTime the exact time at which the process must be run next time + * @throws \ProcessInvalidConfigException */ - public function GetNextOccurrence() + public function GetNextOccurrence($sCurrentTime = 'now') { - $bEnabled = MetaModel::GetConfig()->GetModuleSetting( + $bEnabled = $this->getOConfig()->GetModuleSetting( $this->GetModuleName(), static::MODULE_SETTING_ENABLED, static::DEFAULT_MODULE_SETTING_ENABLED ); + + $sItopTimeZone = $this->getOConfig()->Get('timezone'); + $timezone = new DateTimeZone($sItopTimeZone); + if (!$bEnabled) { - return new DateTime('3000-01-01'); + return new DateTime('3000-01-01', $timezone); } // 1st - Interpret the list of days as ordered numbers (monday = 1) @@ -181,7 +199,7 @@ abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess // 2nd - Find the next active week day // - $sProcessTime = MetaModel::GetConfig()->GetModuleSetting( + $sProcessTime = $this->getOConfig()->GetModuleSetting( $this->GetModuleName(), static::MODULE_SETTING_TIME, static::GetDefaultModuleSettingTime() @@ -190,7 +208,8 @@ abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess { throw new ProcessInvalidConfigException($this->GetModuleName().": wrong format for setting '".static::MODULE_SETTING_TIME."' (found '$sProcessTime')"); } - $oNow = new DateTime(); + + $oNow = new DateTime($sCurrentTime, $timezone); $iNextPos = false; $sDay = $oNow->format('N'); for ($iDay = (int) $sDay; $iDay <= 7; $iDay++) diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index 8b8bdef71..d7a48bf4c 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -1248,9 +1248,9 @@ class CMDBSource */ private static function GetFieldDataTypeAndOptions($sCompleteFieldType) { - preg_match('/^([a-zA-Z]+)(\(([^\)]+)\))?( .+)?$/', $sCompleteFieldType, $aMatches); + preg_match('/^([a-zA-Z]+)(\(([^\)]+)\))?( .+)?/', $sCompleteFieldType, $aMatches); - $sDataType = $aMatches[1]; + $sDataType = isset($aMatches[1]) ? $aMatches[1] : ''; $sTypeOptions = isset($aMatches[2]) ? $aMatches[3] : ''; $sOtherOptions = isset($aMatches[4]) ? $aMatches[4] : ''; diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 95a78010d..492c503c0 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -313,6 +313,11 @@ class DBObjectSearch extends DBSearch return true; } + /** + * Move conditions from $oFilter to $this + * @param \DBSearch $oFilter + * @param $aTranslation + */ protected function TransferConditionExpression($oFilter, $aTranslation) { // Prevent collisions in the parameter names by renaming them if needed @@ -335,6 +340,7 @@ class DBObjectSearch extends DBSearch $oTranslated = $oFilter->GetCriteria()->Translate($aTranslation, false, false /* leave unresolved fields */); $this->AddConditionExpression($oTranslated); $this->m_aParams = array_merge($this->m_aParams, $oFilter->m_aParams); + $oFilter->ResetCondition(); } public function RenameParam($sOldName, $sNewName) diff --git a/core/dbsearch.class.php b/core/dbsearch.class.php index 6c7dc7459..66884183e 100644 --- a/core/dbsearch.class.php +++ b/core/dbsearch.class.php @@ -1179,8 +1179,7 @@ abstract class DBSearch if (is_object($oVisibleObjects)) { $oVisibleObjects->AllowAllData(); - $oSearch = $this->Filter($sClassAlias, $oVisibleObjects); - /** @var DBSearch $oSearch */ + $oSearch = $oSearch->Filter($sClassAlias, $oVisibleObjects); $oSearch->SetDataFiltered(); } } diff --git a/core/log.class.inc.php b/core/log.class.inc.php index 1590d26e3..dfb0fa3e6 100644 --- a/core/log.class.inc.php +++ b/core/log.class.inc.php @@ -686,6 +686,12 @@ abstract class LogAPI class SetupLog extends LogAPI { const CHANNEL_DEFAULT = 'SetupLog'; + /** + * @inheritDoc + * + * As this object is used during setup, without any conf file available, customizing the level can be done by changing this constant ! + */ + const LEVEL_DEFAULT = self::LEVEL_INFO; protected static $m_oFileLog = null; } diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 50a46c0fa..fb09fab78 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -5747,6 +5747,15 @@ abstract class MetaModel { $sTableItems = implode(', ', $aCreateTableItems[$sTable]); $aCondensedQueries[] = "CREATE TABLE `$sTable` ($sTableItems) $sTableOptions"; + // Add request right after the CREATE TABLE + if (isset($aPostTableAlteration[$sTable])) + { + foreach ($aPostTableAlteration[$sTable] as $sQuery) + { + $aCondensedQueries[] = $sQuery; + } + unset($aPostTableAlteration[$sTable]); + } } foreach ($aAlterTableMetaData as $sTableAlterQuery) { @@ -5763,9 +5772,19 @@ abstract class MetaModel { $aCondensedQueries[] = $sQuery; } + unset($aPostTableAlteration[$sTable]); } } + // Add alterations not yet managed + foreach ($aPostTableAlteration as $aQueries) + { + foreach ($aQueries as $sQuery) + { + $aCondensedQueries[] = $sQuery; + } + } + return array($aErrors, $aSugFix, $aCondensedQueries); } diff --git a/datamodels/2.x/itop-core-update/en.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/en.dict.itop-core-update.php index 5d3fff5cd..72288f7cf 100644 --- a/datamodels/2.x/itop-core-update/en.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/en.dict.itop-core-update.php @@ -73,7 +73,8 @@ Dict::Add('EN US', 'English', 'English', array( 'iTopUpdate:UI:CanCoreUpdate:ErrorFileNotExist' => 'Checking files failed (File not exist %1$s)', 'iTopUpdate:UI:CanCoreUpdate:Failed' => 'Checking files failed', 'iTopUpdate:UI:CanCoreUpdate:Yes' => 'Application can be updated', - 'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s', + 'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s', + 'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s', // Setup Messages 'iTopUpdate:UI:SetupMessage:Ready' => 'Ready to start', diff --git a/datamodels/2.x/itop-core-update/fr.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/fr.dict.itop-core-update.php index bb1f926c9..8dae6b928 100644 --- a/datamodels/2.x/itop-core-update/fr.dict.itop-core-update.php +++ b/datamodels/2.x/itop-core-update/fr.dict.itop-core-update.php @@ -74,6 +74,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'iTopUpdate:UI:CanCoreUpdate:Failed' => 'Échec de la vérification des fichiers', 'iTopUpdate:UI:CanCoreUpdate:Yes' => 'L\'application peut être mise à jour', 'iTopUpdate:UI:CanCoreUpdate:No' => 'L\'application ne peut pas être mise à jour : %1$s', + 'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Attention : la mise à jour de l\'application peut échouer : %1$s', // Setup Messages 'iTopUpdate:UI:SetupMessage:Ready' => 'Prêt pour l\\installation', diff --git a/datamodels/2.x/itop-core-update/src/Controller/AjaxController.php b/datamodels/2.x/itop-core-update/src/Controller/AjaxController.php index 736947708..dc33efa7f 100644 --- a/datamodels/2.x/itop-core-update/src/Controller/AjaxController.php +++ b/datamodels/2.x/itop-core-update/src/Controller/AjaxController.php @@ -28,7 +28,8 @@ class AjaxController extends Controller try { - $bCanUpdateCore = FilesInformation::CanUpdateCore($sMessage); + $sCanUpdateCore = FilesInformation::CanUpdateCore($sMessage); + $bCanUpdateCore = ($sCanUpdateCore == 'Yes'); $aParams['bStatus'] = $bCanUpdateCore; if ($bCanUpdateCore) { @@ -36,7 +37,7 @@ class AjaxController extends Controller } else { - $aParams['sMessage'] = Dict::Format('iTopUpdate:UI:CanCoreUpdate:No', $sMessage); + $aParams['sMessage'] = Dict::Format("iTopUpdate:UI:CanCoreUpdate:{$sCanUpdateCore}", $sMessage); } } catch (FileNotExistException $e) { diff --git a/datamodels/2.x/itop-files-information/src/Service/FilesInformation.php b/datamodels/2.x/itop-files-information/src/Service/FilesInformation.php index 251ea6e7d..db105ba8a 100644 --- a/datamodels/2.x/itop-files-information/src/Service/FilesInformation.php +++ b/datamodels/2.x/itop-files-information/src/Service/FilesInformation.php @@ -21,33 +21,71 @@ class FilesInformation * * @param string $sMessage * - * @return bool true if core update is possible + * @return string 'Yes', 'No', 'Warning' * @throws \Combodo\iTop\FilesInformation\Service\FileNotExistException + * @throws \Exception */ public static function CanUpdateCore(&$sMessage) { self::Init(); // Check than iTop can write everywhere - if (!self::CanWriteRecursive('', $sMessage)) + $aFilesInfo = FilesIntegrity::GetInstalledFiles(APPROOT.'manifest.xml'); + if ($aFilesInfo === false) + { + $sMessage = Dict::Format('FilesInformation:Error:MissingFile', 'manifest.xml'); + return 'No'; + } + // generate files and folders list + $aInstalledFiles = array(); + foreach (array_keys($aFilesInfo) as $sFile) + { + $sLocalDirPath = utils::LocalPath(APPROOT.dirname($sFile)); + if ($sLocalDirPath !== false) + { + if (!isset($aInstalledFiles[$sLocalDirPath])) + { + $aInstalledFiles[$sLocalDirPath] = true; + } + $aInstalledFiles[$sFile] = true; + } + } + if (!self::CanWriteRecursive('', $sMessage, $aInstalledFiles)) { - return false; + return 'No'; } - return true; + try + { + FilesIntegrity::CheckInstallationIntegrity(); + } + catch (FileIntegrityException $e) + { + $sMessage = $e->getMessage(); + return 'Warning'; + } + + return 'Yes'; } /** * @param string $sRootPath * @param string $sMessage + * @param array $aInstalledFiles * * @return bool * @throws \Combodo\iTop\FilesInformation\Service\FileNotExistException */ - private static function CanWriteRecursive($sRootPath = '', &$sMessage = null) + private static function CanWriteRecursive($sRootPath = '', &$sMessage = null, $aInstalledFiles = array()) { $aDirStats = FilesInformationUtils::Scan($sRootPath, false); foreach ($aDirStats as $sFileName => $aFileStats) { + // For name normalization + $sLocalPath = utils::LocalPath(APPROOT.$sRootPath.DIRECTORY_SEPARATOR.$sFileName); + if (($sLocalPath === false) || !isset($aInstalledFiles[$sLocalPath])) + { + continue; + } if (!self::CanWriteToFile($aFileStats)) { $sMessage = Dict::Format('FilesInformation:Error:CantWriteToFile', $sRootPath.DIRECTORY_SEPARATOR.$sFileName); @@ -55,7 +93,7 @@ class FilesInformation } if (($sFileName != '.') && ($aFileStats['type'] == 'dir')) { - if (!self::CanWriteRecursive($sRootPath.DIRECTORY_SEPARATOR.$sFileName, $sMessage)) + if (!self::CanWriteRecursive($sRootPath.DIRECTORY_SEPARATOR.$sFileName, $sMessage, $aInstalledFiles)) { return false; } diff --git a/datamodels/2.x/itop-files-information/src/Service/FilesIntegrity.php b/datamodels/2.x/itop-files-information/src/Service/FilesIntegrity.php index 25753b3b7..66c8163cf 100644 --- a/datamodels/2.x/itop-files-information/src/Service/FilesIntegrity.php +++ b/datamodels/2.x/itop-files-information/src/Service/FilesIntegrity.php @@ -25,7 +25,7 @@ class FilesIntegrity * @return array|false list of file info (path, size, md5) * @throws \Exception */ - private static function GetInstalledFiles($sManifest) + public static function GetInstalledFiles($sManifest) { $aFiles = array(); @@ -54,14 +54,19 @@ class FilesIntegrity if ($oFileNode->hasChildNodes()) { $aFileInfo = array(); + $sFilePath = uniqid(); // just in case no path... foreach ($oFileNode->childNodes as $oFileInfo) { if ($oFileInfo instanceof DOMElement) { $aFileInfo[$oFileInfo->tagName] = $oFileInfo->textContent; + if ($oFileInfo->tagName == 'path') + { + $sFilePath = $oFileInfo->textContent; + } } } - $aFiles[] = $aFileInfo; + $aFiles[$sFilePath] = $aFileInfo; } } } @@ -98,7 +103,6 @@ class FilesIntegrity $sChecksum = md5($sContent); if (($iSize != $aFileInfo['size']) || ($sChecksum != $aFileInfo['md5'])) { - throw new FileIntegrityException(Dict::Format('FilesInformation:Error:CorruptedFile', basename($sFile))); } } diff --git a/datamodels/2.x/itop-portal-base/portal/templates/modal/mode_loader.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/modal/mode_loader.html.twig index 75c4afd6e..2061b19a5 100644 --- a/datamodels/2.x/itop-portal-base/portal/templates/modal/mode_loader.html.twig +++ b/datamodels/2.x/itop-portal-base/portal/templates/modal/mode_loader.html.twig @@ -8,7 +8,14 @@ {% if redirection is defined and redirection.url is defined %} {% endif %} diff --git a/datamodels/2.x/itop-service-mgmt-provider/datamodel.itop-service-mgmt-provider.xml b/datamodels/2.x/itop-service-mgmt-provider/datamodel.itop-service-mgmt-provider.xml index ad7912c31..e623d2689 100755 --- a/datamodels/2.x/itop-service-mgmt-provider/datamodel.itop-service-mgmt-provider.xml +++ b/datamodels/2.x/itop-service-mgmt-provider/datamodel.itop-service-mgmt-provider.xml @@ -1637,23 +1637,26 @@ public function PrefillSearchForm(&$aContextParam) parent::DoCheckToWrite(); $aCustomerContracts = $this->Get("customercontracts_list"); - foreach($aCustomerContracts as $sAttCode => $oCustomerContracts) + foreach ($aCustomerContracts as $sAttCode => $oCustomerContracts) { // Recurse inside the subdirectories - $sOql="SELECT lnkCustomerContractToService AS ccs WHERE ccs.customercontract_id=:customercontract_id AND ccs.service_id=:service_id AND ccs.sla_id!=:sla_id"; + $sOql = "SELECT lnkCustomerContractToService AS ccs WHERE ccs.customercontract_id=:customercontract_id AND ccs.service_id=:service_id"; $aQueryParams['customercontract_id'] = $oCustomerContracts->Get("customercontract_id"); $aQueryParams['service_id'] = $oCustomerContracts->Get("service_id"); - $aQueryParams['sla_id'] = $this->Get("id"); + if ($this->Get("id") != null) + { + $sOql = $sOql." AND ccs.sla_id!=:sla_id"; + $aQueryParams['sla_id'] = $this->Get("id"); + } $oQuery = DBSearch::FromOQL($sOql, $aQueryParams); $oResultSql = new DBObjectSet($oQuery); $oResultSql->OptimizeColumnLoad(['ccs.customercontract_name','ccs.service_name']); - while ($aCurrentRow = $oResultSql->Fetch()) + if ($aCurrentRow = $oResultSql->Fetch()) { $this->m_aCheckIssues[] = Dict::Format('Class:SLA/Error:UniqueLnkCustomerContractToService',$aCurrentRow->Get('customercontract_name'),$aCurrentRow->Get('service_name')); } } } - ]]> diff --git a/datamodels/2.x/itop-service-mgmt/datamodel.itop-service-mgmt.xml b/datamodels/2.x/itop-service-mgmt/datamodel.itop-service-mgmt.xml index 19f1885bb..20aa4680c 100755 --- a/datamodels/2.x/itop-service-mgmt/datamodel.itop-service-mgmt.xml +++ b/datamodels/2.x/itop-service-mgmt/datamodel.itop-service-mgmt.xml @@ -1631,23 +1631,26 @@ public function PrefillSearchForm(&$aContextParam) parent::DoCheckToWrite(); $aCustomerContracts = $this->Get("customercontracts_list"); - foreach($aCustomerContracts as $sAttCode => $oCustomerContracts) + foreach ($aCustomerContracts as $sAttCode => $oCustomerContracts) { // Recurse inside the subdirectories - $sOql="SELECT lnkCustomerContractToService AS ccs WHERE ccs.customercontract_id=:customercontract_id AND ccs.service_id=:service_id AND ccs.sla_id!=:sla_id"; + $sOql = "SELECT lnkCustomerContractToService AS ccs WHERE ccs.customercontract_id=:customercontract_id AND ccs.service_id=:service_id"; $aQueryParams['customercontract_id'] = $oCustomerContracts->Get("customercontract_id"); $aQueryParams['service_id'] = $oCustomerContracts->Get("service_id"); - $aQueryParams['sla_id'] = $this->Get("id"); + if ($this->Get("id") != null) + { + $sOql = $sOql." AND ccs.sla_id!=:sla_id"; + $aQueryParams['sla_id'] = $this->Get("id"); + } $oQuery = DBSearch::FromOQL($sOql, $aQueryParams); $oResultSql = new DBObjectSet($oQuery); $oResultSql->OptimizeColumnLoad(['ccs.customercontract_name','ccs.service_name']); - while ($aCurrentRow = $oResultSql->Fetch()) + if ($aCurrentRow = $oResultSql->Fetch()) { $this->m_aCheckIssues[] = Dict::Format('Class:SLA/Error:UniqueLnkCustomerContractToService',$aCurrentRow->Get('customercontract_name'),$aCurrentRow->Get('service_name')); } } } - ]]> diff --git a/js/dashboard.js b/js/dashboard.js index 4c8350bcc..bcf20dd00 100644 --- a/js/dashboard.js +++ b/js/dashboard.js @@ -154,8 +154,10 @@ $(function() var sDashletUniqueId = $(this).attr("id"); var sDashletIdParts = sDashletUniqueId.split('_'); var sDashletOrigId = sDashletIdParts[sDashletIdParts.length - 1]; - return isNaN(sDashletOrigId) ? 0 : parseInt(sDashletOrigId); + return isNaN(parseInt(sDashletOrigId)) ? 0 : parseInt(sDashletOrigId); }).get(); + // avoid empty array for IE + aDashletsIds.push(0); // Note: Use of .apply() to be compatible with IE10 var iHighestDashletOrigId = Math.max.apply(null, aDashletsIds); diff --git a/lib/combodo/tcpdf/README.md b/lib/combodo/tcpdf/README.md index 734b9879f..db0149f69 100644 --- a/lib/combodo/tcpdf/README.md +++ b/lib/combodo/tcpdf/README.md @@ -6,7 +6,7 @@ * **category** Library * **author** Nicola Asuni -* **copyright** 2002-2019 Nicola Asuni - Tecnick.com LTD +* **copyright** 2002-2020 Nicola Asuni - Tecnick.com LTD * **license** http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) * **link** http://www.tcpdf.org * **source** https://github.com/tecnickcom/TCPDF diff --git a/lib/combodo/tcpdf/VERSION b/lib/combodo/tcpdf/VERSION index 8ac3c4451..b98d1d3fa 100644 --- a/lib/combodo/tcpdf/VERSION +++ b/lib/combodo/tcpdf/VERSION @@ -1 +1 @@ -6.3.4 +6.3.5 diff --git a/lib/combodo/tcpdf/composer.json b/lib/combodo/tcpdf/composer.json index fe90ae9eb..d9ac48435 100644 --- a/lib/combodo/tcpdf/composer.json +++ b/lib/combodo/tcpdf/composer.json @@ -6,13 +6,8 @@ "homepage": "https://github.com/combodo-itop-libs/TCPDF", "type": "library", "description": "TCPDF fork adding requirements for iTop: Specific fonts.", - "license": "LGPL-3.0-only", + "license": "LGPL-3.0", "authors": [ - { - "name": "Nicola Asuni", - "email": "info@tecnick.com", - "role": "lead" - }, { "name": "Combodo", "email": "contact@combodo.com" diff --git a/lib/combodo/tcpdf/include/barcodes/pdf417.php b/lib/combodo/tcpdf/include/barcodes/pdf417.php index 3b1774eaa..9a58a21f6 100644 --- a/lib/combodo/tcpdf/include/barcodes/pdf417.php +++ b/lib/combodo/tcpdf/include/barcodes/pdf417.php @@ -878,7 +878,7 @@ class PDF417 { $txtarr = array(); // array of characters and sub-mode switching characters $codelen = strlen($code); for ($i = 0; $i < $codelen; ++$i) { - $chval = ord($code{$i}); + $chval = ord($code[$i]); if (($k = array_search($chval, $this->textsubmodes[$submode])) !== false) { // we are on the same sub-mode $txtarr[] = $k; @@ -888,7 +888,7 @@ class PDF417 { // search new sub-mode if (($s != $submode) AND (($k = array_search($chval, $this->textsubmodes[$s])) !== false)) { // $s is the new submode - if (((($i + 1) == $codelen) OR ((($i + 1) < $codelen) AND (array_search(ord($code{($i + 1)}), $this->textsubmodes[$submode]) !== false))) AND (($s == 3) OR (($s == 0) AND ($submode == 1)))) { + if (((($i + 1) == $codelen) OR ((($i + 1) < $codelen) AND (array_search(ord($code[($i + 1)]), $this->textsubmodes[$submode]) !== false))) AND (($s == 3) OR (($s == 0) AND ($submode == 1)))) { // shift (temporary change only for this char) if ($s == 3) { // shift to puntuaction @@ -952,7 +952,7 @@ class PDF417 { $cw = array_merge($cw, $cw6); } else { for ($i = 0; $i < $sublen; ++$i) { - $cw[] = ord($code{$i}); + $cw[] = ord($code[$i]); } } $code = $rest; diff --git a/lib/combodo/tcpdf/include/tcpdf_static.php b/lib/combodo/tcpdf/include/tcpdf_static.php index 8e9686953..06a1dddcc 100644 --- a/lib/combodo/tcpdf/include/tcpdf_static.php +++ b/lib/combodo/tcpdf/include/tcpdf_static.php @@ -55,7 +55,7 @@ class TCPDF_STATIC { * Current TCPDF version. * @private static */ - private static $tcpdf_version = '6.3.4'; + private static $tcpdf_version = '6.3.5'; /** * String alias for total number of pages. @@ -1859,7 +1859,7 @@ class TCPDF_STATIC { public static function encodeUrlQuery($url) { $urlData = parse_url($url); if (isset($urlData['query']) && $urlData['query']) { - $urlQueryData = []; + $urlQueryData = array(); parse_str(urldecode($urlData['query']), $urlQueryData); $updatedUrl = $urlData['scheme'] . '://' . $urlData['host'] . $urlData['path'] . '?' . http_build_query($urlQueryData); } else { diff --git a/lib/combodo/tcpdf/tcpdf.php b/lib/combodo/tcpdf/tcpdf.php index be32cae7a..a71afc3a8 100644 --- a/lib/combodo/tcpdf/tcpdf.php +++ b/lib/combodo/tcpdf/tcpdf.php @@ -7178,7 +7178,7 @@ class TCPDF { } else { $ximg = $x; } - + if ($ismask OR $hidden) { // image is not displayed return $info['i']; @@ -12384,7 +12384,8 @@ class TCPDF { $x = $this->w; } $fixed = false; - if ((string)$page && (((string)$page)[0] == '*')) { + $pageAsString = (string) $page; + if ($pageAsString && $pageAsString[0] == '*') { $page = intval(substr($page, 1)); // this page number will not be changed when moving/add/deleting pages $fixed = true; diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index e1139fb16..8f8e6b200 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -197,6 +197,7 @@ return array( 'DataTableSettings' => $baseDir . '/application/datatable.class.inc.php', 'Datamatrix' => $vendorDir . '/combodo/tcpdf/include/barcodes/datamatrix.php', 'DateTimeFormat' => $baseDir . '/core/datetimeformat.class.inc.php', + 'DeadLockLog' => $baseDir . '/core/log.class.inc.php', 'DefaultLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php', 'DefaultMetricComputer' => $baseDir . '/core/computing.inc.php', 'DefaultWorkingTimeComputer' => $baseDir . '/core/computing.inc.php', diff --git a/lib/composer/autoload_real.php b/lib/composer/autoload_real.php index e8c595bf1..ac16a9508 100644 --- a/lib/composer/autoload_real.php +++ b/lib/composer/autoload_real.php @@ -13,6 +13,9 @@ class ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b } } + /** + * @return \Composer\Autoload\ClassLoader + */ public static function getLoader() { if (null !== self::$loader) { diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index d51cb9049..818c364ae 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -427,6 +427,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'DataTableSettings' => __DIR__ . '/../..' . '/application/datatable.class.inc.php', 'Datamatrix' => __DIR__ . '/..' . '/combodo/tcpdf/include/barcodes/datamatrix.php', 'DateTimeFormat' => __DIR__ . '/../..' . '/core/datetimeformat.class.inc.php', + 'DeadLockLog' => __DIR__ . '/../..' . '/core/log.class.inc.php', 'DefaultLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php', 'DefaultMetricComputer' => __DIR__ . '/../..' . '/core/computing.inc.php', 'DefaultWorkingTimeComputer' => __DIR__ . '/../..' . '/core/computing.inc.php', diff --git a/lib/composer/installed.json b/lib/composer/installed.json index 9ff117e69..1ec9d2a9a 100644 --- a/lib/composer/installed.json +++ b/lib/composer/installed.json @@ -1,17 +1,17 @@ [ { "name": "combodo/tcpdf", - "version": "6.3.4", - "version_normalized": "6.3.4.0", + "version": "6.3.5", + "version_normalized": "6.3.5.0", "source": { "type": "git", "url": "https://github.com/combodo-itop-libs/TCPDF.git", - "reference": "fe1c625d33e8f7d872d6fb69fb0255fd0e5cee2d" + "reference": "abbfedb8ca59843dec11c97ca3f308742265c3fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/combodo-itop-libs/TCPDF/zipball/fe1c625d33e8f7d872d6fb69fb0255fd0e5cee2d", - "reference": "fe1c625d33e8f7d872d6fb69fb0255fd0e5cee2d", + "url": "https://api.github.com/repos/combodo-itop-libs/TCPDF/zipball/abbfedb8ca59843dec11c97ca3f308742265c3fc", + "reference": "abbfedb8ca59843dec11c97ca3f308742265c3fc", "shasum": "" }, "require": { @@ -20,7 +20,7 @@ "replace": { "tecnickcom/tcpdf": "self.version" }, - "time": "2020-02-12T14:16:56+00:00", + "time": "2020-06-05T13:06:44+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -45,21 +45,22 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0-only" + "LGPL-3.0" ], "authors": [ - { - "name": "Nicola Asuni", - "email": "info@tecnick.com", - "role": "lead" - }, { "name": "Combodo", "email": "contact@combodo.com" } ], "description": "TCPDF fork adding requirements for iTop: Specific fonts.", - "homepage": "https://github.com/combodo-itop-libs/TCPDF" + "homepage": "https://github.com/combodo-itop-libs/TCPDF", + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_donations¤cy_code=GBP&business=paypal@tecnick.com&item_name=donation%20for%20tcpdf%20project", + "type": "custom" + } + ] }, { "name": "nikic/php-parser", diff --git a/setup/setuputils.class.inc.php b/setup/setuputils.class.inc.php index 70911820c..2b260890c 100644 --- a/setup/setuputils.class.inc.php +++ b/setup/setuputils.class.inc.php @@ -76,7 +76,7 @@ class SetupUtils * @internal SetupPage $oP The page used only for its 'log' method * @return CheckResult[] */ - static function CheckPhpAndExtensions() + public static function CheckPhpAndExtensions() { $aResult = array(); @@ -419,7 +419,7 @@ class SetupUtils * @param $aSelectedModules * @return array */ - static function CheckSelectedModules($sSourceDir, $sExtensionDir, $aSelectedModules) + public static function CheckSelectedModules($sSourceDir, $sExtensionDir, $aSelectedModules) { $aResult = array(); SetupPage::log('Info - CheckSelectedModules'); @@ -450,7 +450,7 @@ class SetupUtils * @return array An array of CheckResults objects * @internal param Page $oP The page used only for its 'log' method */ - static function CheckBackupPrerequisites($sDBBackupPath, $sMySQLBinDir = null) + public static function CheckBackupPrerequisites($sDBBackupPath, $sMySQLBinDir = null) { $aResult = array(); SetupPage::log('Info - CheckBackupPrerequisites'); @@ -540,7 +540,7 @@ class SetupUtils * @return CheckResult The result of the check * @internal param string $GraphvizPath The path where graphviz' dot program is installed */ - static function CheckGraphviz($sGraphvizPath) + public static function CheckGraphviz($sGraphvizPath) { $oResult = null; SetupPage::log('Info - CheckGraphviz'); @@ -589,7 +589,7 @@ class SetupUtils * Emulates sys_get_temp_dir if needed (PHP < 5.2.1) * @return string Path to the system's temp directory */ - static function GetTmpDir() + public static function GetTmpDir() { return realpath(sys_get_temp_dir()); } @@ -598,7 +598,7 @@ class SetupUtils * Helper function to retrieve the directory where files are to be uploaded * @return string Path to the temp directory used for uploading files */ - static function GetUploadTmpDir() + public static function GetUploadTmpDir() { $sPath = ini_get('upload_tmp_dir'); if (empty($sPath)) @@ -821,7 +821,7 @@ class SetupUtils } } - static function GetPreviousInstance($sDir) + public static function GetPreviousInstance($sDir) { $sSourceDir = ''; $sSourceEnvironment = ''; @@ -875,7 +875,7 @@ class SetupUtils * @return bool|float false if failure * @uses \disk_free_space() */ - static function CheckDiskSpace($sDir) + public static function CheckDiskSpace($sDir) { while(($f = @disk_free_space($sDir)) == false) { @@ -887,7 +887,7 @@ class SetupUtils return $f; } - static function HumanReadableSize($fBytes) + public static function HumanReadableSize($fBytes) { $aSizes = array('bytes', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb', 'Hb'); $index = 0; @@ -912,7 +912,7 @@ class SetupUtils * @param string $sTlsCA * @param string $sNewDBName */ - static function DisplayDBParameters( + public static function DisplayDBParameters( $oPage, $bIsItopInstall, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $bTlsEnabled, $sTlsCA, $sNewDBName = '' ) { @@ -1155,7 +1155,7 @@ EOF * @return bool|array false if the connection failed or array('checks' => Array of CheckResult, 'databases' => * Array of database names (as strings) or null if not allowed) */ - static function CheckDbServer( + public static function CheckDbServer( $sDBServer, $sDBUser, $sDBPwd, $bTlsEnabled = false, $sTlsCA = null ) { @@ -1606,29 +1606,16 @@ JS } $sManualInstallModulesFullPath = APPROOT.$sExtensionsDir.DIRECTORY_SEPARATOR; - $aManualInstallModules = array_filter($aModules, - static function ($v, $k) use ($sManualInstallModulesFullPath) { - if (!isset($v['root_dir'])) // avoid index undefined for the _Root_ entry - { - return false; - } - // calling realpath to avoid problems with dir separator (almost everywhere we are adding '/' instead of DIRECTORY_SEPARATOR) - $return = utils::RealPath($v['root_dir'], $sManualInstallModulesFullPath); - if ($return === false) - { - return false; - } - - return true; - }, - ARRAY_FILTER_USE_BOTH); - - if (empty($aManualInstallModules)) + //simple test in order to prevent install iTop pro with module in extension folder + $aFileInfo = scandir($sManualInstallModulesFullPath); + foreach ($aFileInfo as $sFolder) { - return ''; + if ($sFolder != "." && $sFolder != ".." && is_dir($sManualInstallModulesFullPath.$sFolder) === true) + { + return "Some modules are present in the '$sExtensionsDir' directory, this is not allowed when using ".ITOP_APPLICATION; + } } - - return "Some modules are present in the '$sExtensionsDir' directory, this is not allowed when using ".ITOP_APPLICATION; + return ''; } /** @@ -2067,7 +2054,7 @@ JS */ class SetupInfo { - static $aSelectedModules = array(); + public static $aSelectedModules = array(); /** * Called by the setup process to initializes the list of selected modules. Do not call this method @@ -2075,7 +2062,7 @@ class SetupInfo * @param hash $aModules * @return void */ - static function SetSelectedModules($aModules) + public static function SetSelectedModules($aModules) { self::$aSelectedModules = $aModules; } @@ -2086,7 +2073,7 @@ class SetupInfo * @param string $sModuleId The identifier of the module (without the version number. Example: itop-config-mgmt) * @return boolean True if the module is already selected, false otherwise */ - static function ModuleIsSelected($sModuleId) + public static function ModuleIsSelected($sModuleId) { return (array_key_exists($sModuleId, self::$aSelectedModules)); } diff --git a/sources/application/TwigBase/Controller/Controller.php b/sources/application/TwigBase/Controller/Controller.php index 6fe3723cf..7ff7eb49d 100644 --- a/sources/application/TwigBase/Controller/Controller.php +++ b/sources/application/TwigBase/Controller/Controller.php @@ -23,6 +23,7 @@ use ajax_page; use ApplicationMenu; use Combodo\iTop\Application\TwigBase\Twig\TwigHelper; use Dict; +use ErrorPage; use Exception; use IssueLog; use iTopWebPage; diff --git a/sources/application/TwigBase/Twig/TwigHelper.php b/sources/application/TwigBase/Twig/TwigHelper.php index ad1bcfb39..9637d54d7 100644 --- a/sources/application/TwigBase/Twig/TwigHelper.php +++ b/sources/application/TwigBase/Twig/TwigHelper.php @@ -22,6 +22,10 @@ class TwigHelper $oLoader = new Twig_Loader_Filesystem($sViewPath); $oTwig = new Twig_Environment($oLoader); Extension::RegisterTwigExtensions($oTwig); + $sLocalPath = utils::LocalPath($sViewPath); + $sLocalPath = str_replace('env-'.utils::GetCurrentEnvironment(), 'twig', $sLocalPath); + $sCachePath = utils::GetCachePath().$sLocalPath; + $oTwig->setCache($sCachePath); return $oTwig; } diff --git a/sources/renderer/bootstrap/fieldrenderer/bsfileuploadfieldrenderer.class.inc.php b/sources/renderer/bootstrap/fieldrenderer/bsfileuploadfieldrenderer.class.inc.php index 642d6b375..299bba55c 100644 --- a/sources/renderer/bootstrap/fieldrenderer/bsfileuploadfieldrenderer.class.inc.php +++ b/sources/renderer/bootstrap/fieldrenderer/bsfileuploadfieldrenderer.class.inc.php @@ -143,7 +143,7 @@ var oTable_{$this->oField->GetGlobalId()}; var buildTable_{$this->oField->GetGlobalId()} = function() { oTable_{$this->oField->GetGlobalId()} = $("table#$sAttachmentTableId").DataTable( { - "dom": "t", + "dom": "tp", "order": [[3, "asc"]], "columnDefs": [ $sDeleteColumnDef diff --git a/test/application/UtilsTest.php b/test/application/UtilsTest.php index 9fe2bd0f3..be2819237 100644 --- a/test/application/UtilsTest.php +++ b/test/application/UtilsTest.php @@ -103,6 +103,48 @@ class UtilsTest extends \Combodo\iTop\Test\UnitTest\ItopTestCase ]; } + /** + * @dataProvider LocalPathProvider + * + * @param $sAbsolutePath + * @param $expected + */ + public function testLocalPath($sAbsolutePath, $expected) + { + $this->assertSame($expected, utils::LocalPath($sAbsolutePath)); + + } + + public function LocalPathProvider() + { + return array( + 'index.php' => array( + 'sAbsolutePath' => APPROOT.'index.php', + 'expected' => 'index.php', + ), + 'non existing' => array( + 'sAbsolutePath' => APPROOT.'nonexisting/nonexisting', + 'expected' => false, + ), + 'outside' => array( + 'sAbsolutePath' => '/tmp', + 'expected' => false, + ), + 'application/cmdbabstract.class.inc.php' => array( + 'sAbsolutePath' => APPROOT.'application/cmdbabstract.class.inc.php', + 'expected' => 'application/cmdbabstract.class.inc.php', + ), + 'dir' => array( + 'sAbsolutePath' => APPROOT.'application/.', + 'expected' => 'application', + ), + 'root' => array( + 'sAbsolutePath' => APPROOT.'.', + 'expected' => '', + ), + ); + } + /** * @dataProvider appRootUrlProvider * @covers utils::GetAppRootUrl diff --git a/test/core/CMDBSourceTest.php b/test/core/CMDBSourceTest.php index 6901da313..010d825ce 100644 --- a/test/core/CMDBSourceTest.php +++ b/test/core/CMDBSourceTest.php @@ -101,6 +101,11 @@ class CMDBSourceTest extends ItopTestCase "ENUM('1','2','3') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '1'", "enum('1','2','3') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '1'", ), + 'ENUM with values containing parenthesis' => array( + true, + "ENUM('CSP A','CSP M','NA','OEM(ROC)','OPEN(VL)','RETAIL (Boite)') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", + "enum('CSP A','CSP M','NA','OEM(ROC)','OPEN(VL)','RETAIL (Boite)') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", + ), ); } } diff --git a/test/core/DBSearchIntersectTest.php b/test/core/DBSearchIntersectTest.php index dd0b3e149..96379b2e4 100644 --- a/test/core/DBSearchIntersectTest.php +++ b/test/core/DBSearchIntersectTest.php @@ -3,7 +3,7 @@ namespace Combodo\iTop\Test\UnitTest\Core; use CMDBSource; -use Combodo\iTop\Test\UnitTest\ItopDataTestCase; +use Combodo\iTop\Test\UnitTest\ItopTestCase; use DBSearch; /** @@ -15,9 +15,8 @@ use DBSearch; * @preserveGlobalState disabled * @backupGlobals disabled */ -class DBSearchIntersectTest extends ItopDataTestCase +class DBSearchIntersectTest extends ItopTestCase { - const USE_TRANSACTION = false; protected function setUp() { @@ -58,7 +57,6 @@ class DBSearchIntersectTest extends ItopDataTestCase 'alias' => "ApplicationSolution", 'result' => "SELECT `ApplicationSolution` FROM ApplicationSolution AS `ApplicationSolution` WHERE (`ApplicationSolution`.`org_id` = 3) UNION SELECT `BusinessProcess` FROM BusinessProcess AS `BusinessProcess` WHERE (`BusinessProcess`.`org_id` = 3)"); - // Bug to fix // $aTests['Test union #2902'] = array( // 'left' => "SELECT `L-1` FROM ServiceFamily AS `L-1` WHERE 1", @@ -456,4 +454,53 @@ class DBSearchIntersectTest extends ItopDataTestCase return $aQueries; } + + /** + * Bug #2970 + * @throws \CoreException + * @throws \CoreWarning + * @throws \OQLException + */ + public function testFilterOnJoin() + { + $sReq1 = "SELECT `L-1` FROM Organization AS `L-1` WHERE (`L-1`.`id` = :current_contact->org_id)"; + $sReq2 = "SELECT `L-1-1` FROM CustomerContract AS `L-1-1` JOIN Organization AS `O` ON `L-1-1`.org_id = `O`.id WHERE (((`L-1-1`.`status` = 'active') OR (`L-1-1`.`status` = 'standby')) AND (`O`.`id` = :current_contact->org_id))"; + + $oFilter1 = DBSearch::FromOQL($sReq1); + $oFilter2 = DBSearch::FromOQL($sReq2); + $aRealiasingMap = array(); + $oFilter1 = $oFilter1->Join($oFilter2, + DBSearch::JOIN_REFERENCED_BY, + 'org_id', + TREE_OPERATOR_EQUALS, $aRealiasingMap); + + $sRes1 = $oFilter1->ToOQL(); + $this->debug($sRes1); + + foreach($oFilter1->GetCriteria_ReferencedBy() as $sForeignClass => $aReferences) + { + foreach ($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator) + { + foreach ($aFiltersByOperator as $iOperatorCode => $aFilters) + { + foreach ($aFilters as $index => $oForeignFilter) + { + $this->debug($oForeignFilter->ToOQL()); + } + } + } + } + + $this->assertFalse(strpos($sRes1, '`O`.')); + + $sReq3 = "SELECT `CustomerContract` FROM CustomerContract AS `CustomerContract` WHERE (`CustomerContract`.`org_id` IN ('2'))"; + $oFilter3 = DBSearch::FromOQL($sReq3); + + $oFilter1 = $oFilter1->Filter('L-1-1', $oFilter3); + + $sRes1 = $oFilter1->ToOQL(); + $this->debug($sRes1); + + $this->assertFalse(strpos($sRes1, '`O`.')); + } } diff --git a/test/core/WeeklyScheduledProcessMockConfig.php b/test/core/WeeklyScheduledProcessMockConfig.php new file mode 100644 index 000000000..236f83797 --- /dev/null +++ b/test/core/WeeklyScheduledProcessMockConfig.php @@ -0,0 +1,36 @@ +oConfig = new Config(); + $this->oConfig->SetModuleSetting(self::MODULE_NAME, self::MODULE_SETTING_ENABLED, $bEnabledValue); + $this->oConfig->SetModuleSetting(self::MODULE_NAME, self::MODULE_SETTING_TIME, $sTimeValue); + $this->oConfig->SetModuleSetting(self::MODULE_NAME, self::MODULE_SETTING_WEEKDAYS, 'monday, tuesday, wednesday, thursday, friday'); + } + + protected function GetModuleName() + { + return self::MODULE_NAME; + } + + protected function GetDefaultModuleSettingTime() + { + return null; // config mock injected in the constructor + } + + public function Process($iUnixTimeLimit) + { + // nothing to do here (not tested) + } +} \ No newline at end of file diff --git a/test/core/WeeklyScheduledProcessTest.php b/test/core/WeeklyScheduledProcessTest.php new file mode 100644 index 000000000..ffd879b64 --- /dev/null +++ b/test/core/WeeklyScheduledProcessTest.php @@ -0,0 +1,67 @@ +getOConfig()->Get('timezone'); + $timezone = new \DateTimeZone($sItopTimeZone); + $oExpectedDateTime = new DateTime($sExpectedTime, $timezone); + + $this->assertEquals($oExpectedDateTime, $oWeeklyImpl->GetNextOccurrence($sCurrentTime)); + } + + public function GetNextOccurrenceProvider() + { + return array( + 'Disabled process' => array(false, 'now', null, '3000-01-01'), + 'working day same day, prog noon and current before noon' => array(true, '2020-05-11 11:00', '12:00', '2020-05-11 12:00'), + 'working day same day, prog noon and current is noon' => array(true, '2020-05-11 12:00', '12:00', '2020-05-12 12:00'), + 'working day same day, prog noon and current after noon' => array(true, '2020-05-11 13:00', '12:00', '2020-05-12 12:00'), + 'saturday, prog noon and current before noon' => array(true, '2020-05-09 11:00', '12:00', '2020-05-11 12:00'), + 'saturday, prog noon and current is noon' => array(true, '2020-05-09 12:00', '12:00', '2020-05-11 12:00'), + 'saturday, prog noon and current after noon' => array(true, '2020-05-09 13:00', '12:00', '2020-05-11 12:00'), + 'sunday, prog noon and current before noon' => array(true, '2020-05-10 11:00', '12:00', '2020-05-11 12:00'), + 'sunday, prog noon and current is noon' => array(true, '2020-05-10 12:00', '12:00', '2020-05-11 12:00'), + 'sunday, prog noon and current after noon' => array(true, '2020-05-10 13:00', '12:00', '2020-05-11 12:00'), + 'working day, day before, prog noon and current before midnight' => array(true, '2020-05-11 23:59', '00:00', '2020-05-12 00:00'), + 'working day same day, prog noon and current is midnight' => array(true, '2020-05-12 00:00', '00:00', '2020-05-13 00:00'), + 'working day same day, prog noon and current after midnight' => array(true, '2020-05-12 00:01', '00:00', '2020-05-13 00:00'), + ); + } +} +