Merge remote-tracking branch 'origin/support/2.7' into develop

# Conflicts:
#	core/config.class.inc.php
#	datamodels/2.x/itop-core-update/view/SelectUpdateFile.html.twig
#	datamodels/2.x/itop-core-update/view/SelectUpdateFile.ready.js.twig
#	setup/setuputils.class.inc.php
#	test/setup/SetupUtilsTest.php
This commit is contained in:
bruno-ds
2021-03-02 14:34:19 +01:00
21 changed files with 271 additions and 50 deletions

View File

@@ -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);

View File

@@ -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;

View File

@@ -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)

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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.',

View File

@@ -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());

View File

@@ -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 <a href="%1$s">setup</a> to update the application.<br />Some modified files were detected, a partial update cannot be executed.',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Ready to start',

View File

@@ -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 <a href="%1$s">d\'installation</a> pour mettre à jour l\'application.<br />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',

View File

@@ -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)
{

View File

@@ -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} %}

View File

@@ -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");
}
}

View File

@@ -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(

View File

@@ -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;
}
/**

View File

@@ -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',

View File

@@ -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éé !',

View File

@@ -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;

View File

@@ -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;
}
/**

View File

@@ -0,0 +1,135 @@
<?php
/**
* Copyright (C) 2013-2020 Combodo SARL
*
* 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
*/
use Combodo\iTop\Test\UnitTest\ItopTestCase;
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
* @covers utils
*/
class privUITransactionFileTest extends ItopTestCase
{
public function setUp()
{
parent::setUp();
require_once(APPROOT.'/application/startup.inc.php');
// require_once(APPROOT.'application/utils.inc.php');
}
/**
* @dataProvider cleanupOldTransactionsProvider
*/
public function testCleanupOldTransactions($iCleanableCreated, $iPreservableCreated, $sCleanablePrefix, $sPreservablePrefix)
{
MetaModel::GetConfig()->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);
}
}

View File

@@ -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",
),
);
}
}

View File

@@ -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" => [