N°4789 - Parse datamodel module.xxx.php files instead of interpreting them (#746)

* N°4789 - Parse datamodel module.xxx.php files instead of interpreting them - refactoring all in a dedicated service first

* N°4789 - fix broken setup + tests

* N°4789 - replace legacy eval by module file parsing

* N°4789 - handle constants and if conditional structures

* N°4789 - compute boolean expressions

* N°4789 - make autoselect and dependencies work as well

* cleanup

* N°4789 - fix BeforeWritingConfig calls during setup

* N°4789 - refactor and split in ModuleDiscoveryEvaluationService + handle ModuleInstallerAPI methods calls during setup

* N°4789 - PR review changes with Romain

* PR review + code cleanup + added usecases and test cover

* temp evaluation work

* replace eval by iTop custom evaluation classes

* move PhpParser/Evaluation classes in a specific namespave + composer dumpautoload

* fix broken setup

* fix broken setup

* complete Evaluators list + autoload

* cleanup useless testing resources

* cleanup + replace last eval call in VariableEvaluator

* fix few Evaluators code

* enhance nikic evaluators + test with/without nikic lib

* Evaluator fixes/enhancements + tests

* bump to nikic fork temporarly

* bump nikic-parser fork + use only nikic fork  evaluation + cleanup itop redondant evaluators

* review with Romain: use distinct whitelists in setup time/runtime + move ModuleFileParser internal logic into ModuleFileReader

* PhpExpressionEvaluator used via constructor and not as a service

* dumpautoload again after rebase
This commit is contained in:
odain-cbd
2025-09-09 17:54:18 +02:00
committed by GitHub
parent 2ee68ff819
commit 15103dc49f
51 changed files with 3199 additions and 1334 deletions

View File

@@ -0,0 +1,220 @@
<?php
namespace Combodo\iTop\Test\UnitTest\Setup\ModuleDiscovery;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use ModuleFileReader;
class ModuleFileReaderTest extends ItopDataTestCase
{
private string $sTempModuleFilePath;
protected function setUp(): void
{
parent::setUp();
$this->RequireOnceItopFile('setup/modulediscovery/ModuleFileReader.php');
}
public function testReadModuleFileInformationUnsafe()
{
$sModuleFilePath = __DIR__.'/resources/module.itop-full-itil.php';
$aRes = ModuleFileReader::GetInstance()->ReadModuleFileInformationUnsafe($sModuleFilePath);
$this->assertCount(3, $aRes);
$this->assertEquals($sModuleFilePath, $aRes[0]);
$this->assertEquals('itop-full-itil/3.3.0', $aRes[1]);
$this->assertIsArray($aRes[2]);
$this->assertArrayHasKey('label', $aRes[2]);
$this->assertEquals('Bridge - Request management ITIL + Incident management ITIL', $aRes[2]['label'] ?? null);
}
public static function ReadModuleFileConfigurationFileNameProvider()
{
$aUsecases=[];
foreach (glob(__DIR__.'/resources/*.php') as $sModuleFilePath){
if (false !== strpos($sModuleFilePath, "module.__MODULE__.php")){
continue;
}
$aUsecases[basename($sModuleFilePath)]=[$sModuleFilePath];
}
return $aUsecases;
}
/**
* @dataProvider ReadModuleFileConfigurationFileNameProvider
*/
public function testReadModuleFileConfigurationVsLegacyMethod(string $sModuleFilePath)
{
$_SERVER=[
'SERVER_NAME' => 'titi'
];
$aRes = ModuleFileReader::GetInstance()->ReadModuleFileInformation($sModuleFilePath);
$aExpected = ModuleFileReader::GetInstance()->ReadModuleFileInformationUnsafe($sModuleFilePath);
$this->assertEquals($aExpected, $aRes);
}
public function testReadModuleFileConfigurationParsingIssue()
{
$sModuleFilePath = __DIR__.'/resources/module.__MODULE__.php';
$this->expectException(\ModuleFileReaderException::class);
$this->expectExceptionMessage("Syntax error, unexpected T_CONSTANT_ENCAPSED_STRING, expecting ',' or ']' or ')' on line 31");
ModuleFileReader::GetInstance()->ReadModuleFileInformation($sModuleFilePath);
}
/**
* local tool function
*/
private function CallReadModuleFileConfiguration($sPHpCode)
{
$this->sTempModuleFilePath = tempnam(__DIR__, "test");
file_put_contents($this->sTempModuleFilePath, $sPHpCode);
try {
return ModuleFileReader::GetInstance()->ReadModuleFileInformation($this->sTempModuleFilePath);
}
finally {
@unlink($this->sTempModuleFilePath);
}
}
public function testReadModuleFileConfigurationCheckBasicStatementWithoutIf()
{
$sPHP = <<<PHP
<?php
\$a=1;
SetupWebPage::AddModule("a", "noif", ["c" => "d"]);
\$b=2;
PHP;
$val = $this->CallReadModuleFileConfiguration($sPHP);
$this->assertEquals([$this->sTempModuleFilePath, "noif", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val);
}
public function testReadModuleFileConfigurationCheckBasicStatement_IfConditionVerified()
{
$sPHP = <<<PHP
<?php
\$a=1;
if (true){
SetupWebPage::AddModule("a", "if", ["c" => "d"]);
} elseif (true){
SetupWebPage::AddModule("a", "elseif1", ["c" => "d"]);
} elseif (true){
SetupWebPage::AddModule("a", "elseif2", ["c" => "d"]);
} else {
SetupWebPage::AddModule("a", "else", ["c" => "d"]);
}
SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]);
\$b=2;
PHP;
$val = $this->CallReadModuleFileConfiguration($sPHP);
$this->assertEquals([$this->sTempModuleFilePath, "if", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val);
}
public function testReadModuleFileConfigurationCheckBasicStatement_IfNoConditionVerifiedAndNoElse()
{
$sPHP = <<<PHP
<?php
\$a=1;
if (false){
SetupWebPage::AddModule("a", "if", ["c" => "d"]);
} elseif (false){
SetupWebPage::AddModule("a", "elseif1", ["c" => "d"]);
} elseif (false){
SetupWebPage::AddModule("a", "elseif2", ["c" => "d"]);
}
SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]);
\$b=2;
PHP;
$val = $this->CallReadModuleFileConfiguration($sPHP);
$this->assertEquals([$this->sTempModuleFilePath, "outsideif", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val);
}
public function testReadModuleFileConfigurationCheckBasicStatement_ElseApplied()
{
$sPHP = <<<PHP
<?php
\$a=1;
if (false){
SetupWebPage::AddModule("a", "if", ["c" => "d"]);
} elseif (false){
SetupWebPage::AddModule("a", "elseif1", ["c" => "d"]);
} elseif (false){
SetupWebPage::AddModule("a", "elseif2", ["c" => "d"]);
} else {
SetupWebPage::AddModule("a", "else", ["c" => "d"]);
}
SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]);
\$b=2;
PHP;
$val = $this->CallReadModuleFileConfiguration($sPHP);
$this->assertEquals([$this->sTempModuleFilePath, "else", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val);
}
public function testReadModuleFileConfigurationCheckBasicStatement_FirstElseIfApplied()
{
$sPHP = <<<PHP
<?php
\$a=1;
if (false){
SetupWebPage::AddModule("a", "if", ["c" => "d"]);
} elseif (true){
SetupWebPage::AddModule("a", "elseif1", ["c" => "d"]);
} elseif (true){
SetupWebPage::AddModule("a", "elseif2", ["c" => "d"]);
} else {
SetupWebPage::AddModule("a", "else", ["c" => "d"]);
}
SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]);
\$b=2;
PHP;
$val = $this->CallReadModuleFileConfiguration($sPHP);
$this->assertEquals([$this->sTempModuleFilePath, "elseif1", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val);
}
public function testReadModuleFileConfigurationCheckBasicStatement_LastElseIfApplied()
{
$sPHP = <<<PHP
<?php
\$a=1;
if (false){
SetupWebPage::AddModule("a", "if", ["c" => "d"]);
} elseif (false){
SetupWebPage::AddModule("a", "elseif1", ["c" => "d"]);
} elseif (true){
SetupWebPage::AddModule("a", "elseif2", ["c" => "d"]);
} else {
SetupWebPage::AddModule("a", "else", ["c" => "d"]);
}
SetupWebPage::AddModule("a", "outsideif", ["c" => "d"]);
\$b=2;
PHP;
$val = $this->CallReadModuleFileConfiguration($sPHP);
$this->assertEquals([$this->sTempModuleFilePath, "elseif2", ["c" => "d", 'module_file_path' => $this->sTempModuleFilePath]], $val);
}
public function testGetAndCheckModuleInstallerClass()
{
$sModuleInstallerClass = "TicketsInstaller" . uniqid();
$sPHpCode = file_get_contents(__DIR__.'/resources/module.itop-tickets.php');
$sPHpCode = str_replace("TicketsInstaller", $sModuleInstallerClass, $sPHpCode);
$this->sTempModuleFilePath = tempnam(__DIR__, "test");
file_put_contents($this->sTempModuleFilePath, $sPHpCode);
var_dump($sPHpCode);
try {
$this->assertFalse(class_exists($sModuleInstallerClass));
$aModuleInfo = ModuleFileReader::GetInstance()->ReadModuleFileInformation($this->sTempModuleFilePath);
$this->assertFalse(class_exists($sModuleInstallerClass));
$this->assertEquals($sModuleInstallerClass, ModuleFileReader::GetInstance()->GetAndCheckModuleInstallerClass($aModuleInfo[2]));
}
finally {
@unlink($this->sTempModuleFilePath);
}
$this->assertTrue(class_exists($sModuleInstallerClass));
}
}

