diff --git a/application/dashlet.class.inc.php b/application/dashlet.class.inc.php
index 307f885bb..d548e7a91 100644
--- a/application/dashlet.class.inc.php
+++ b/application/dashlet.class.inc.php
@@ -438,17 +438,21 @@ EOF
$sAttType = $aTargetAttCodes[$sTargetAttCode];
$sExtFieldAttCode = $sTargetAttCode;
}
- if (is_a($sAttType, 'AttributeLinkedSet', true))
- {
- continue;
- }
- if (is_a($sAttType, 'AttributeFriendlyName', true))
- {
- continue;
- }
- if (is_a($sAttType, 'AttributeOneWayPassword', true))
- {
- continue;
+
+ $aForbidenAttType = [
+ 'AttributeLinkedSet',
+ 'AttributeFriendlyName',
+
+ 'iAttributeNoGroupBy', //we cannot only use iAttributeNoGroupBy since this method is also used by the designer who do not have access to the classes' PHP reflection API. So the known classes has to be listed altogether
+ 'AttributeOneWayPassword',
+ 'AttributeEncryptedString',
+ 'AttributePassword',
+ ];
+ foreach ($aForbidenAttType as $sForbidenAttType) {
+ if (is_a($sAttType, $sForbidenAttType, true))
+ {
+ continue 2;
+ }
}
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php
index 3ab5797b3..49e28be05 100644
--- a/application/displayblock.class.inc.php
+++ b/application/displayblock.class.inc.php
@@ -636,8 +636,21 @@ HTML;
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, $aOrderBy, $aQueryParams);
}
$this->m_oSet->SetShowObsoleteData($this->m_bShowObsoleteData);
- switch($this->m_sStyle)
- {
+
+ switch($this->m_sStyle) {
+ case 'list_search':
+ case 'list':
+ break;
+ default:
+ // N°3473: except for 'list_search' and 'list' (which have more granularity, see the other switch below),
+ // refuse to render if the user is not allowed to see the class.
+ if (! UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) {
+ $sHtml .= $oPage->GetP(Dict::Format('UI:Error:ReadNotAllowedOn_Class', $this->m_oSet->GetClass()));
+ return $sHtml;
+ }
+ }
+
+ switch ($this->m_sStyle) {
case 'count':
$oBlock = $this->RenderCount($aExtraParams);
break;
diff --git a/application/transaction.class.inc.php b/application/transaction.class.inc.php
index eac5ef6c9..8e3afb2ee 100644
--- a/application/transaction.class.inc.php
+++ b/application/transaction.class.inc.php
@@ -297,11 +297,19 @@ class privUITransactionFile
* Cleanup old transactions which have been pending since more than 24 hours
* Use filemtime instead of filectime since filectime may be affected by operations on the directory (like changing the access rights)
*/
- protected static function CleanupOldTransactions()
+ protected static function CleanupOldTransactions($sTransactionDir = null)
{
- $iLimit = time() - 24*3600;
+ $iThreshold = (int) MetaModel::GetConfig()->Get('transactions_gc_threshold');
+ $iThreshold = min(100, $iThreshold);
+ $iThreshold = max(1, $iThreshold);
+ if ((100 != $iThreshold) && (rand(1, 100) > $iThreshold)) {
+ return;
+ }
+
clearstatcache();
- $aTransactions = glob(APPROOT.'data/transactions/*-*');
+ $iLimit = time() - 24*3600;
+ $sPattern = $sTransactionDir ? "$sTransactionDir/*" : APPROOT.'data/transactions/*';
+ $aTransactions = glob($sPattern);
foreach($aTransactions as $sFileName)
{
if (filemtime($sFileName) < $iLimit)
diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php
index 18a251542..565580709 100644
--- a/core/attributedef.class.inc.php
+++ b/core/attributedef.class.inc.php
@@ -101,6 +101,14 @@ define('LINKSET_EDITMODE_ACTIONS', 2); // Show the usual 'Actions' popup menu
define('LINKSET_EDITMODE_INPLACE', 3); // The "linked" objects can be created/modified/deleted in place
define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/removed in place
+/**
+ * Attributes implementing this interface won't be accepted as `group by` field
+ * @since 2.7.4 N°3473
+ */
+interface iAttributeNoGroupBy
+{
+ //no method, just a contract on implement
+}
/**
* Attribute definition API, implemented in and many flavours (Int, String, Enum, etc.)
@@ -3794,7 +3802,7 @@ class AttributeFinalClass extends AttributeString
*
* @package iTopORM
*/
-class AttributePassword extends AttributeString
+class AttributePassword extends AttributeString implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
@@ -3871,7 +3879,7 @@ class AttributePassword extends AttributeString
*
* @package iTopORM
*/
-class AttributeEncryptedString extends AttributeString
+class AttributeEncryptedString extends AttributeString implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
@@ -9313,7 +9321,7 @@ class AttributeSubItem extends AttributeDefinition
/**
* One way encrypted (hashed) password
*/
-class AttributeOneWayPassword extends AttributeDefinition
+class AttributeOneWayPassword extends AttributeDefinition implements iAttributeNoGroupBy
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php
index 89f6cbaa8..b5bc51f5e 100644
--- a/core/cmdbsource.class.inc.php
+++ b/core/cmdbsource.class.inc.php
@@ -1082,18 +1082,23 @@ class CMDBSource
}
/**
- * There may have some differences between DB : for example in MySQL 5.7 we have "INT", while in MariaDB >= 10.2 you get "int DEFAULT
- * 'NULL'"
+ * There may have some differences between DB ! For example in :
+ * * MySQL 5.7 we have `INT`
+ * * MariaDB >= 10.2 you get `int DEFAULT 'NULL'`
*
- * We still do a case sensitive comparison for enum values !
+ * We still need to do a case sensitive comparison for enum values !
*
* A better solution would be to generate SQL field definitions ({@link GetFieldSpec} method) based on the DB used... But for
* now (N°2490 / SF #1756 / PR #91) we did implement this simpler solution
*
- * @param string $sItopGeneratedFieldType
+ * @see GetFieldDataTypeAndOptions extracts all info from the SQL field definition
+ *
* @param string $sDbFieldType
*
+ * @param string $sItopGeneratedFieldType
+ *
* @return bool true if same type and options (case sensitive comparison only for type options), false otherwise
+ *
* @throws \CoreException
* @since 2.7.0 N°2490
*/
@@ -1142,12 +1147,15 @@ class CMDBSource
}
/**
- * @param string $sCompleteFieldType sql field type, for example 'VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0'
+ * @see \self::GetEnumOptions() specific processing for ENUM fields
+ *
+ * @param string $sCompleteFieldType sql field type, for example `VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0`
*
* @return string[] consisting of 3 items :
- * 1. data type : for example 'VARCHAR'
- * 2. type value : for example '255'
- * 3. other options : for example ' CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0'
+ * 1. data type : for example `VARCHAR`
+ * 2. type value : for example `255`
+ * 3. other options : for example `CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0`
+ *
* @throws \CoreException
*/
private static function GetFieldDataTypeAndOptions($sCompleteFieldType)
@@ -1157,7 +1165,12 @@ class CMDBSource
$sDataType = isset($aMatches[1]) ? $aMatches[1] : '';
if (strcasecmp($sDataType, 'ENUM') === 0){
- return self::GetEnumOptions($sDataType, $sCompleteFieldType);
+ try{
+ return self::GetEnumOptions($sDataType, $sCompleteFieldType);
+ }catch(CoreException $e){
+ //do nothing ; especially do not block setup.
+ IssueLog::Warning("enum was not parsed properly: $sCompleteFieldType. it should not happen during setup.");
+ }
}
$sTypeOptions = isset($aMatches[2]) ? $aMatches[3] : '';
@@ -1167,18 +1180,17 @@ class CMDBSource
}
/**
- * @since 2.7.4 N°3065
- * Handle ENUM options
- *
- * @param $sDataType
- * @param $sCompleteFieldType
- * Example: ENUM('CSP A','CSP (aaaa) M','NA','OEM(ROC)','OPEN(VL)','RETAIL (Boite)') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
+ * @param string $sDataType for example `ENUM`
+ * @param string $sCompleteFieldType Example:
+ * `ENUM('CSP A','CSP (aaaa) M','NA','OEM(ROC)','OPEN(VL)','RETAIL (Boite)') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`
*
* @return string[] consisting of 3 items :
* 1. data type : ENUM or enum here
* 2. type value : in-between EUM parenthesis
* 3. other options : for example ' CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT 0'
* @throws \CoreException
+ * @since 2.7.4 N°3065 specific processing for enum fields : fix no alter table when enum values containing parenthesis
+ * Handle ENUM options
*/
private static function GetEnumOptions($sDataType, $sCompleteFieldType)
{
diff --git a/core/config.class.inc.php b/core/config.class.inc.php
index 2c5983d18..712a068d0 100644
--- a/core/config.class.inc.php
+++ b/core/config.class.inc.php
@@ -1051,6 +1051,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
+ 'transactions_gc_threshold' => [
+ 'type' => 'integer',
+ 'description' => 'probability in percent for the garbage collector to be triggered (100 mean always)',
+ 'default' => 10, // added in itop 2.7.4, before the GC was always called
+ 'value' => '',
+ 'source_of_value' => '',
+ 'show_in_conf_sample' => false,
+ ],
'log_transactions' => [
'type' => 'bool',
'description' => 'Whether or not to enable the debug log for the transactions.',
diff --git a/core/dbsearch.class.php b/core/dbsearch.class.php
index 95b65fa16..ce7ff1b39 100644
--- a/core/dbsearch.class.php
+++ b/core/dbsearch.class.php
@@ -1186,6 +1186,30 @@ abstract class DBSearch
}
}
}
+
+ if (is_array($aGroupByExpr))
+ {
+ foreach($aGroupByExpr as $sAlias => $oGroupByExp)
+ {
+ /** @var \Expression $oGroupByExp */
+
+ $aFields = $oGroupByExp->ListRequiredFields();
+ foreach($aFields as $sFieldAlias)
+ {
+ $aMatches = array();
+ if (preg_match('/^([^.]+)\\.([^.]+)$/', $sFieldAlias, $aMatches))
+ {
+ $sFieldClass = $this->GetClassName($aMatches[1]);
+ $oAttDef = MetaModel::GetAttributeDef($sFieldClass, $aMatches[2]);
+ if ( $oAttDef instanceof iAttributeNoGroupBy)
+ {
+ throw new Exception("Grouping on '$sFieldClass' fields is not supported.");
+ }
+ }
+ }
+ }
+ }
+
$oSQLQuery = $oSearch->GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr, null, $aSelectExpr);
$oSQLQuery->SetSourceOQL($oSearch->ToOQL());
diff --git a/datamodels/2.x/itop-core-update/dictionaries/en.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/en.dict.itop-core-update.php
index 72288f7cf..8d3143cba 100644
--- a/datamodels/2.x/itop-core-update/dictionaries/en.dict.itop-core-update.php
+++ b/datamodels/2.x/itop-core-update/dictionaries/en.dict.itop-core-update.php
@@ -75,6 +75,7 @@ Dict::Add('EN US', 'English', 'English', array(
'iTopUpdate:UI:CanCoreUpdate:Yes' => 'Application can be updated',
'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s',
+ 'iTopUpdate:UI:CannotUpdateUseSetup' => 'You must use the setup to update the application.
Some modified files were detected, a partial update cannot be executed.',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Ready to start',
diff --git a/datamodels/2.x/itop-core-update/dictionaries/fr.dict.itop-core-update.php b/datamodels/2.x/itop-core-update/dictionaries/fr.dict.itop-core-update.php
index fde558df6..1287df141 100644
--- a/datamodels/2.x/itop-core-update/dictionaries/fr.dict.itop-core-update.php
+++ b/datamodels/2.x/itop-core-update/dictionaries/fr.dict.itop-core-update.php
@@ -75,6 +75,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'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',
+ 'iTopUpdate:UI:CannotUpdateUseSetup' => 'Vous devez utiliser la page d\'installation pour mettre à jour l\'application.
Des fichiers modifiés ont été détectés, une mise à jour partielle ne peut pas être effectuée.',
// 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 dc33efa7f..8064ea2b0 100644
--- a/datamodels/2.x/itop-core-update/src/Controller/AjaxController.php
+++ b/datamodels/2.x/itop-core-update/src/Controller/AjaxController.php
@@ -37,7 +37,8 @@ class AjaxController extends Controller
}
else
{
- $aParams['sMessage'] = Dict::Format("iTopUpdate:UI:CanCoreUpdate:{$sCanUpdateCore}", $sMessage);
+ $sLink = utils::GetAbsoluteUrlAppRoot().'setup/';
+ $aParams['sMessage'] = Dict::Format('iTopUpdate:UI:CannotUpdateUseSetup', $sLink);
}
} catch (FileNotExistException $e)
{
diff --git a/datamodels/2.x/itop-core-update/view/SelectUpdateFile.html.twig b/datamodels/2.x/itop-core-update/view/SelectUpdateFile.html.twig
index 241e08347..e822afb5c 100644
--- a/datamodels/2.x/itop-core-update/view/SelectUpdateFile.html.twig
+++ b/datamodels/2.x/itop-core-update/view/SelectUpdateFile.html.twig
@@ -39,7 +39,7 @@
{% EndUIFieldSet %}
- {% UIFieldSet Standard {'sLegend':'iTopUpdate:UI:SelectUpdateFile'|dict_s} %}
+ {% UIFieldSet Standard {'sLegend':'iTopUpdate:UI:SelectUpdateFile'|dict_s, 'sId':'form-update-outer'} %}
{% UIForm Standard {} %}
{% UIInput ForHidden {'sName':'operation', 'sValue':'ConfirmUpdate'} %}
{% UIInput ForHidden {'sName':'transaction_id', 'sValue':sTransactionId} %}
diff --git a/datamodels/2.x/itop-core-update/view/SelectUpdateFile.ready.js.twig b/datamodels/2.x/itop-core-update/view/SelectUpdateFile.ready.js.twig
index 9fe1ab866..8fe5dec7c 100644
--- a/datamodels/2.x/itop-core-update/view/SelectUpdateFile.ready.js.twig
+++ b/datamodels/2.x/itop-core-update/view/SelectUpdateFile.ready.js.twig
@@ -18,6 +18,9 @@ $.ajax({
if (data.bStatus) {
oRequirements.addClass("ibo-is-success");
} else {
+ $("#check-update").prop("disabled", true);
+ $("#file").prop("disabled", true);
+ $('#form-update-outer').slideUp(600);
oRequirements.addClass("ibo-is-failure");
}
}
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 da09aa9a2..cf9c15bdc 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
@@ -252,6 +252,7 @@ class ManageBrickController extends BrickController
$oExporter->SetObjectList($oSearch);
$oExporter->SetFormat($sFormat);
$oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE);
+ $oExporter->SetLocalizeOutput(true);
$oExporter->SetFields($sFields);
$aData = array(
diff --git a/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php b/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php
index 34f473ffa..4d94436f3 100644
--- a/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php
+++ b/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php
@@ -1064,7 +1064,7 @@ class ObjectFormManager extends FormManager
/**
* @inheritDoc
*/
- public function CheckTransaction($aData)
+ public function CheckTransaction(&$aData)
{
$isTransactionValid = \utils::IsTransactionValid($this->oForm->GetTransactionId(), false); //The transaction token is kept in order to preserve BC with ajax forms (the second call would fail if the token is deleted). (The GC will take care of cleaning the token for us later on)
if (!$isTransactionValid) {
@@ -1079,8 +1079,6 @@ class ObjectFormManager extends FormManager
];
$aData['valid'] = false;
}
-
- return $aData;
}
/**
diff --git a/dictionaries/en.dictionary.itop.ui.php b/dictionaries/en.dictionary.itop.ui.php
index 4ea4ad886..5551df50e 100644
--- a/dictionaries/en.dictionary.itop.ui.php
+++ b/dictionaries/en.dictionary.itop.ui.php
@@ -465,6 +465,7 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:Error:ObjectsAlreadyDeleted' => 'Error: objects have already been deleted!',
'UI:Error:BulkDeleteNotAllowedOn_Class' => 'You are not allowed to perform a bulk delete of objects of class %1$s',
'UI:Error:DeleteNotAllowedOn_Class' => 'You are not allowed to delete objects of class %1$s',
+ 'UI:Error:ReadNotAllowedOn_Class' => 'You are not allowed to view objects of class %1$s',
'UI:Error:BulkModifyNotAllowedOn_Class' => 'You are not allowed to perform a bulk update of objects of class %1$s',
'UI:Error:ObjectAlreadyCloned' => 'Error: the object has already been cloned!',
'UI:Error:ObjectAlreadyCreated' => 'Error: the object has already been created!',
@@ -473,7 +474,7 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:Error:InvalidDashboard' => 'Error: invalid dashboard',
'UI:Error:MaintenanceMode' => 'Application is currently in maintenance',
'UI:Error:MaintenanceTitle' => 'Maintenance',
- 'UI:Error:InvalidToken' => 'Error: the requested operation have already been performed (CSRF token not found)',
+ 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)',
'UI:GroupBy:Count' => 'Count',
'UI:GroupBy:Count+' => 'Number of elements',
diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php
index 0e73c7f11..6f7be9d14 100644
--- a/dictionaries/fr.dictionary.itop.ui.php
+++ b/dictionaries/fr.dictionary.itop.ui.php
@@ -445,7 +445,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'UI:Error:ObjectCannotBeUpdated' => 'Erreur: l\'objet ne peut pas être mis à jour.',
'UI:Error:ObjectsAlreadyDeleted' => 'Erreur: les objets ont déjà été supprimés !',
'UI:Error:BulkDeleteNotAllowedOn_Class' => 'Vous n\'êtes pas autorisé à faire une suppression massive sur les objets de type %1$s',
- 'UI:Error:DeleteNotAllowedOn_Class' => 'Vous n\'êtes pas autorisé supprimer des objets de type %1$s',
+ 'UI:Error:DeleteNotAllowedOn_Class' => 'Vous n\'êtes pas autorisé à supprimer des objets de type %1$s',
+ 'UI:Error:ReadNotAllowedOn_Class' => 'Vous n\'êtes pas autorisé à voir des objets de type %1$s',
'UI:Error:BulkModifyNotAllowedOn_Class' => 'Vous n\'êtes pas autorisé à faire une modification massive sur les objets de type %1$s',
'UI:Error:ObjectAlreadyCloned' => 'Erreur: l\'objet a déjà été dupliqué !',
'UI:Error:ObjectAlreadyCreated' => 'Erreur: l\'objet a déjà été créé !',
diff --git a/setup/setuputils.class.inc.php b/setup/setuputils.class.inc.php
index d637006f2..4864bbd0f 100644
--- a/setup/setuputils.class.inc.php
+++ b/setup/setuputils.class.inc.php
@@ -618,7 +618,7 @@ class SetupUtils
if (!is_file($sGraphvizPath) || !is_executable($sGraphvizPath)) {
//N°3412 avoid shell injection
$aResult = [];
- $aResult[] = new CheckResult(CheckResult::ERROR,
+ $aResult[] = new CheckResult(CheckResult::WARNING,
self::GetStringForJsonEncode("$sGraphvizPath could not be executed: Please make sure it is installed and in the path", 'Graphviz could not be executed')
);
return $aResult;
diff --git a/sources/Form/FormManager.php b/sources/Form/FormManager.php
index cb6e15f2d..feeb2359e 100644
--- a/sources/Form/FormManager.php
+++ b/sources/Form/FormManager.php
@@ -175,7 +175,7 @@ abstract class FormManager
),
);
- $aData = $this->CheckTransaction($aData);
+ $this->CheckTransaction($aData);
return $aData;
}
@@ -183,11 +183,9 @@ abstract class FormManager
/**
* @param array $aData
*
- * @return array
- *
* @since 2.7.4 3.0.0 N°3430
*/
- public function CheckTransaction($aData)
+ public function CheckTransaction(&$aData)
{
$isTransactionValid = \utils::IsTransactionValid($this->oForm->GetTransactionId(), false); //The transaction token is kept in order to preserve BC with ajax forms (the second call would fail if the token is deleted). (The GC will take care of cleaning the token for us later on)
if (!$isTransactionValid) {
@@ -196,8 +194,6 @@ abstract class FormManager
];
$aData['valid'] = false;
}
-
- return $aData;
}
/**
diff --git a/test/application/privUITransactionFileTest.php b/test/application/privUITransactionFileTest.php
new file mode 100644
index 000000000..1b27ebe86
--- /dev/null
+++ b/test/application/privUITransactionFileTest.php
@@ -0,0 +1,135 @@
+Set('transactions_gc_threshold', 100);
+
+ $iBaseLimit = time() - 24*3600; //24h
+
+ $sBaseDir = sys_get_temp_dir();
+ $sDir = "$sBaseDir/privUITransactionFileTest/cleanupOldTransactions";
+ if (is_dir($sDir)) {
+ $this->rm($sDir);
+ }
+ mkdir("$sDir", 0777, true);
+
+ for ($i = 0; $i < $iCleanableCreated; $i++) {
+ touch("$sDir/{$sCleanablePrefix}$i", $iBaseLimit - 10*60);
+ }
+ for ($i = 0; $i < $iPreservableCreated; $i++) {
+ touch("$sDir/{$sPreservablePrefix}$i", $iBaseLimit + 10*60);
+ }
+
+ $iCleanableCount = count(glob("$sDir/{$sCleanablePrefix}*"));
+ $iPreservableCount = count(glob("$sDir/{$sPreservablePrefix}*"));
+ $this->assertEquals($iCleanableCreated, $iCleanableCount);
+ $this->assertEquals($iPreservableCreated, $iPreservableCount);
+
+ $aArgs = [
+ 'sTransactionDir' => "$sDir",
+ ];
+ $oprivUITransactionFile = new privUITransactionFile();
+ $this->InvokeNonPublicMethod(get_class($oprivUITransactionFile), 'CleanupOldTransactions', $oprivUITransactionFile, $aArgs);
+
+ $iCleanableCount = count(glob("$sDir/{$sCleanablePrefix}*"));
+ $iPreservableCount = count(glob("$sDir/{$sPreservablePrefix}*"));
+ $this->assertEquals(0, $iCleanableCount);
+ $this->assertEquals($iPreservableCreated, $iPreservableCount);
+ }
+
+ public function cleanupOldTransactionsProvider()
+ {
+ $iBaseLimit = time() - 60 * 10; //ten minutes ago
+
+ $sBaseDir = sys_get_temp_dir();
+ $sDir = "$sBaseDir/privUITransactionFileTest/cleanupOldTransactions";
+
+ return [
+ 'linux - no content' => [
+ 'iCleanableCreated' => 0,
+ 'iPreservableCreated' => 0,
+ 'sCleanablePrefix' => 'cleanable-',
+ 'sPreservablePrefix' => 'preservable-',
+ ],
+ 'linux - cleanable content' => [
+ 'iCleanableCreated' => 2,
+ 'iPreservableCreated' => 0,
+ 'sCleanablePrefix' => 'cleanable-',
+ 'sPreservablePrefix' => 'preservable-',
+ ],
+ 'linux - preseved content' => [
+ 'iCleanableCreated' => 0,
+ 'iPreservableCreated' => 2,
+ 'sCleanablePrefix' => 'cleanable-',
+ 'sPreservablePrefix' => 'preservable-',
+ ],
+ 'win - no content' => [
+ 'iCleanableCreated' => 0,
+ 'iPreservableCreated' => 0,
+ 'sCleanablePrefix' => 'cle',
+ 'sPreservablePrefix' => 'pre',
+ ],
+ 'win - cleanable content' => [
+ 'iCleanableCreated' => 2,
+ 'iPreservableCreated' => 0,
+ 'sCleanablePrefix' => 'cle',
+ 'sPreservablePrefix' => 'pre',
+ ],
+ 'win - preseved content' => [
+ 'iCleanableCreated' => 0,
+ 'iPreservableCreated' => 2,
+ 'sCleanablePrefix' => 'cle',
+ 'sPreservablePrefix' => 'pre',
+ ],
+ ];
+ }
+
+ public function rm($sDir) {
+ $aFiles = array_diff(scandir($sDir), ['.','..']);
+ foreach ($aFiles as $sFile) {
+ if ((is_dir("$sDir/$sFile"))) {
+ $this->rm("$sDir/$sFile");
+ } else {
+ unlink("$sDir/$sFile");
+ }
+ }
+ return rmdir($sDir);
+ }
+}
diff --git a/test/core/CMDBSourceTest.php b/test/core/CMDBSourceTest.php
index 132d71292..229db18b5 100644
--- a/test/core/CMDBSourceTest.php
+++ b/test/core/CMDBSourceTest.php
@@ -21,6 +21,7 @@ class CMDBSourceTest extends ItopTestCase
{
protected function setUp()
{
+
parent::setUp();
require_once(APPROOT.'/core/cmdbsource.class.inc.php');
}
@@ -106,12 +107,17 @@ class CMDBSourceTest extends ItopTestCase
"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",
),
- //FIXME N°3065 before the fix this returns true :(
+ // N°3065 before the fix this returned true :(
'ENUM with different values, containing parenthesis' => array(
false,
"ENUM('value 1 (with parenthesis)','value 2') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",
"enum('value 1 (with parenthesis)','value 3') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",
),
+ 'ENUM with different values, containing parenthesis' => array(
+ false,
+ "ENUM('value 1 ) with parenthesis)','value 2') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",
+ "enum('value 1 (with parenthesis)','value 3') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",
+ ),
);
}
}
diff --git a/test/setup/SetupUtilsTest.php b/test/setup/SetupUtilsTest.php
index 085f95237..a4b04db85 100644
--- a/test/setup/SetupUtilsTest.php
+++ b/test/setup/SetupUtilsTest.php
@@ -58,7 +58,7 @@ class SetupUtilsTest extends ItopTestCase
return [
"bash injection" => [
"touch /tmp/toto",
- self::ERROR,
+ self::WARNING,
"could not be executed: Please make sure it is installed and in the path",
],
"command ok" => [