Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

N°7506 - Add method to check if a specific module is installed in iTop #39

Merged
3 changes: 2 additions & 1 deletion core/restclient.class.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ public function SetVersion($sVersion)
}


public function Get($sClass, $keySpec, $sOutputFields = '*')
public function Get($sClass, $keySpec, $sOutputFields = '*', $iLimit = 0)
{
$aOperation = array(
'operation' => 'core/get', // operation code
'class' => $sClass,
'key' => $keySpec,
'output_fields' => $sOutputFields, // list of fields to show in the results (* or a,b,c)
'limit' => $iLimit,
);

return self::ExecOperation($aOperation, $this->sVersion);
Expand Down
63 changes: 63 additions & 0 deletions core/utils.class.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ class Utils
* @since 1.3.0 N°6012
*/
static protected $oMockedDoPostRequestService;

/**
* @var string Keeps track of the latest date the datamodel has been installed/updated
* (in order to check which modules were installed with it)
*/
static protected $sLastInstallDate;

static public function SetProjectName($sProjectName)
{
Expand Down Expand Up @@ -665,6 +671,63 @@ public static function ComputeCurlOptions(array $aRawCurlOptions, int $iCurrentT

return $aCurlOptions;
}

/**
* Check if the given module is installed in iTop.
* Mind that this assumes the `ModuleInstallation` class is ordered by descending installation date
*
* @param string $sModuleId Name of the module to be found, optionally included version
Hipska marked this conversation as resolved.
Show resolved Hide resolved
* @param bool $bRequired Whether to throw exceptions when module not found
* @param RestClient|null $oClient
* @return bool True when the given module is installed, false otherwise
* @throws Exception When the module is required but could not be found
*/
public static function CheckModuleInstallation(string $sModuleId, bool $bRequired = false, RestClient $oClient = null): bool
{
if (!isset($oClient))
{
$oClient = new RestClient();
}

if (preg_match('/^([^\/]+)(?:\/([<>]?=?)(.+))?$/', $sModuleId, $aModuleMatches)) {
$sName = $aModuleMatches[1];
$sOperator = $aModuleMatches[2] ?? null ?: '>=';
$sExpectedVersion = $aModuleMatches[3] ?? null;
}

try {
if (!isset(static::$sLastInstallDate)) {
$aDatamodelResults = $oClient->Get('ModuleInstallation', ['name' => 'datamodel'], 'installed', 1);
if ($aDatamodelResults['code'] != 0 || empty($aDatamodelResults['objects'])){
throw new Exception($aDatamodelResults['message'], $aDatamodelResults['code']);
}
$aDatamodel = current($aDatamodelResults['objects']);
static::$sLastInstallDate = $aDatamodel['fields']['installed'];
}

$aResults = $oClient->Get('ModuleInstallation', ['name' => $sName, 'installed' => static::$sLastInstallDate], 'name,version', 1);
if ($aResults['code'] != 0 || empty($aResults['objects'])) {
throw new Exception($aResults['message'], $aResults['code']);
}
$aObject = current($aResults['objects']);
$sCurrentVersion = $aObject['fields']['version'];

if (isset($sExpectedVersion) && !version_compare($sCurrentVersion, $sExpectedVersion, $sOperator)) {
throw new Exception(sprintf('Version mismatch (%s %s %s)', $sCurrentVersion, $sOperator, $sExpectedVersion));
}

Utils::Log(LOG_DEBUG, sprintf('iTop module %s version %s is installed.', $aObject['fields']['name'], $sCurrentVersion));
} catch (Exception $e) {
$sMessage = sprintf('%s iTop module %s is considered as not installed due to: %s', $bRequired ? 'Required' : 'Optional', $sName, $e->getMessage());
if ($bRequired) {
throw new Exception($sMessage, 0, $e);
} else {
Utils::Log(LOG_INFO, $sMessage);
return false;
}
}
return true;
}
}

class UtilsLogger
Expand Down
29 changes: 29 additions & 0 deletions test/UtilsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

require_once(APPROOT.'core/utils.class.inc.php');
require_once(APPROOT.'core/parameters.class.inc.php');
require_once(APPROOT.'core/restclient.class.inc.php');

class UtilsTest extends TestCase
{
Expand Down Expand Up @@ -230,4 +231,32 @@ public function testDumpConfig(){
$sContent = file_get_contents($sXmlPath);
$this->assertEquals($sContent, Utils::DumpConfig());
}

public function testCheckModuleInstallation(){
$oRestClient = $this->createMock(\RestClient::class);

$oReflectionLastInstallDate = new \ReflectionProperty(Utils::class, 'sLastInstallDate');
$oReflectionLastInstallDate->setAccessible(true);
$oReflectionLastInstallDate->setValue(null,'0000-00-00 00:00:00');

$oRestClient->expects($this->exactly(4))
->method('Get')
->willReturnMap([
['ModuleInstallation', ['name' => 'itop-structure', 'installed' => '0000-00-00 00:00:00'], 'name,version', 1, [
'code' => 0,
'objects' => ['ModuleInstallation::0' => ['fields' => ['name' => 'itop-structure', 'version' => '0.0.0']]],
'message' => 'Found: 1',
]],
['ModuleInstallation', ['name' => 'fake-module', 'installed' => '0000-00-00 00:00:00'], 'name,version', 1, [
'code' => 0,
'objects' => null,
'message' => 'Found: 0',
]],
]);

$this->assertTrue(Utils::CheckModuleInstallation('itop-structure', false, $oRestClient));
$this->assertTrue(Utils::CheckModuleInstallation('itop-structure/0.0.0', false, $oRestClient));
$this->assertFalse(Utils::CheckModuleInstallation('itop-structure/1.2.3', false, $oRestClient));
Molkobain marked this conversation as resolved.
Show resolved Hide resolved
$this->assertFalse(Utils::CheckModuleInstallation('fake-module', false, $oRestClient));
}
}