View File

@@ -0,0 +1,56 @@
<?php
/**
* @copyright Copyright (C) 2010-__YEAR__ Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
//
// iTop module definition file
//
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'__module_full_name__',
[
// Identification
//
'label' => '__module_label__',
'category' => '__module_category__',
// Setup
//
'dependencies' => [
__module_dependencies__
],
'mandatory' => __module_mandatory__,
'visible' => __module_visible__,
__module_setup_handler_class__
// Components
//
'datamodel' => [
'vendor/autoload.php',
__module_data_model__, // Contains the PHP code generated by the "compilation" of datamodel.__module_name__.xml
],
'webservice' => [],
'data.struct' => [
// add your 'structure' definition XML files here,
],
'data.sample' => [
// add your sample data XML files here,
],
// Documentation
//
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
'doc.more_information' => '', // hyperlink to more information, if any
// Default settings
//
'settings' => [
// Module specific settings go here, if any
],
]
);
__module_setup_handler__

View File

@@ -0,0 +1,80 @@
<?php
// Until we develop a mean to adress this within the setup, let's check that this instance
// of PHP has the php_ldap extension
//
if (function_exists('ldap_connect'))
{
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'authent-ldap/3.3.0',
array(
// Identification
//
'label' => 'User authentication based on LDAP',
'category' => 'authentication',
// Setup
//
'dependencies' => array(
),
'mandatory' => false,
'visible' => true,
'installer' => 'AuthentLDAPInstaller',
// Components
//
'datamodel' => array(
),
'data.struct' => array(
//'data.struct.authent-ldap.xml',
),
'data.sample' => array(
//'data.sample.authent-ldap.xml',
),
// Documentation
//
'doc.manual_setup' => '',
'doc.more_information' => '',
// Default settings
//
'settings' => array(
'uri' => 'ldap://localhost', // URI with host or IP address of your LDAP server
'default_user' => '', // User and password used for initial "Anonymous" bind to LDAP
'default_pwd' => '', // Leave both blank, if anonymous (read-only) bind is allowed
'base_dn' => 'dc=yourcompany,dc=com', // Base DN for User queries, adjust it to your LDAP schema
'user_query' => '(&(uid=%1$s)(inetuserstatus=ACTIVE))', // Query used to retrieve each user %1$s => iTop login
// For Windows AD use (samaccountname=%1$s) or (userprincipalname=%1$s)
// Some extra LDAP options, refer to: http://www.php.net/manual/en/function.ldap-set-option.php for more info
'options' => array(
LDAP_OPT_PROTOCOL_VERSION => 3,
LDAP_OPT_REFERRALS => 0,
),
'start_tls' => false,
'debug' => false,
'servers' => array(),
),
)
);
// Module installation handler
//
class AuthentLDAPInstaller extends ModuleInstallerAPI
{
public static function AfterDataLoad(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion)
{
}
public static function BeforeWritingConfig(Config $oConfiguration)
{
}
}
} // if (function_exists('ldap_connect'))

View File

@@ -0,0 +1,123 @@
<?php
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'combodo-email-synchro/3.8.2',
array(
// Identification
'label' => 'Tickets synchronization via e-mail',
'category' => 'business',
// Setup
'dependencies' => array(
'itop-profiles-itil/3.0.0',
),
'mandatory' => false,
'visible' => true,
'installer' => 'EmailSynchroInstaller',
// Components
'datamodel' => array(
'classes/autoload.php',
'model.combodo-email-synchro.php',
),
'dictionary' => array(),
'data.struct' => array(
),
'data.sample' => array(
),
// Documentation
'doc.manual_setup' => '', // No manual installation required
'doc.more_information' => '', // None
// Default settings
'settings' => array(
'notify_errors_to' => '', // mandatory to track errors not handled by the email processing module
'notify_errors_from' => '', // mandatory as well (can be set at the same value as notify_errors_to)
'debug' => false, // Set to true to turn on debugging
'periodicity' => 30, // interval at which to check for incoming emails (in s)
'retention_period' => 1, // number of hour we keep the replica
'body_parts_order' => 'text/html,text/plain', // Order in which to read the parts of the incoming emails
'pop3_auth_option' => 'USER',
'imap_options' => array('imap'),
'imap_open_options' => array(),
'maximum_email_size' => '10M', // Maximum allowed size for incoming emails
'big_files_dir' => '',
'exclude_attachment_types' => array('application/exe'), // Example: 'application/exe', 'application/x-winexe', 'application/msdos-windows'
// Lines to be removed just above the 'new part' in a reply-to message... add your own patterns below
'introductory-patterns' => array(
'/^le .+ a écrit :$/i', // Thunderbird French
'/^on .+ wrote:$/i', // Thunderbird English
'|^[0-9]{4}/[0-9]{1,2}/[0-9]{1,2} .+:$|', // Gmail style
),
// Some patterns which delimit the previous message in case of a Reply
// The "new" part of the message is the text before the pattern
// Add your own multi-line patterns (use \\R for a line break)
// These patterns depend on the mail client/server used... feel free to add your own discoveries to the list
'multiline-delimiter-patterns' => array(
'/\\RFrom: .+\\RSent: .+\\R/m', // Outlook English
'/\\R_+\\R/m', // A whole line made only of underscore characters
'/\\RDe : .+\\R\\R?Envoyé : /m', // Outlook French, HTML and rich text
'/\\RDe : .+\\RDate d\'envoi : .+\\R/m', // Outlook French, plain text
'/\\R-----Message d\'origine-----\\R/m',
),
'use_message_id_as_uid' => false, // Do NOT change this unless you known what you are doing!!
'images_minimum_size' => '100x20', // Images smaller that these dimensions will be ignored (signatures...)
'images_maximum_size' => '', // Images bigger that these dimensions will be resized before uploading into iTop
'recommended_max_allowed_packet' => 10*1024*1024, // MySQL parameter for attachments
),
)
);
if (!class_exists('EmailSynchroInstaller'))
{
// Module installation handler
//
class EmailSynchroInstaller extends ModuleInstallerAPI
{
/**
* Handler called after the creation/update of the database schema
*
* @param $oConfiguration Config The new configuration of the application
* @param $sPreviousVersion string Previous version number of the module (empty string in case of first install)
* @param $sCurrentVersion string Current version number of the module
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion)
{
// For each email sources, update email replicas by setting mailbox_path to source.mailbox where mailbox_path is null
SetupLog::Info("Updating email replicas to set their mailbox path.");
// Preparing mailboxes search
$oSearch = new DBObjectSearch('MailInboxBase');
// Retrieving definition of attribute to update
$sTableName = MetaModel::DBGetTable('EmailReplica');
$UidlAttDef = MetaModel::GetAttributeDef('EmailReplica', 'uidl');
$sUidlColName = $UidlAttDef->Get('sql');
$oMailboxAttDef = MetaModel::GetAttributeDef('EmailReplica', 'mailbox_path');
$sMailboxColName = $oMailboxAttDef->Get('sql');
$sFrienlynameAttCode = MetaModel::GetFriendlyNameAttributeCode('EmailReplica');
// Looping on inboxes to update
$oSet = new DBObjectSet($oSearch);
while ($oInbox = $oSet->Fetch())
{
$sUpdateQuery = "UPDATE $sTableName SET $sMailboxColName = " . CMDBSource::Quote($oInbox->Get('mailbox')) . " WHERE $sUidlColName LIKE " . CMDBSource::Quote($oInbox->Get('login') . '_%') . " AND $sMailboxColName IS NULL";
SetupLog::Info("Executing query: " . $sUpdateQuery);
$iRet = CMDBSource::Query($sUpdateQuery); // Throws an exception in case of error
SetupLog::Info("Updated $iRet rows.");
}
}
}
}

View File

@@ -0,0 +1,51 @@
<?php
//
// iTop module definition file
//
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-admin-delegation-profiles-bridge-for-combodo-email-synchro/1.0.0',
array(
// Identification
//
'label' => 'Profiles per admin fonction: Mail inboxes and messages',
'category' => 'Datamodel',
// Setup
//
'dependencies' => array(
'itop-admin-delegation-profiles/1.0.0',
'itop-admin-delegation-profiles/1.0.0 || combodo-email-synchro/3.7.2 || itop-oauth-client/2.7.7', // Optional dependency to silence the setup to not display a warning if the other module is not present
),
'mandatory' => false,
'visible' => false,
'auto_select' => 'SetupInfo::ModuleIsSelected("itop-admin-delegation-profiles") && SetupInfo::ModuleIsSelected("combodo-email-synchro") && SetupInfo::ModuleIsSelected("itop-oauth-client")',
// Components
//
'datamodel' => array(
'model.itop-admin-delegation-profiles-bridge-for-combodo-email-synchro.php'
),
'webservice' => array(
),
'data.struct' => array(
// add your 'structure' definition XML files here,
),
'data.sample' => array(
// add your sample data XML files here,
),
// Documentation
//
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
'doc.more_information' => '', // hyperlink to more information, if any
// Default settings
//
'settings' => array(
// Module specific settings go here, if any
),
)
);

View File

@@ -0,0 +1,52 @@
<?php
//
// iTop module definition file
//
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-admin-delegation-profiles/1.2.1',
array(
// Identification
//
'label' => 'Profiles per admin fonction',
'category' => 'Datamodel',
// Setup
//
'dependencies' => array(
'itop-config-mgmt/2.7.0' || 'itop-structure/3.0.0',
// itop-profiles-itil is here to ensure that the /itop_design/groups/group[@id="History"] alteration comes after those from that module.
// This allows to define the missing "History" group in iTop 2.7 / 3.0, while merging smoothly with iTop 3.1+
'itop-profiles-itil/2.7.0',
),
'mandatory' => false,
'visible' => true,
// Components
//
'datamodel' => array(
'model.itop-admin-delegation-profiles.php'
),
'webservice' => array(
),
'data.struct' => array(
// add your 'structure' definition XML files here,
),
'data.sample' => array(
// add your sample data XML files here,
),
// Documentation
//
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
'doc.more_information' => '', // hyperlink to more information, if any
// Default settings
//
'settings' => array(
// Module specific settings go here, if any
),
)
);

View File

@@ -0,0 +1,41 @@
<?php
//
// iTop module definition file
//
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-full-itil/3.3.0',
array(
// Identification
//
'label' => 'Bridge - Request management ITIL + Incident management ITIL',
'category' => 'business',
// Setup
//
'dependencies' => array(
'itop-request-mgmt-itil/2.3.0',
'itop-incident-mgmt-itil/2.3.0',
),
'mandatory' => false,
'visible' => false,
'auto_select' => 'SetupInfo::ModuleIsSelected("itop-request-mgmt-itil") && SetupInfo::ModuleIsSelected("itop-incident-mgmt-itil")',
// Components
//
'datamodel' => array(),
'webservice' => array(),
'data.struct' => array(// add your 'structure' definition XML files here,
),
'data.sample' => array(// add your sample data XML files here,
),
// Documentation
//
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
'doc.more_information' => '', // hyperlink to more information, if any
// Default settings
//
'settings' => array(// Module specific settings go here, if any
),
)
);

View File

@@ -0,0 +1,75 @@
<?php
/**
* Module itop-global-requests
*
* @copyright Copyright (C) 2012-2019 Combodo SARL
* @license https://www.combodo.com/documentation/combodo-software-license.html
*/
/** @noinspection PhpUnhandledExceptionInspection */
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-global-requests-mgmt/1.6.3',
array(
// Identification
//
'label' => 'iTop Global Requests Management',
'category' => 'business',
// Setup
//
'dependencies' => array(
'itop-portal-base/3.2.0',
),
'mandatory' => false,
'visible' => true,
'installer' => GlobalRequestInstaller::class,
// Components
//
'datamodel' => array(
'vendor/autoload.php',
),
'webservice' => array(),
'data.struct' => array(// add your 'structure' definition XML files here,
),
'data.sample' => array(// add your sample data XML files here,
),
// Documentation
//
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
'doc.more_information' => '', // hyperlink to more information, if any
// Default settings
//
'settings' => array(
'target_state' => 'new',
'bypass_profiles' => 'Administrator, Service Manager',
'reuse_previous_answers' => true,
),
)
);
class GlobalRequestInstaller extends ModuleInstallerAPI
{
/**
* Handler called before creating or upgrading the database schema
*
* @param $oConfiguration Config The new configuration of the application
* @param $sPreviousVersion string Previous version number of the module (empty string in case of first install)
* @param $sCurrentVersion string Current version number of the module
*
* @throws \CoreException
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion)
{
//code
}
}

View File

@@ -0,0 +1,107 @@
<?php
SetupWebPage::AddModule(
__FILE__,
'itop-tickets/3.3.0',
array(
// Identification
//
'label' => 'Tickets Management',
'category' => 'business',
// Setup
//
'dependencies' => array(
'itop-structure/2.7.1',
),
'mandatory' => false,
'visible' => true,
'installer' => 'TicketsInstaller',
// Components
//
'datamodel' => array(
'main.itop-tickets.php',
),
'data.struct' => array(
// 'data.struct.ta-actions.xml',
),
'data.sample' => array(
),
// Documentation
//
'doc.manual_setup' => '',
'doc.more_information' => '',
// Default settings
//
'settings' => array(
),
)
);
// Module installation handler
//
class TicketsInstaller extends ModuleInstallerAPI
{
public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion)
{
// Delete all Triggers corresponding to a no more valid class
CMDBObject::SetTrackInfo('Uninstallation');
$oSearch = new DBObjectSearch('TriggerOnObject');
$oSet = new DBObjectSet($oSearch);
while($oTrigger = $oSet->Fetch())
{
try
{
if (!MetaModel::IsValidClass($oTrigger->Get('target_class')))
{
$oTrigger->DBDelete();
}
}
catch(Exception $e)
{
utils::EnrichRaisedException($oTrigger, $e);
}
}
// It's not very clear if it make sense to test a particular version,
// as the loading mechanism checks object existence using reconc_keys
// and do not recreate them, nor update existing.
// Without test, new entries added to the data files, would be automatically loaded
if (($sPreviousVersion === '') ||
(version_compare($sPreviousVersion, $sCurrentVersion, '<')
&& version_compare($sPreviousVersion, '3.0.0', '<'))) {
$oDataLoader = new XMLDataLoader();
CMDBObject::SetTrackInfo("Initialization TicketsInstaller");
$oMyChange = CMDBObject::GetCurrentChange();
$sLang = null;
// - Try to get app. language from configuration fil (app. upgrade)
$sConfigFileName = APPCONF.'production/'.ITOP_CONFIG_FILE;
if (file_exists($sConfigFileName)) {
$oFileConfig = new Config($sConfigFileName);
if (is_object($oFileConfig)) {
$sLang = str_replace(' ', '_', strtolower($oFileConfig->GetDefaultLanguage()));
}
}
// - I still no language, get the default one
if (null === $sLang) {
$sLang = str_replace(' ', '_', strtolower($oConfiguration->GetDefaultLanguage()));
}
$sFileName = dirname(__FILE__)."/data/{$sLang}.data.itop-tickets.xml";
SetupLog::Info("Searching file: $sFileName");
if (!file_exists($sFileName)) {
$sFileName = dirname(__FILE__)."/data/en_us.data.itop-tickets.xml";
}
SetupLog::Info("Loading file: $sFileName");
$oDataLoader->StartSession($oMyChange);
$oDataLoader->LoadFile($sFileName, false, true);
$oDataLoader->EndSession();
}
}
}

View File

@@ -0,0 +1,79 @@
<?php
//
// iTop module definition file
//
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'servername-ticket/2.6.2',
array(
// Identification
//
'label' => 'I3S Datamodel tickets',
'category' => 'business',
// Setup
//
'dependencies' => array(
'itop-attachments/2.5.0',
),
'mandatory' => false,
'visible' => true,
// Components
//
'datamodel' => array(
'model.servername-datamodel-ticket.php',
'main.servername-datamodel-ticket.php'
),
'webservice' => array(
),
'data.struct' => array(
// add your 'structure' definition XML files here,
),
'data.sample' => array(
// add your sample data XML files here,
),
// Documentation
//
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
'doc.more_information' => '', // hyperlink to more information, if any
// Default settings
//
'settings' => array(
// url d'accès au répertoire des traces applicatives liées aux tickets
'traces_base_url' => 'file://'.$_SERVER['SERVER_NAME'].'/traces',
// répertoire des faqi liées aux tickets
'traces_base_dir_faqi' => '/data/i3s-gsit-tt/faqi',
// url d'accès au répertoire des faqi liées aux tickets
// ce serveur est-il le serveur de consolidation ?
'consolidation_server' => false,
// restriction des franchissements
'max_allowed_transitions' => array(
// Les noms des transitions sont visibles dans Outils d'admin => Modèle de données => Incident => Cycle de vie
// Les transitions qui ne sont pas présentes dans ce tableau sont considérées comme étant à 0
/* 'nom technique de la transition' => nombre maximal autorisé */
'ev_askinfo' => 0, // Demander des informations (au demandeur)
'ev_assign' => 0, // Assigner
'ev_cancel_by_user' => 0, // Annuler (par le demandeur)
'ev_cancel' => 0, // Annuler
'ev_close' => 0, // Clore
'ev_escalate' => 0, // Escalader
'ev_giveinfo' => 0, // Envoyer les informations
'ev_monitor' => 0, // Surveiller
'ev_pending' => 0, // En attente
'ev_reassign' => 0, // Ré-assigner
'ev_refuse_reject' => 0, // Refuser le rejet
'ev_refuse_solution' => 0, // Refuser la solution
'ev_reject' => 0, // Rejeter
'ev_resolve' => 0, // Marquer comme résolu
'ev_suspend' => 0, // Suspendre
'ev_terminate' => 0, // Solder
'ev_verify' => 0, // Accepter la solution / Confirmer la résolution
),
),
)
);

View File

@@ -0,0 +1,243 @@
<?php
namespace Combodo\iTop\Test\UnitTest\Sources\PhpParser\Evaluation;
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use ModuleFileReader;
class PhpExpressionEvaluatorTest extends ItopDataTestCase {
public static $STATIC_PROPERTY = 123;
private static $PRIVATE_STATIC_PROPERTY = 123;
private const PRIVATE_CONSTANT = 123;
protected function tearDown(): void
{
parent::tearDown();
}
public static function EvaluateExpressionProvider() {
return [
'Array: [1000 => "a"]' => ['sExpression' => '[1000 => "a"]'],
'Array: ["a"]' => ['sExpression' => '["a"]'],
'Array dict: ["a"=>"b"]' => ['sExpression' => '["a"=>"b"]'],
'ArrayDimFetch: $_SERVER[\'toto\']' => ['sExpression' => '$_SERVER[\'toto\']'],
'BinaryOperator: false|true' => [ 'sExpression' => 'false|true'],
'BinaryOperator: false||true' => [ 'sExpression' => 'false||true'],
'BinaryOperator: false&&true' => [ 'sExpression' => 'false&&true'],
'BinaryOperator: true&&true&&true&&false' => [ 'sExpression' => 'true && true && true && false'],
'BinaryOperator: false&true' => [ 'sExpression' => 'false&true'],
'BinaryOperator: ! true' => [ 'sExpression' => '! true'],
'BinaryOperator: 10 * 5' => [ 'sExpression' => '10 * 5'],
'BinaryOperator: 1 > 2' => [ 'sExpression' => '1 > 2'],
'BinaryOperator: 1 >= 1' => [ 'sExpression' => '1 >= 1'],
'BinaryOperator: 1 <= 1' => [ 'sExpression' => '1 <= 1'],
'BinaryOperator: PHP_VERSION_ID == PHP_VERSION_ID' => [ 'sExpression' => 'PHP_VERSION_ID == PHP_VERSION_ID'],
'BinaryOperator: PHP_VERSION_ID != PHP_VERSION_ID' => [ 'sExpression' => 'PHP_VERSION_ID != PHP_VERSION_ID'],
'BitwiseNot: ~3' => ['sExpression' => '~3'],
'BitwiseXor: 3^2' => ['sExpression' => '3^2'],
'BooleanAnd: true && false' => ['sExpression' => 'true && false'],
'Cast: (array)3' => ['sExpression' => '(array)3'],
'Cast: (bool)1' => ['sExpression' => '(bool)1'],
'Cast: (bool)0' => ['sExpression' => '(bool)0'],
'Cast: (double)3' => ['sExpression' => '(double)3'],
'Cast: (float)3' => ['sExpression' => '(float)3'],
'Cast: (int)3' => ['sExpression' => '(int)3'],
'Cast: (object)3' => ['sExpression' => '(object)3'],
'Cast: (string) $oEvaluationFakeClass' => ['sExpression' => '(string) $oEvaluationFakeClass', "toString"],
'ClassConstFetch: public existing constant' => [ 'sExpression' => 'SetupUtils::PHP_MIN_VERSION'],
'ClassConstFetch: unknown class:class' => [ 'sExpression' => 'GabuZomeuUnknownClass::class'],
'Coalesce: $oNullVar ?? 1' => ['sExpression' => '$oNullVar ?? 1', 1],
'Coalesce: $oNonNullVar ?? 1' => ['sExpression' => '$oNonNullVar ?? 1', 1],
'Coalesce: $_SERVER["toto"] ?? 1' => ['sExpression' => '$_SERVER["toto"] ?? 1', "titi"],
'Coalesce: $_SERVER["unknown_key"] ?? 1' => ['sExpression' => '$_SERVER["unknown_key"] ?? 1', 1],
'Coalesce: $oGlobalNonNullVar ?? 1' => ['sExpression' => '$oGlobalNonNullVar ?? 1', "a"],
'Coalesce: $oGlobalNullVar ?? 1' => ['sExpression' => '$oGlobalNullVar ?? 1', 1],
'Concat: "a"."b"' => ['sExpression' => '"a"."b"'],
'ConstFetch: false' => [ 'sExpression' => 'false'],
'ConstFetch: (false)' => [ 'sExpression' => 'false'],
'ConstFetch: true' => [ 'sExpression' => 'true'],
'ConstFetch: (true)' => [ 'sExpression' => 'true'],
'Equal: 1 == true' => [ 'sExpression' => '1 == true', true],
'Equal: 1 == false' => [ 'sExpression' => '1 == false', false],
'FuncCall: function_exists(\'ldap_connect\')' => [ 'sExpression' => 'function_exists(\'ldap_connect\')'],
'FuncCall: function_exists(\'gabuzomeushouldnotexist\')' => [ 'sExpression' => 'function_exists(\'gabuzomeushouldnotexist\')'],
'Identical: 1==="1"' => ['sExpression' => '1==="1"', false],
'Identical: "1"==="1"' => ['sExpression' => '"1"==="1"', true],
'Isset: isset($oNonNullVar)' => ['sExpression' => 'isset($oNonNullVar)', false],
'Isset: isset($oGlobalNonNullVar)' => ['sExpression' => 'isset($oGlobalNonNullVar)', true],
'Isset: isset($a, $_SERVER)' => ['sExpression' => 'isset($a, $_SERVER)', false],
'Isset: isset($_SERVER)' => ['sExpression' => 'isset($_SERVER)', true],
'Isset: isset($_SERVER, $a)' => ['sExpression' => 'isset($_SERVER, $a)', false],
'Isset: isset($oGlobalNonNullVar, $_SERVER)' => ['sExpression' => 'isset($oGlobalNonNullVar, $_SERVER)', true],
'MethodCall: $oEvaluationFakeClass->GetName()' => ['sExpression' => '$oEvaluationFakeClass->GetName()', "gabuzomeu"],
'MethodCall: $oEvaluationFakeClass->GetLongName("aa")' => ['sExpression' => '$oEvaluationFakeClass->GetLongName("aa")', "gabuzomeu_aa"],
'Mod: 3%2' => ['sExpression' => '3%2'],
'NullsafeMethodCall: $oNullVar?->GetName()' => ['sExpression' => '$oNullVar?->GetName()', null],
'NullsafeMethodCall: $oNullVar?->GetLongName("aa")' => ['sExpression' => '$oNullVar?->GetLongName("aa")', null],
'NullsafeMethodCall: $oEvaluationFakeClass?->GetName()' => ['sExpression' => '$oEvaluationFakeClass?->GetName()', "gabuzomeu"],
'NullsafeMethodCall: $oEvaluationFakeClass?->GetLongName("aa")' => ['sExpression' => '$oEvaluationFakeClass?->GetLongName("aa")', "gabuzomeu_aa"],
'NullsafePropertyFetch: $oNullVar?->b' => ['sExpression' => '$oNullVar?->b', null],
'NullsafePropertyFetch: $oEvaluationFakeClass?->iIsOk' => ['sExpression' => '$oEvaluationFakeClass?->iIsOk', "IsOkValue"],
'PropertyFetch: $oEvaluationFakeClass->iIsOk' => ['sExpression' => '$oEvaluationFakeClass->iIsOk', "IsOkValue"],
'StaticCall utils::GetItopVersionWikiSyntax()' => ['sExpression' => 'utils::GetItopVersionWikiSyntax()'],
'StaticProperty: public existing constant' => [ 'sExpression' => 'Combodo\iTop\Test\UnitTest\Sources\PhpParser\Evaluation\PhpExpressionEvaluatorTest::$STATIC_PROPERTY'],
'Ternary: (true) ? 1 : 2' => ['sExpression' => '(true) ? 1 : 2'],
'Ternary: (false) ? 1 : 2' => ['sExpression' => '(false) ? 1 : 2'],
'UnaryMinus: -1' => ['sExpression' => '-1'],
'UnaryPlus: +1' => ['sExpression' => '+1'],
'Variable: $_SERVER' => ['sExpression' => '$_SERVER', ['toto' => 'titi']],
'Variable: $oGlobalNonNullVar' => ['sExpression' => '$oGlobalNonNullVar', "a"],
'Variable: $oEvaluationFakeClass' => ['sExpression' => '$oEvaluationFakeClass', new EvaluationFakeClass()],
];
}
/**
* @dataProvider EvaluateExpressionProvider
*/
public function testEvaluateExpression($sExpression, $forced_expected="NOTPROVIDED")
{
global $oGlobalNonNullVar;
$oGlobalNonNullVar="a";
global $oGlobalNullVar;
$oGlobalNullVar=null;
$oNonNullVar="a";
$oNullVar=null;
$_SERVER=[
'toto' => 'titi',
];
global $oEvaluationFakeClass;
$oEvaluationFakeClass = new EvaluationFakeClass();
$oPhpExpressionEvaluator = new PhpExpressionEvaluator(ModuleFileReader::FUNC_CALL_WHITELIST, ModuleFileReader::STATIC_CALLWHITELIST);
$res = $oPhpExpressionEvaluator->ParseAndEvaluateExpression($sExpression);
if ($forced_expected === "NOTPROVIDED"){
$this->assertEquals($this->UnprotectedComputeExpression($sExpression), $res, $sExpression);
} else {
$this->assertEquals($forced_expected, $res, $sExpression);
}
}
public static function EvaluateExpressionThrowsExceptionProvider()
{
return [
'StaticProperty: private existing constant' => [
'sExpression' => 'Combodo\iTop\Test\UnitTest\Setup\ModuleDiscovery\PhpExpressionEvaluatorTest::$PRIVATE_STATIC_PROPERTY',
'forced_expected' => null,
],
'ClassConstFetch: unknown constant' => [ 'sExpression' => 'SetupUtils::UNKNOWN_CONSTANT'],
'ClassConstFetch: unknown class:constant' => [ 'sExpression' => 'GabuZomeuUnknownClass::UNKNOWN_CONSTANT'],
'ClassConstFetch: private existing constant' => [
'sExpression' => 'Combodo\iTop\Test\UnitTest\Setup\ModuleDiscovery\PhpExpressionEvaluatorTest::PRIVATE_CONSTANT',
'forced_expected' => null,
],
'Variable: $oNonNullVar' => ['sExpression' => '$oNonNullVar', null],
'FuncCall: function_exists(\'ldap_connect\')' => [ 'sExpression' => 'function_exists(\'ldap_connect\')'],
'StaticCall utils::GetItopVersionWikiSyntax()' => ['sExpression' => 'utils::GetItopVersionWikiSyntax()'],
];
}
/**
* @dataProvider EvaluateExpressionThrowsExceptionProvider
*/
public function testEvaluateExpressionThrowsException($sExpression)
{
global $oGlobalNonNullVar;
$oGlobalNonNullVar="a";
global $oGlobalNullVar;
$oGlobalNullVar=null;
$oNonNullVar="a";
$oNullVar=null;
$_SERVER=[
'toto' => 'titi',
];
global $oEvaluationFakeClass;
$oEvaluationFakeClass = new EvaluationFakeClass();
$this->expectException(\ModuleFileReaderException::class);
$oPhpExpressionEvaluator = new PhpExpressionEvaluator();
$oPhpExpressionEvaluator->ParseAndEvaluateExpression($sExpression);
}
/**
* @param string $sBooleanExpr
*
* @return mixed
* @throws \ModuleFileReaderException
*/
private function UnprotectedComputeExpression(string $sExpr) : mixed
{
try {
$bResult = null;
@eval('$bResult = '.$sExpr.';');
return $bResult;
} catch (\Throwable $t){
return null;
}
}
public static function ParseAndEvaluateBooleanExpression_AutoselectProvider()
{
$sSimpleCallToModuleIsSelected = "SetupInfo::ModuleIsSelected(\"itop-storage-mgmt\")";
$sSimpleCallToModuleIsSelected2 = "SetupInfo::ModuleIsSelected(\"itop-storage-mgmt-notselected\")";
$sCallToModuleIsSelectedCombinedWithAndOperator = "SetupInfo::ModuleIsSelected(\"itop-storage-mgmt\") || SetupInfo::ModuleIsSelected(\"itop-virtualization-mgmt\")";
$sCallToModuleIsSelectedCombinedWithAndOperator2 = "SetupInfo::ModuleIsSelected(\"itop-storage-mgmt-notselected\") || SetupInfo::ModuleIsSelected(\"itop-virtualization-mgmt\")";
return [
"simple call to SetupInfo::ModuleIsSelected SELECTED" => [
"expr" => $sSimpleCallToModuleIsSelected,
"expected" => true,
],
"simple call to SetupInfo::ModuleIsSelected NOT SELECTED" => [
"expr" => $sSimpleCallToModuleIsSelected2,
"expected" => false,
],
"call to SetupInfo::ModuleIsSelected + OR => SELECTED" => [
"expr" => $sCallToModuleIsSelectedCombinedWithAndOperator,
"expected" => true,
],
"simple call to SetupInfo::ModuleIsSelected + OR => NOT SELECTED" => [
"expr" => $sCallToModuleIsSelectedCombinedWithAndOperator2,
"expected" => false,
],
];
}
/**
* @dataProvider ParseAndEvaluateBooleanExpression_AutoselectProvider
*/
public function testEvaluateBooleanExpression_Autoselect(string $sBooleanExpression, bool $expected){
\SetupInfo::SetSelectedModules(["itop-storage-mgmt" => "123"]);
$oPhpExpressionEvaluator = new PhpExpressionEvaluator([], ["SetupInfo::ModuleIsSelected"]);
$this->assertEquals($expected, $oPhpExpressionEvaluator->ParseAndEvaluateBooleanExpression($sBooleanExpression), $sBooleanExpression);
}
}
class EvaluationFakeClass {
public string $iIsOk="IsOkValue";
public function GetName()
{
return "gabuzomeu";
}
public function GetLongName($suffix)
{
return "gabuzomeu_" . $suffix;
}
public function __toString(): string
{
return "toString";
}
}