mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-16 00:44:10 +01:00
Compare commits
127 Commits
3.2.0-alph
...
3.1.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46aaeb4301 | ||
|
|
affed69999 | ||
|
|
64a216e0f6 | ||
|
|
d5754fc568 | ||
|
|
ccb1ca9d79 | ||
|
|
44290db312 | ||
|
|
025af923ea | ||
|
|
858b12abaa | ||
|
|
a7bc4bd411 | ||
|
|
a07f66c061 | ||
|
|
c49ceae75e | ||
|
|
4da975cb64 | ||
|
|
8980f627e9 | ||
|
|
ec61b52238 | ||
|
|
072596a53b | ||
|
|
160bfd714b | ||
|
|
1c5cb1547f | ||
|
|
8d58372074 | ||
|
|
5780f26817 | ||
|
|
343f3286b8 | ||
|
|
37fc1a5723 | ||
|
|
1fa50f695d | ||
|
|
692cf4f635 | ||
|
|
95aa444ee6 | ||
|
|
f5de808c7c | ||
|
|
cbb4281a37 | ||
|
|
06dcae1dd1 | ||
|
|
e03033ce52 | ||
|
|
c70d62a51e | ||
|
|
0d5ff261fe | ||
|
|
374b35f78a | ||
|
|
9371bc6d7b | ||
|
|
58e964fb8c | ||
|
|
e8ecc85828 | ||
|
|
04bd8cc5ce | ||
|
|
88756a443a | ||
|
|
052e2a1a42 | ||
|
|
eb1ecff7d8 | ||
|
|
8141723869 | ||
|
|
8cb701bda3 | ||
|
|
1b29746806 | ||
|
|
fb9c317256 | ||
|
|
8c704951e1 | ||
|
|
24c23628d6 | ||
|
|
141c22ff67 | ||
|
|
1e3c425e81 | ||
|
|
b2e3fb6354 | ||
|
|
45ce68e16e | ||
|
|
1aef576403 | ||
|
|
bfd9be8280 | ||
|
|
96e1388dde | ||
|
|
86b48b8980 | ||
|
|
69c8791fc5 | ||
|
|
5fd8678a3a | ||
|
|
86df9ac035 | ||
|
|
3f997b416f | ||
|
|
cceb6809e7 | ||
|
|
48b559472e | ||
|
|
cddc452693 | ||
|
|
0904a21e3f | ||
|
|
66230199f8 | ||
|
|
1f1a2b660f | ||
|
|
4f36369ea1 | ||
|
|
33a906f11a | ||
|
|
82d11eeb47 | ||
|
|
b8bf66031b | ||
|
|
2596a150bf | ||
|
|
142d6c8993 | ||
|
|
d70e5dff45 | ||
|
|
c4fc0ed982 | ||
|
|
320922a13d | ||
|
|
f96d28a0a8 | ||
|
|
d3b9965283 | ||
|
|
f03d731b1d | ||
|
|
3e3ac0d83f | ||
|
|
32835f70b9 | ||
|
|
63cf78f64d | ||
|
|
8be7628668 | ||
|
|
5e64be8580 | ||
|
|
a0b76a25be | ||
|
|
e2f2afad54 | ||
|
|
f632cf3155 | ||
|
|
13dfdc3d9d | ||
|
|
62caf16153 | ||
|
|
020a37ead9 | ||
|
|
163a3afc0f | ||
|
|
35265718c1 | ||
|
|
d98e35d918 | ||
|
|
f8b54be896 | ||
|
|
a88ce075cc | ||
|
|
c6f3e36451 | ||
|
|
53dc452d61 | ||
|
|
ccaf2dc5b7 | ||
|
|
d9581a083d | ||
|
|
46738d4ba4 | ||
|
|
5d5df5ad1a | ||
|
|
8c46c99ecc | ||
|
|
02be397e8f | ||
|
|
61469a28b9 | ||
|
|
9ff54cead8 | ||
|
|
dbcbb187b2 | ||
|
|
71b4d672d4 | ||
|
|
93bba66323 | ||
|
|
0a95aa385a | ||
|
|
cab6394cba | ||
|
|
32140b360f | ||
|
|
06a8481511 | ||
|
|
59a955f491 | ||
|
|
2ffcfd2f57 | ||
|
|
e657052d17 | ||
|
|
d85767a838 | ||
|
|
e56d5ed3aa | ||
|
|
e5a8bd61b0 | ||
|
|
eeec57536b | ||
|
|
7fba61ff35 | ||
|
|
1bf156bdda | ||
|
|
1164e757de | ||
|
|
514e0b80a5 | ||
|
|
1235452a1b | ||
|
|
35f4ab4941 | ||
|
|
16ff6341d0 | ||
|
|
12ad3c2732 | ||
|
|
ac826cb9f1 | ||
|
|
9dab8679d6 | ||
|
|
4f3b25aa46 | ||
|
|
f737bcb9a0 | ||
|
|
4c78488644 |
43
.github/workflows/action.yml
vendored
Normal file
43
.github/workflows/action.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Add PRs to Combodo PRs Dashboard
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
name: Add PR to Combodo Project
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if author is a member of the organization
|
||||
id: check-membership
|
||||
run: |
|
||||
ORG="Combodo"
|
||||
AUTHOR=$(jq -r .pull_request.user.login "$GITHUB_EVENT_PATH")
|
||||
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
|
||||
"https://api.github.com/orgs/$ORG/members/$AUTHOR")
|
||||
if [ "$RESPONSE" == "404" ]; then
|
||||
echo "project_url=https://github.com/orgs/Combodo/projects/5" >> $GITHUB_ENV
|
||||
echo "is_member=false" >> $GITHUB_ENV
|
||||
else
|
||||
echo "project_url=https://github.com/orgs/Combodo/projects/4" >> $GITHUB_ENV
|
||||
echo "is_member=true" >> $GITHUB_ENV
|
||||
|
||||
fi
|
||||
|
||||
- name: Add internal tag if member
|
||||
if: env.is_member == 'true'
|
||||
run: |
|
||||
curl -X POST -H "Authorization: token ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/Combodo/iTop/issues/${{ github.event.pull_request.number }}/labels \
|
||||
-d '{"labels":["internal"]}'
|
||||
env:
|
||||
is_member: ${{ env.is_member }}
|
||||
|
||||
- name: Add PR to the appropriate project
|
||||
uses: actions/add-to-project@v1.0.2
|
||||
with:
|
||||
project-url: ${{ env.project_url }}
|
||||
github-token: ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}
|
||||
@@ -27,7 +27,7 @@ $iTopFolder = __DIR__."/../../../";
|
||||
require_once("$iTopFolder/approot.inc.php");
|
||||
require_once(APPROOT."/application/utils.inc.php");
|
||||
|
||||
if (php_sapi_name() !== 'cli')
|
||||
if (PHP_SAPI !== 'cli')
|
||||
{
|
||||
throw new \Exception('This script can only run from CLI');
|
||||
}
|
||||
@@ -48,4 +48,4 @@ if (!file_exists($sCssFile))
|
||||
{
|
||||
fwrite(STDERR, "Failed to compile $sCssFile, exiting.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ $iTopFolder = __DIR__ . "/../../" ;
|
||||
require_once ("$iTopFolder/approot.inc.php");
|
||||
require_once (APPROOT."/setup/setuputils.class.inc.php");
|
||||
|
||||
if (php_sapi_name() !== 'cli')
|
||||
if (PHP_SAPI !== 'cli')
|
||||
{
|
||||
throw new \Exception('This script can only run from CLI');
|
||||
}
|
||||
@@ -70,4 +70,4 @@ if (false === empty($aMissing)) {
|
||||
echo "Some new tests dirs exists !\n"
|
||||
.' They must be declared either in the allowed or denied list in '.iTopComposer::class." (see N°2651).\n"
|
||||
.' List of dirs:'."\n".var_export($aMissing, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1115,7 +1115,9 @@ HTML
|
||||
|
||||
// Note: DisplayBareHeader is called before adding $oObjectDetails to the page, so it can inject HTML before it through $oPage.
|
||||
/** @var \iTopWebPage $oPage */
|
||||
$oKPI = new ExecutionKPI();
|
||||
$aHeadersBlocks = $this->DisplayBareHeader($oPage, $bEditMode);
|
||||
$oKPI->ComputeStatsForExtension($this, 'DisplayBareHeader');
|
||||
if (false === empty($aHeadersBlocks['subtitle'])) {
|
||||
$oObjectDetails->AddSubTitleBlocks($aHeadersBlocks['subtitle']);
|
||||
}
|
||||
@@ -1128,8 +1130,12 @@ HTML
|
||||
$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB, '', $oObjectDetails);
|
||||
$oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB);
|
||||
$oPage->SetCurrentTab('UI:PropertiesTab');
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->DisplayBareProperties($oPage, $bEditMode);
|
||||
$oKPI->ComputeStatsForExtension($this, 'DisplayBareProperties');
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->DisplayBareRelations($oPage, $bEditMode);
|
||||
$oKPI->ComputeStatsForExtension($this, 'DisplayBareRelations');
|
||||
|
||||
|
||||
// Note: Adding the JS snippet which enables the image upload should have been done directly by the ActivityPanel which would have kept the independance principle
|
||||
@@ -4583,6 +4589,8 @@ HTML;
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sExtensionClass = get_class($oExtensionInstance);
|
||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBInsert()");
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange());
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
|
||||
@@ -4664,7 +4672,22 @@ HTML;
|
||||
return $oDeletionPlan;
|
||||
}
|
||||
|
||||
protected function PostDeleteActions(): void
|
||||
final protected function PreDeleteActions(): void
|
||||
{
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sExtensionClass = get_class($oExtensionInstance);
|
||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBDelete()");
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBDelete');
|
||||
}
|
||||
|
||||
parent::PreDeleteActions();
|
||||
}
|
||||
|
||||
final protected function PostDeleteActions(): void
|
||||
{
|
||||
parent::PostDeleteActions();
|
||||
}
|
||||
@@ -4678,6 +4701,8 @@ HTML;
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sExtensionClass = get_class($oExtensionInstance);
|
||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBDelete()");
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBDelete');
|
||||
@@ -4699,6 +4724,7 @@ HTML;
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sExtensionClass = get_class($oExtensionInstance);
|
||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified()");
|
||||
$oKPI = new ExecutionKPI();
|
||||
$bIsModified = $oExtensionInstance->OnIsModified($this);
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnIsModified');
|
||||
@@ -4758,6 +4784,8 @@ HTML;
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sExtensionClass = get_class($oExtensionInstance);
|
||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnCheckToWrite()");
|
||||
$oKPI = new ExecutionKPI();
|
||||
$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToWrite');
|
||||
@@ -4808,6 +4836,8 @@ HTML;
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
$sExtensionClass = get_class($oExtensionInstance);
|
||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnCheckToDelete()");
|
||||
$oKPI = new ExecutionKPI();
|
||||
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
|
||||
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToDelete');
|
||||
@@ -5934,7 +5964,7 @@ JS
|
||||
final protected function FireEventAfterWrite(array $aChanges, bool $bIsNew): void
|
||||
{
|
||||
$this->NotifyAttachedObjectsOnLinkClassModification();
|
||||
$this->FireEventDbLinksChangedForCurrentObject();
|
||||
$this->RemoveObjectAwaitingEventDbLinksChanged(get_class($this), $this->GetKey());
|
||||
$this->FireEvent(EVENT_DB_AFTER_WRITE, ['is_new' => $bIsNew, 'changes' => $aChanges]);
|
||||
}
|
||||
|
||||
@@ -6047,31 +6077,6 @@ JS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire the EVENT_DB_LINKS_CHANGED event if current object is registered
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
*
|
||||
* @since 3.1.0 N°5906
|
||||
*/
|
||||
final protected function FireEventDbLinksChangedForCurrentObject(): void
|
||||
{
|
||||
if (true === static::IsEventDBLinksChangedBlocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sClass = get_class($this);
|
||||
$sId = $this->GetKey();
|
||||
$bIsObjectAwaitingEventDbLinksChanged = self::RemoveObjectAwaitingEventDbLinksChanged($sClass, $sId);
|
||||
if (false === $bIsObjectAwaitingEventDbLinksChanged) {
|
||||
return;
|
||||
}
|
||||
self::FireEventDbLinksChangedForObject($this);
|
||||
self::RemoveObjectAwaitingEventDbLinksChanged($sClass, $sId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire the EVENT_DB_LINKS_CHANGED event if given object is registered, and unregister it
|
||||
*
|
||||
@@ -6101,7 +6106,9 @@ JS
|
||||
// We want to avoid launching the listener twice, first here, and secondly after saving the Ticket in the listener
|
||||
// By disabling the event to be fired, we can remove the current object from the attribute !
|
||||
$oObject = MetaModel::GetObject($sClass, $sId, false);
|
||||
self::FireEventDbLinksChangedForObject($oObject);
|
||||
if (!is_null($oObject)) {
|
||||
self::FireEventDbLinksChangedForObject($oObject);
|
||||
}
|
||||
self::RemoveObjectAwaitingEventDbLinksChanged($sClass, $sId);
|
||||
}
|
||||
|
||||
@@ -6109,13 +6116,11 @@ JS
|
||||
{
|
||||
self::SetEventDBLinksChangedBlocked(true);
|
||||
// N°6408 The object can have been deleted
|
||||
if (!is_null($oObject)) {
|
||||
MetaModel::StartReentranceProtection($oObject);
|
||||
$oObject->FireEvent(EVENT_DB_LINKS_CHANGED);
|
||||
MetaModel::StopReentranceProtection($oObject);
|
||||
if (count($oObject->ListChanges()) !== 0) {
|
||||
$oObject->DBUpdate();
|
||||
}
|
||||
$oObject->FireEvent(EVENT_DB_LINKS_CHANGED);
|
||||
|
||||
// Update the object if needed
|
||||
if (count($oObject->ListChanges()) !== 0) {
|
||||
$oObject->DBUpdate();
|
||||
}
|
||||
cmdbAbstractObject::SetEventDBLinksChangedBlocked(false);
|
||||
}
|
||||
|
||||
@@ -1264,12 +1264,12 @@ EOF
|
||||
$sOkButtonLabel = Dict::S('UI:Button:Save');
|
||||
$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
|
||||
|
||||
$sId = utils::HtmlEntities($this->sId);
|
||||
$sLayoutClass = utils::HtmlEntities($this->sLayoutClass);
|
||||
$sId = json_encode($this->sId);
|
||||
$sLayoutClass = json_encode($this->sLayoutClass);
|
||||
$sAutoReload = $this->bAutoReload ? 'true' : 'false';
|
||||
$sAutoReloadSec = (string) $this->iAutoReloadSec;
|
||||
$sTitle = utils::HtmlEntities($this->sTitle);
|
||||
$sFile = utils::HtmlEntities($this->GetDefinitionFile());
|
||||
$sTitle = json_encode($this->sTitle);
|
||||
$sFile = json_encode($this->GetDefinitionFile());
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php';
|
||||
$sReloadURL = $this->GetReloadURL();
|
||||
|
||||
@@ -1325,15 +1325,15 @@ $('#dashboard_editor').dialog({
|
||||
});
|
||||
|
||||
$('#dashboard_editor .ui-layout-center').runtimedashboard({
|
||||
dashboard_id: '$sId',
|
||||
layout_class: '$sLayoutClass',
|
||||
title: '$sTitle',
|
||||
dashboard_id: $sId,
|
||||
layout_class: $sLayoutClass,
|
||||
title: $sTitle,
|
||||
auto_reload: $sAutoReload,
|
||||
auto_reload_sec: $sAutoReloadSec,
|
||||
submit_to: '$sUrl',
|
||||
submit_parameters: {operation: 'save_dashboard', file: '$sFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
|
||||
submit_parameters: {operation: 'save_dashboard', file: $sFile, extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
|
||||
render_to: '$sUrl',
|
||||
render_parameters: {operation: 'render_dashboard', file: '$sFile', extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
|
||||
render_parameters: {operation: 'render_dashboard', file: $sFile, extra_params: $sJSExtraParams, reload_url: '$sReloadURL'},
|
||||
new_dashlet_parameters: {operation: 'new_dashlet'}
|
||||
});
|
||||
|
||||
|
||||
@@ -704,7 +704,7 @@ class DisplayBlock
|
||||
if ($bDoSearch)
|
||||
{
|
||||
// Keep the table_id identifying this table if we're performing a search
|
||||
$sTableId = utils::ReadParam('_table_id_', null, false, 'raw_data');
|
||||
$sTableId = utils::ReadParam('_table_id_', null, false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
|
||||
if ($sTableId != null)
|
||||
{
|
||||
$aExtraParams['table_id'] = $sTableId;
|
||||
|
||||
@@ -60,6 +60,24 @@ class CoreCannotSaveObjectException extends CoreException
|
||||
return $sContent;
|
||||
}
|
||||
|
||||
public function getTextMessage()
|
||||
{
|
||||
$sTitle = Dict::S('UI:Error:SaveFailed');
|
||||
$sContent = utils::HtmlEntities($sTitle);
|
||||
|
||||
if (count($this->aIssues) == 1) {
|
||||
$sIssue = reset($this->aIssues);
|
||||
$sContent .= utils::HtmlEntities($sIssue);
|
||||
} else {
|
||||
foreach ($this->aIssues as $sError) {
|
||||
$sContent .= " ".utils::HtmlEntities($sError).", ";
|
||||
}
|
||||
}
|
||||
|
||||
return $sContent;
|
||||
}
|
||||
|
||||
|
||||
public function getIssues()
|
||||
{
|
||||
return $this->aIssues;
|
||||
|
||||
@@ -109,6 +109,11 @@ class utils
|
||||
* @since 2.7.10 3.0.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER = 'element_identifier';
|
||||
/**
|
||||
* @var string For XML / HTML node id/class selector
|
||||
* @since 3.1.2 3.2.1
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR = 'element_selector';
|
||||
/**
|
||||
* @var string For variables names
|
||||
* @since 3.0.0
|
||||
@@ -238,13 +243,8 @@ class utils
|
||||
|
||||
public static function IsModeCLI()
|
||||
{
|
||||
$sSAPIName = php_sapi_name();
|
||||
$sCleanName = strtolower(trim($sSAPIName));
|
||||
if ($sCleanName == 'cli') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
$sCleanName = strtolower(trim(PHP_SAPI));
|
||||
return ($sCleanName === 'cli');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -367,13 +367,13 @@ class utils
|
||||
}
|
||||
return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter);
|
||||
}
|
||||
|
||||
|
||||
public static function ReadPostedParam($sName, $defaultValue = '', $sSanitizationFilter = 'parameter')
|
||||
{
|
||||
$retValue = isset($_POST[$sName]) ? $_POST[$sName] : $defaultValue;
|
||||
return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter);
|
||||
}
|
||||
|
||||
|
||||
public static function Sanitize($value, $defaultValue, $sSanitizationFilter)
|
||||
{
|
||||
if ($value === $defaultValue)
|
||||
@@ -389,7 +389,7 @@ class utils
|
||||
$retValue = $defaultValue;
|
||||
}
|
||||
}
|
||||
return $retValue;
|
||||
return $retValue;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -494,8 +494,17 @@ class utils
|
||||
}
|
||||
break;
|
||||
|
||||
// For XML / HTML node identifiers
|
||||
case static::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER:
|
||||
$retValue = preg_replace('/[^a-zA-Z0-9_-]/', '', $value);
|
||||
$retValue = filter_var($retValue, FILTER_VALIDATE_REGEXP,
|
||||
['options' => ['regexp' => '/^[A-Za-z0-9][A-Za-z0-9_-]*$/']]);
|
||||
break;
|
||||
|
||||
// For XML / HTML node id selector
|
||||
case static::ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR:
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
|
||||
['options' => ['regexp' => '/^[#\.][A-Za-z0-9][A-Za-z0-9_-]*$/']]);
|
||||
break;
|
||||
|
||||
case static::ENUM_SANITIZATION_FILTER_VARIABLE_NAME:
|
||||
@@ -545,11 +554,11 @@ class utils
|
||||
$sMimeType = self::GetFileMimeType($sTmpName);
|
||||
$oDocument = new ormDocument($doc_content, $sMimeType, $sName);
|
||||
break;
|
||||
|
||||
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
// no file to load, it's a normal case, just return an empty document
|
||||
break;
|
||||
|
||||
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
throw new FileUploadException(Dict::Format('UI:Error:UploadedFileTooBig', ini_get('upload_max_filesize')));
|
||||
@@ -558,7 +567,7 @@ class utils
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
throw new FileUploadException(Dict::S('UI:Error:UploadedFileTruncated.'));
|
||||
break;
|
||||
|
||||
|
||||
case UPLOAD_ERR_NO_TMP_DIR:
|
||||
throw new FileUploadException(Dict::S('UI:Error:NoTmpDir'));
|
||||
break;
|
||||
@@ -571,7 +580,7 @@ class utils
|
||||
$sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex];
|
||||
throw new FileUploadException(Dict::Format('UI:Error:UploadStoppedByExtension_FileName', $sName));
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
throw new FileUploadException(Dict::Format('UI:Error:UploadFailedUnknownCause_Code', $sError));
|
||||
break;
|
||||
@@ -677,17 +686,17 @@ class utils
|
||||
|
||||
return $aSelectedObj;
|
||||
}
|
||||
|
||||
|
||||
public static function GetNewTransactionId()
|
||||
{
|
||||
return privUITransaction::GetNewTransactionId();
|
||||
}
|
||||
|
||||
|
||||
public static function IsTransactionValid($sId, $bRemoveTransaction = true)
|
||||
{
|
||||
return privUITransaction::IsTransactionValid($sId, $bRemoveTransaction);
|
||||
}
|
||||
|
||||
|
||||
public static function RemoveTransaction($sId)
|
||||
{
|
||||
return privUITransaction::RemoveTransaction($sId);
|
||||
@@ -871,9 +880,9 @@ class utils
|
||||
$aDateTokens = array_keys($aSpec);
|
||||
$aDateRegexps = array_values($aSpec);
|
||||
}
|
||||
|
||||
|
||||
$sDateRegexp = str_replace($aDateTokens, $aDateRegexps, $sFormat);
|
||||
|
||||
|
||||
if (preg_match('!^(?<head>)'.$sDateRegexp.'(?<tail>)$!', $sDate, $aMatches))
|
||||
{
|
||||
$sYear = isset($aMatches['year']) ? $aMatches['year'] : 0;
|
||||
@@ -890,7 +899,7 @@ class utils
|
||||
}
|
||||
// http://www.spaweditor.com/scripts/regex/index.php
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert an old date/time format specification (using % placeholders)
|
||||
* to a format compatible with DateTime::createFromFormat
|
||||
@@ -1488,7 +1497,7 @@ class utils
|
||||
$aResult = [];
|
||||
|
||||
// 1st - add standard built-in menu items
|
||||
//
|
||||
//
|
||||
switch($iMenuId)
|
||||
{
|
||||
case iPopupMenuExtension::MENU_OBJLIST_ACTIONS:
|
||||
@@ -1872,7 +1881,7 @@ SQL;
|
||||
return $sProposed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Some characters cause troubles with jQuery when used inside DOM IDs, so let's replace them by the safe _ (underscore)
|
||||
* @param string $sId The ID to sanitize
|
||||
@@ -1882,7 +1891,7 @@ SQL;
|
||||
{
|
||||
return str_replace(array(':', '[', ']', '+', '-', ' '), '_', $sId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper to execute an HTTP POST request, uses CURL PHP extension
|
||||
*
|
||||
@@ -1967,7 +1976,7 @@ SQL;
|
||||
|
||||
/**
|
||||
* Get a standard list of character sets
|
||||
*
|
||||
*
|
||||
* @param array $aAdditionalEncodings Additional values
|
||||
* @return array of iconv code => english label, sorted by label
|
||||
*/
|
||||
@@ -2074,7 +2083,7 @@ SQL;
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert (?) plain text to some HTML markup by replacing newlines by <br/> tags
|
||||
* and escaping HTML entities
|
||||
@@ -2091,7 +2100,7 @@ SQL;
|
||||
|
||||
return str_replace("\n", '<br/>', utils::EscapeHtml($sText));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Eventually compiles the SASS (.scss) file into the CSS (.css) file
|
||||
*
|
||||
@@ -2205,7 +2214,7 @@ SQL;
|
||||
case 'image/png':
|
||||
$img = @imagecreatefromstring($oImage->GetData());
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
// Unsupported image type, return the image as-is
|
||||
//throw new Exception("Unsupported image type: '".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
|
||||
@@ -2219,14 +2228,14 @@ SQL;
|
||||
else
|
||||
{
|
||||
// Let's scale the image, preserving the transparency for GIFs and PNGs
|
||||
|
||||
|
||||
$fScale = min($iMaxImageWidth / $iWidth, $iMaxImageHeight / $iHeight);
|
||||
|
||||
$iNewWidth = $iWidth * $fScale;
|
||||
$iNewHeight = $iHeight * $fScale;
|
||||
|
||||
|
||||
$new = imagecreatetruecolor($iNewWidth, $iNewHeight);
|
||||
|
||||
|
||||
// Preserve transparency
|
||||
if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png"))
|
||||
{
|
||||
@@ -2234,38 +2243,38 @@ SQL;
|
||||
imagealphablending($new, false);
|
||||
imagesavealpha($new, true);
|
||||
}
|
||||
|
||||
|
||||
imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight);
|
||||
|
||||
|
||||
ob_start();
|
||||
switch ($oImage->GetMimeType())
|
||||
{
|
||||
case 'image/gif':
|
||||
imagegif($new); // send image to output buffer
|
||||
break;
|
||||
|
||||
|
||||
case 'image/jpeg':
|
||||
imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality
|
||||
break;
|
||||
|
||||
|
||||
case 'image/png':
|
||||
imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression
|
||||
break;
|
||||
}
|
||||
$oResampledImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName());
|
||||
@ob_end_clean();
|
||||
|
||||
|
||||
imagedestroy($img);
|
||||
imagedestroy($new);
|
||||
|
||||
|
||||
return $oResampledImage;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a 128 bit UUID in the format: {########-####-####-####-############}
|
||||
*
|
||||
*
|
||||
* Note: this method can be run from the command line as well as from the web server.
|
||||
* Note2: this method is not cryptographically secure! If you need a cryptographically secure value
|
||||
* consider using open_ssl or PHP 7 methods.
|
||||
@@ -2303,7 +2312,7 @@ SQL;
|
||||
{
|
||||
return ModuleService::GetInstance()->GetCurrentModuleName($iCallDepth + 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* **Warning** : returned result can be invalid as we're using backtrace to find the module dir name
|
||||
*
|
||||
@@ -2340,7 +2349,7 @@ SQL;
|
||||
{
|
||||
return ModuleService::GetInstance()->GetCurrentModuleUrl(1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $sProperty The name of the property to retrieve
|
||||
* @param mixed $defaultvalue
|
||||
@@ -2350,7 +2359,7 @@ SQL;
|
||||
{
|
||||
return ModuleService::GetInstance()->GetCurrentModuleSetting($sProperty, $defaultvalue);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $sModuleName
|
||||
* @return string|NULL compiled version of a given module, as it was seen by the compiler
|
||||
@@ -2359,7 +2368,7 @@ SQL;
|
||||
{
|
||||
return ModuleService::GetInstance()->GetCompiledModuleVersion($sModuleName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the given path/url is an http(s) URL
|
||||
* @param string $sPath
|
||||
@@ -2374,7 +2383,7 @@ SQL;
|
||||
}
|
||||
return $bRet;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the given URL is a link to download a document/image on the CURRENT iTop
|
||||
* In such a case we can read the content of the file directly in the database (if the users rights allow) and return the ormDocument
|
||||
@@ -2423,7 +2432,7 @@ SQL;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read the content of a file (and retrieve its MIME type) from either:
|
||||
* - an URL pointing to a blob (image/document) on the current iTop server
|
||||
@@ -2467,7 +2476,7 @@ SQL;
|
||||
'html' => 'text/html',
|
||||
'exe' => 'application/octet-stream',
|
||||
);
|
||||
|
||||
|
||||
$sData = null;
|
||||
$sMimeType = 'text/plain'; // Default MIME Type: treat the file as a bunch a characters...
|
||||
$sFileName = 'uploaded-file'; // Default name for downloaded-files
|
||||
@@ -2525,7 +2534,7 @@ SQL;
|
||||
}
|
||||
$sExtension = strtolower(pathinfo($sPath, PATHINFO_EXTENSION));
|
||||
$sFileName = basename($sPath);
|
||||
|
||||
|
||||
if (array_key_exists($sExtension, $aKnownExtensions))
|
||||
{
|
||||
$sMimeType = $aKnownExtensions[$sExtension];
|
||||
@@ -2539,7 +2548,7 @@ SQL;
|
||||
}
|
||||
return $oUploadedDoc;
|
||||
}
|
||||
|
||||
|
||||
protected static function ParseHeaders($aHeaders)
|
||||
{
|
||||
$aCleanHeaders = array();
|
||||
@@ -2564,7 +2573,7 @@ SQL;
|
||||
}
|
||||
return $aCleanHeaders;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string a string based on compilation time or (if not available because the datamodel has not been loaded)
|
||||
* the version of iTop. This string is useful to prevent browser side caching of content that may vary at each
|
||||
|
||||
@@ -23,7 +23,7 @@ define('ITOP_DESIGN_LATEST_VERSION', '3.1');
|
||||
* @used-by utils::GetItopVersionWikiSyntax()
|
||||
* @used-by iTopModulesPhpVersionIntegrationTest
|
||||
*/
|
||||
define('ITOP_CORE_VERSION', '3.1.1');
|
||||
define('ITOP_CORE_VERSION', '3.1.3');
|
||||
|
||||
/**
|
||||
* @var string
|
||||
|
||||
@@ -68,7 +68,7 @@ if (file_exists(MAINTENANCE_MODE_FILE) && !$bBypassMaintenance)
|
||||
http_response_code(503);
|
||||
// Display message depending on the request
|
||||
include(APPROOT.'application/maintenancemsg.php');
|
||||
$sSAPIName = strtoupper(trim(php_sapi_name()));
|
||||
$sSAPIName = strtoupper(trim(PHP_SAPI));
|
||||
|
||||
switch (true)
|
||||
{
|
||||
|
||||
@@ -11508,6 +11508,11 @@ class AttributeClassAttCodeSet extends AttributeSet
|
||||
}
|
||||
return '<span class="'.implode(' ', $this->aCSSClasses).'">'.$value.'</span>';
|
||||
}
|
||||
|
||||
public function IsNull($proposedValue)
|
||||
{
|
||||
return (empty($proposedValue));
|
||||
}
|
||||
}
|
||||
|
||||
class AttributeQueryAttCodeSet extends AttributeSet
|
||||
|
||||
@@ -1169,8 +1169,8 @@ class CMDBSource
|
||||
*/
|
||||
public static function IsSameFieldTypes($sItopGeneratedFieldType, $sDbFieldType)
|
||||
{
|
||||
list($sItopFieldDataType, $sItopFieldTypeOptions, $sItopFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sItopGeneratedFieldType);
|
||||
list($sDbFieldDataType, $sDbFieldTypeOptions, $sDbFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sDbFieldType);
|
||||
[$sItopFieldDataType, $sItopFieldTypeOptions, $sItopFieldOtherOptions] = static::GetFieldDataTypeAndOptions($sItopGeneratedFieldType);
|
||||
[$sDbFieldDataType, $sDbFieldTypeOptions, $sDbFieldOtherOptions] = static::GetFieldDataTypeAndOptions($sDbFieldType);
|
||||
|
||||
if (strcasecmp($sItopFieldDataType, $sDbFieldDataType) !== 0)
|
||||
{
|
||||
@@ -1603,7 +1603,19 @@ class CMDBSource
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
public static function GetClusterNb()
|
||||
{
|
||||
$result = 0;
|
||||
$sSql = "SHOW STATUS LIKE 'wsrep_cluster_size';";
|
||||
$aRows = self::QueryToArray($sSql);
|
||||
if (count($aRows) > 0)
|
||||
{
|
||||
$result = $aRows[0]['Value'];
|
||||
}
|
||||
return intval($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://dev.mysql.com/doc/refman/5.7/en/charset-database.html
|
||||
* @return string query to upgrade database charset and collation if needed, null if not
|
||||
* @throws \MySQLException
|
||||
|
||||
@@ -766,6 +766,42 @@ abstract class DBObject implements iDisplay
|
||||
$this->Set($sAttCode, $sValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected function PreDeleteActions(): void
|
||||
{
|
||||
$this->SetReadOnly('No modification allowed before delete');
|
||||
$this->FireEventAboutToDelete();
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->OnDelete();
|
||||
$oKPI->ComputeStatsForExtension($this, 'OnDelete');
|
||||
|
||||
// Activate any existing trigger
|
||||
$sClass = get_class($this);
|
||||
$aParams = array('class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL('SELECT TriggerOnObjectDelete AS t WHERE t.target_class IN (:class_list)'), array(),
|
||||
$aParams);
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \TriggerOnObjectDelete $oTrigger */
|
||||
try {
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
finally {
|
||||
$oKPI->ComputeStatsForExtension($this, 'TriggerOnObjectDelete');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \ReflectionException
|
||||
@@ -3332,6 +3368,9 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
public function DBInsertNoReload()
|
||||
{
|
||||
// Prevent DBUpdate at this point (reentrancy protection with temp id)
|
||||
MetaModel::StartReentranceProtection($this);
|
||||
|
||||
$sClass = get_class($this);
|
||||
|
||||
$this->AddCurrentObjectInCrudStack('INSERT');
|
||||
@@ -3378,6 +3417,8 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
|
||||
$this->ComputeStopWatchesDeadline(true);
|
||||
// With temp id
|
||||
MetaModel::StopReentranceProtection($this);
|
||||
|
||||
$iTransactionRetry = 1;
|
||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
@@ -3570,6 +3611,12 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
public function DBUpdate()
|
||||
{
|
||||
if (!MetaModel::StartReentranceProtection($this)) {
|
||||
$this->LogCRUDExit(__METHOD__, 'Rejected (reentrance)');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->LogCRUDEnter(__METHOD__);
|
||||
if (!$this->m_bIsInDB)
|
||||
{
|
||||
@@ -3579,12 +3626,6 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
$this->AddCurrentObjectInCrudStack('UPDATE');
|
||||
|
||||
if (!MetaModel::StartReentranceProtection($this)) {
|
||||
$this->RemoveCurrentObjectInCrudStack();
|
||||
$this->LogCRUDExit(__METHOD__, 'Rejected (reentrance)');
|
||||
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// Protect against infinite loop
|
||||
$this->iUpdateLoopCount++;
|
||||
@@ -4085,16 +4126,17 @@ abstract class DBObject implements iDisplay
|
||||
CMDBSource::DeleteFrom($sDeleteSQL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @throws ArchivedObjectException
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
* @throws MySQLException
|
||||
* @throws MySQLHasGoneAwayException
|
||||
* @throws OQLException
|
||||
*/
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
* @throws \OQLException
|
||||
* @throws \Random\RandomException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected function DBDeleteSingleObject()
|
||||
{
|
||||
$this->LogCRUDEnter(__METHOD__);
|
||||
@@ -4105,29 +4147,7 @@ abstract class DBObject implements iDisplay
|
||||
return;
|
||||
}
|
||||
|
||||
$this->SetReadOnly("No modification allowed before delete");
|
||||
$this->FireEventAboutToDelete();
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->OnDelete();
|
||||
$oKPI->ComputeStatsForExtension($this, 'OnDelete');
|
||||
|
||||
// Activate any existing trigger
|
||||
$sClass = get_class($this);
|
||||
$aParams = array('class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectDelete AS t WHERE t.target_class IN (:class_list)"), array(),
|
||||
$aParams);
|
||||
while ($oTrigger = $oSet->Fetch())
|
||||
{
|
||||
/** @var \TriggerOnObjectDelete $oTrigger */
|
||||
try
|
||||
{
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch(Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
$this->PreDeleteActions();
|
||||
|
||||
$this->RecordObjDeletion($this->m_iKey); // May cause a reload for storing history information
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ class EMail implements iEMail
|
||||
*/
|
||||
public function SetInReplyTo(string $sMessageId)
|
||||
{
|
||||
$this->AddToHeader('In-Reply-To', $sMessageId);
|
||||
$this->oMailer->SetInReplyTo($sMessageId);
|
||||
}
|
||||
|
||||
public function SetBody($sBody, $sMimeType = 'text/html', $sCustomStyles = null)
|
||||
|
||||
@@ -6852,6 +6852,9 @@ abstract class MetaModel
|
||||
/**
|
||||
* Instantiate an object already persisted to the Database.
|
||||
*
|
||||
* Note that LinkedSet attributes are not loaded.
|
||||
* DBObject::Reload() will be called when getting a LinkedSet attribute
|
||||
*
|
||||
* @api
|
||||
* @see MetaModel::GetObjectWithArchive to get object even if it's archived
|
||||
* @see utils::PushArchiveMode() to enable search on archived objects
|
||||
|
||||
@@ -537,7 +537,7 @@ EOF
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception('graphviz not found (executable path: '.$sDotExecutable.')');
|
||||
throw new Exception('graphviz not found');
|
||||
}
|
||||
return $sHtml;
|
||||
}
|
||||
@@ -592,7 +592,7 @@ EOF
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception('graphviz not found (executable path: '.$sDotExecutable.')');
|
||||
throw new Exception('graphviz not found');
|
||||
}
|
||||
return $sHtml;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'authent-cas/3.1.1',
|
||||
'authent-cas/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'authent-external/3.1.1',
|
||||
'authent-external/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -9,7 +9,7 @@ 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.1.1',
|
||||
'authent-ldap/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'authent-local/3.1.1',
|
||||
'authent-local/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'combodo-backoffice-darkmoon-theme/3.1.1',
|
||||
'combodo-backoffice-darkmoon-theme/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'combodo-db-tools/3.1.1',
|
||||
'combodo-db-tools/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-attachments/3.1.1',
|
||||
'itop-attachments/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-backup/3.1.1',
|
||||
'itop-backup/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-bridge-cmdb-services/3.1.1',
|
||||
'itop-bridge-cmdb-services/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-bridge-cmdb-ticket/3.1.1',
|
||||
'itop-bridge-cmdb-ticket/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-bridge-datacenter-mgmt-services/3.1.1',
|
||||
'itop-bridge-datacenter-mgmt-services/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-bridge-endusers-devices-services/3.1.1',
|
||||
'itop-bridge-endusers-devices-services/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-bridge-storage-mgmt-services/3.1.1',
|
||||
'itop-bridge-storage-mgmt-services/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-bridge-virtualization-mgmt-services/3.1.1',
|
||||
'itop-bridge-virtualization-mgmt-services/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-bridge-virtualization-storage/3.1.1',
|
||||
'itop-bridge-virtualization-storage/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-change-mgmt-itil/3.1.1',
|
||||
'itop-change-mgmt-itil/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-change-mgmt/3.1.1',
|
||||
'itop-change-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-config-mgmt/3.1.1',
|
||||
'itop-config-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-config/3.1.1',
|
||||
'itop-config/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-core-update/3.1.1',
|
||||
'itop-core-update/3.1.3',
|
||||
[
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-datacenter-mgmt/3.1.1',
|
||||
'itop-datacenter-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-endusers-devices/3.1.1',
|
||||
'itop-endusers-devices/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-faq-light/3.1.1',
|
||||
'itop-faq-light/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-files-information/3.1.1',
|
||||
'itop-files-information/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-full-itil/3.1.1',
|
||||
'itop-full-itil/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
19
datamodels/2.x/itop-hub-connector/TokenValidation.php
Normal file
19
datamodels/2.x/itop-hub-connector/TokenValidation.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
class TokenValidation
|
||||
{
|
||||
// construct function
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
public function isSetupTokenValid($sParamToken) : bool
|
||||
{
|
||||
if (!file_exists(APPROOT.'data/.setup')) {
|
||||
return false;
|
||||
}
|
||||
$sSetupToken = trim(file_get_contents(APPROOT.'data/.setup'));
|
||||
unlink(APPROOT.'data/.setup');
|
||||
return $sParamToken === $sSetupToken;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -263,6 +263,7 @@ try {
|
||||
require_once('hubconnectorpage.class.inc.php');
|
||||
|
||||
require_once(APPROOT.'/application/startup.inc.php');
|
||||
require_once('TokenValidation.php');
|
||||
|
||||
$sTargetRoute = utils::ReadParam('target', ''); // ||browse_extensions|deploy_extensions|
|
||||
|
||||
@@ -278,13 +279,23 @@ try {
|
||||
|
||||
switch ($sTargetRoute) {
|
||||
case 'inform_after_setup':
|
||||
// Hidden IFRAME at the end of the setup
|
||||
$oPage = new NiceWebPage('');
|
||||
$aDataToPost = MakeDataToPost($sTargetRoute);
|
||||
$oPage->add('<form id="hub_launch_form" action="'.$sHubUrlStateless.'" method="post">');
|
||||
$oPage->add('<input type="hidden" name="json" value="'.utils::EscapeHtml(json_encode($aDataToPost)).'">');
|
||||
$oPage->add_ready_script('$("#hub_launch_form").submit();');
|
||||
break;
|
||||
// Hidden IFRAME at the end of the setup
|
||||
require_once (APPROOT.'/application/ajaxwebpage.class.inc.php');
|
||||
|
||||
$sParamToken = utils::ReadParam('setup_token');
|
||||
$oTokenValidation = new TokenValidation();
|
||||
$bIsTokenValid = $oTokenValidation->isSetupTokenValid($sParamToken);
|
||||
if (UserRights::IsAdministrator() || $bIsTokenValid) {
|
||||
$oPage = new NiceWebPage('');
|
||||
$aDataToPost = MakeDataToPost($sTargetRoute);
|
||||
$oPage->add('<form id="hub_launch_form" action="'.$sHubUrlStateless.'" method="post">');
|
||||
$oPage->add('<input type="hidden" name="json" value="'.utils::EscapeHtml(json_encode($aDataToPost)).'">');
|
||||
$oPage->add_ready_script('$("#hub_launch_form").submit();');
|
||||
} else {
|
||||
IssueLog::Error('TokenValidation failed on inform_after_setup page');
|
||||
throw new Exception("Not allowed");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// All other cases, special "Hub like" web page
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-hub-connector/3.1.1',
|
||||
'itop-hub-connector/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-incident-mgmt-itil/3.1.1',
|
||||
'itop-incident-mgmt-itil/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-knownerror-mgmt/3.1.1',
|
||||
'itop-knownerror-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-oauth-client/3.1.1',
|
||||
'itop-oauth-client/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-portal-base/3.1.1', array(
|
||||
'itop-portal-base/3.1.3', array(
|
||||
// Identification
|
||||
'label' => 'Portal Development Library',
|
||||
'category' => 'Portal',
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
p_user_profile_brick_edit_person:
|
||||
path: '/user/edit_person'
|
||||
defaults:
|
||||
_controller: 'Combodo\iTop\Portal\Controller\UserProfileBrickController::EditPerson'
|
||||
|
||||
p_user_profile_brick:
|
||||
path: '/user/{sBrickId}'
|
||||
defaults:
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1334,6 +1334,11 @@ class ObjectController extends BrickController
|
||||
$bIgnoreSilos = $oScopeValidator->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass);
|
||||
$aParams = array('objects_id' => $aObjectIds);
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT $sObjectClass WHERE id IN (:objects_id)");
|
||||
if (!$oScopeValidator->AddScopeToQuery($oSearch, $sObjectClass)
|
||||
) {
|
||||
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to read ' . $sObjectClass . ' object.');
|
||||
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
|
||||
}
|
||||
if ($bIgnoreSilos === true) {
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
@@ -1389,6 +1394,10 @@ class ObjectController extends BrickController
|
||||
$aObjectAttCodes = $oRequestManipulator->ReadParam('aObjectAttCodes', array(), FILTER_UNSAFE_RAW);
|
||||
$aLinkAttCodes = $oRequestManipulator->ReadParam('aLinkAttCodes', array(), FILTER_UNSAFE_RAW);
|
||||
$sDateTimePickerWidgetParent = $oRequestManipulator->ReadParam('sDateTimePickerWidgetParent', array(), FILTER_UNSAFE_RAW);
|
||||
if (!MetaModel::IsLinkClass($sLinkClass)) {
|
||||
IssueLog::Warning(__METHOD__.' at line '.__LINE__.' : User #'.UserRights::GetUserId().' asked for wrong lnk class '.$sLinkClass);
|
||||
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
|
||||
}
|
||||
|
||||
if (empty($sObjectClass) || empty($aObjectIds) || empty($aObjectAttCodes)) {
|
||||
IssueLog::Info(__METHOD__.' at line '.__LINE__.' : sObjectClass, aObjectIds and aObjectAttCodes expected, "'.$sObjectClass.'", "'.implode('/',
|
||||
@@ -1400,7 +1409,12 @@ class ObjectController extends BrickController
|
||||
$bIgnoreSilos = $oScopeValidator->IsAllDataAllowedForScope(UserRights::ListProfiles(), $sObjectClass);
|
||||
$aParams = array('objects_id' => $aObjectIds);
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT $sObjectClass WHERE id IN (:objects_id)");
|
||||
if ($bIgnoreSilos === true)
|
||||
if (!$oScopeValidator->AddScopeToQuery($oSearch, $sObjectClass)
|
||||
) {
|
||||
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to read ' . $sObjectClass . ' object.');
|
||||
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
|
||||
}
|
||||
if ($bIgnoreSilos === true)
|
||||
{
|
||||
$oSearch->AllowAllData();
|
||||
}
|
||||
@@ -1418,10 +1432,35 @@ class ObjectController extends BrickController
|
||||
// Prepare link data
|
||||
$aObjectData = $this->PrepareObjectInformation($oObject, $aObjectAttCodes);
|
||||
// New link object (needed for renderers)
|
||||
$oNewLink = new $sLinkClass();
|
||||
$aAttCodes = MetaModel::GetAttributesList($sLinkClass, ['AttributeExternalKey']);
|
||||
$sAttCodeToObject = '';
|
||||
foreach ($aAttCodes as $sAttCode) {
|
||||
$oAttDef = MetaModel::GetAttributeDef($sLinkClass, $sAttCode);
|
||||
/** @var \AttributeExternalKey $oAttDef */
|
||||
if ($oAttDef->GetTargetClass() === $sObjectClass) {
|
||||
$sAttCodeToObject = $sAttCode;
|
||||
}
|
||||
}
|
||||
if ($sAttCodeToObject === '') {
|
||||
IssueLog::Warning(__METHOD__.' at line '.__LINE__.' : User #'.UserRights::GetUserId().' asked for incoherent lnk class '.$sLinkClass.' with object class '.$sObjectClass);
|
||||
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
|
||||
}
|
||||
$oNewLink = MetaModel::NewObject($sLinkClass, [
|
||||
$sAttCodeToObject => $oObject->GetKey(), // so later placeholders in filters will be applied on external keys on the same link
|
||||
]);
|
||||
foreach ($aLinkAttCodes as $sAttCode) {
|
||||
$oAttDef = MetaModel::GetAttributeDef($sLinkClass, $sAttCode);
|
||||
/** @var \Combodo\iTop\Form\Field\SelectObjectField $oField */
|
||||
$oField = $oAttDef->MakeFormField($oNewLink);
|
||||
if ($oAttDef::GetFormFieldClass() === '\\Combodo\\iTop\\Form\\Field\\SelectObjectField') {
|
||||
$oFieldSearch = $oField->GetSearch();
|
||||
$sFieldClass = $oFieldSearch->GetClass();
|
||||
if ($oScopeValidator->AddScopeToQuery($oFieldSearch, $sFieldClass)){
|
||||
$oField->SetSearch($oFieldSearch);
|
||||
} else {
|
||||
$oField->SetSearch(DBObjectSearch::FromOQL("SELECT $sFieldClass WHERE 1=0"));
|
||||
}
|
||||
}
|
||||
// Prevent datetimepicker popup to be truncated
|
||||
if ($oField instanceof DateTimeField) {
|
||||
$oField->SetDateTimePickerWidgetParent($sDateTimePickerWidgetParent);
|
||||
|
||||
@@ -35,7 +35,7 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use UserRights;
|
||||
use utils;
|
||||
|
||||
use Dict;
|
||||
/**
|
||||
* Class UserProfileBrickController
|
||||
*
|
||||
@@ -66,34 +66,9 @@ class UserProfileBrickController extends BrickController
|
||||
$oRequestManipulator = $this->get('request_manipulator');
|
||||
/** @var \Combodo\iTop\Portal\Helper\ObjectFormHandlerHelper $ObjectFormHandler */
|
||||
$ObjectFormHandler = $this->get('object_form_handler');
|
||||
/** @var \Combodo\iTop\Portal\Brick\BrickCollection $oBrickCollection */
|
||||
$oBrickCollection = $this->get('brick_collection');
|
||||
$oBrick = $this->GetBrick($sBrickId);
|
||||
|
||||
// If the brick id was not specified, we get the first one registered that is an instance of UserProfileBrick as default
|
||||
if ($sBrickId === null)
|
||||
{
|
||||
/** @var \Combodo\iTop\Portal\Brick\PortalBrick $oTmpBrick */
|
||||
foreach ($oBrickCollection->GetBricks() as $oTmpBrick)
|
||||
{
|
||||
if ($oTmpBrick instanceof UserProfileBrick)
|
||||
{
|
||||
$oBrick = $oTmpBrick;
|
||||
}
|
||||
}
|
||||
|
||||
// We make sure a UserProfileBrick was found
|
||||
if (!isset($oBrick) || $oBrick === null)
|
||||
{
|
||||
$oBrick = new UserProfileBrick();
|
||||
//throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, 'UserProfileBrick : Brick could not be loaded as there was no UserProfileBrick loaded in the application.');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$oBrick = $oBrickCollection->GetBrickById($sBrickId);
|
||||
}
|
||||
|
||||
$aData = array();
|
||||
$aData = array();
|
||||
|
||||
// Setting form mode regarding the demo mode parameter
|
||||
$bDemoMode = MetaModel::GetConfig()->Get('demo_mode');
|
||||
@@ -130,11 +105,12 @@ class UserProfileBrickController extends BrickController
|
||||
$oCurContact = UserRights::GetContactObject();
|
||||
$sCurContactClass = get_class($oCurContact);
|
||||
$sCurContactId = $oCurContact->GetKey();
|
||||
|
||||
$aForm = $oBrick->GetForm();
|
||||
$aForm['submit_endpoint'] = $this->generateUrl('p_user_profile_brick_edit_person', ['sBrickId' => $sBrickId]);
|
||||
// Preparing forms
|
||||
$aData['forms']['contact'] = $ObjectFormHandler->HandleForm($oRequest, $sFormMode, $sCurContactClass, $sCurContactId,
|
||||
$oBrick->GetForm());
|
||||
$aData['forms']['preferences'] = $this->HandlePreferencesForm($oRequest, $sFormMode);
|
||||
$aData['forms']['contact'] = $ObjectFormHandler->HandleForm($oRequest, $sFormMode, $sCurContactClass, $sCurContactId,
|
||||
$aForm);
|
||||
$aData['forms']['preferences'] = $this->HandlePreferencesForm($oRequest, $sFormMode);
|
||||
// - If user can change password, we display the form
|
||||
$aData['forms']['password'] = (UserRights::CanChangePassword()) ? $this->HandlePasswordForm($oRequest, $sFormMode) : null;
|
||||
|
||||
@@ -150,6 +126,35 @@ class UserProfileBrickController extends BrickController
|
||||
return $oResponse;
|
||||
}
|
||||
|
||||
public function EditPerson(Request $oRequest)
|
||||
{
|
||||
/** @var \Combodo\iTop\Portal\Helper\ObjectFormHandlerHelper $oObjectFormHandler */
|
||||
$oObjectFormHandler = $this->get('object_form_handler');
|
||||
/** @var \Combodo\iTop\Portal\Helper\SecurityHelper $oSecurityHelper */
|
||||
$oSecurityHelper = $this->get('security_helper');
|
||||
|
||||
$oCurContact = UserRights::GetContactObject();
|
||||
$sObjectClass = get_class($oCurContact);
|
||||
$sObjectId = $oCurContact->GetKey();
|
||||
|
||||
// Checking security layers
|
||||
// Warning : This is a dirty quick fix to allow editing its own contact information
|
||||
$bAllowWrite = ($sObjectClass === 'Person' && $sObjectId == UserRights::GetContactId());
|
||||
if (!$oSecurityHelper->IsActionAllowed(UR_ACTION_MODIFY, $sObjectClass, $sObjectId) && !$bAllowWrite) {
|
||||
IssueLog::Warning(__METHOD__ . ' at line ' . __LINE__ . ' : User #' . UserRights::GetUserId() . ' not allowed to modify ' . $sObjectClass . '::' . $sObjectId . ' object.');
|
||||
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
|
||||
}
|
||||
|
||||
$aForm = $this->GetBrick()->GetForm();
|
||||
$aForm['submit_endpoint'] = $this->generateUrl('p_user_profile_brick_edit_person');
|
||||
|
||||
$aData = ['sMode' => 'edit'];
|
||||
$aData['form'] = $oObjectFormHandler->HandleForm($oRequest, $aData['sMode'], $sObjectClass, $sObjectId, $aForm);
|
||||
|
||||
return new JsonResponse($aData);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \Symfony\Component\HttpFoundation\Request $oRequest
|
||||
* @param string $sFormMode
|
||||
@@ -381,7 +386,7 @@ class UserProfileBrickController extends BrickController
|
||||
'sObjectField' => $sPictureAttCode,
|
||||
'cache' => 86400,
|
||||
's' => $oOrmDoc->GetSignature(),
|
||||
]);
|
||||
]);
|
||||
$aFormData['validation'] = array(
|
||||
'valid' => true,
|
||||
'messages' => array(),
|
||||
@@ -394,4 +399,34 @@ class UserProfileBrickController extends BrickController
|
||||
return $aFormData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sBrickId
|
||||
* @return \Combodo\iTop\Portal\Brick\PortalBrick|UserProfileBrick
|
||||
* @throws \Combodo\iTop\Portal\Brick\BrickNotFoundException
|
||||
*/
|
||||
public function GetBrick($sBrickId = null)
|
||||
{
|
||||
/** @var \Combodo\iTop\Portal\Brick\BrickCollection $oBrickCollection */
|
||||
$oBrickCollection = $this->get('brick_collection');
|
||||
|
||||
// If the brick id was not specified, we get the first one registered that is an instance of UserProfileBrick as default
|
||||
if ($sBrickId === null) {
|
||||
/** @var \Combodo\iTop\Portal\Brick\PortalBrick $oTmpBrick */
|
||||
foreach ($oBrickCollection->GetBricks() as $oTmpBrick) {
|
||||
if ($oTmpBrick instanceof UserProfileBrick) {
|
||||
$oBrick = $oTmpBrick;
|
||||
}
|
||||
}
|
||||
|
||||
// We make sure a UserProfileBrick was found
|
||||
if (!isset($oBrick) || $oBrick === null) {
|
||||
$oBrick = new UserProfileBrick();
|
||||
//throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, 'UserProfileBrick : Brick could not be loaded as there was no UserProfileBrick loaded in the application.');
|
||||
}
|
||||
} else {
|
||||
$oBrick = $oBrickCollection->GetBrickById($sBrickId);
|
||||
}
|
||||
return $oBrick;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -242,13 +242,17 @@ class ObjectFormHandlerHelper
|
||||
case static::ENUM_MODE_CREATE:
|
||||
case static::ENUM_MODE_EDIT:
|
||||
case static::ENUM_MODE_VIEW:
|
||||
$sFormEndpoint = $this->oUrlGenerator->generate(
|
||||
'p_object_'.$sMode,
|
||||
array(
|
||||
'sObjectClass' => $sObjectClass,
|
||||
'sObjectId' => $sObjectId,
|
||||
)
|
||||
);
|
||||
if(array_key_exists('submit_endpoint', $aFormProperties)) {
|
||||
$sFormEndpoint = $aFormProperties['submit_endpoint'];
|
||||
} else {
|
||||
$sFormEndpoint = $this->oUrlGenerator->generate(
|
||||
'p_object_' . $sMode,
|
||||
array(
|
||||
'sObjectClass' => $sObjectClass,
|
||||
'sObjectId' => $sObjectId,
|
||||
)
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case static::ENUM_MODE_APPLY_STIMULUS:
|
||||
@@ -281,7 +285,8 @@ class ObjectFormHandlerHelper
|
||||
->SetActionRulesToken($sActionRulesToken)
|
||||
->SetRenderer($oFormRenderer)
|
||||
->SetFormProperties($aFormProperties);
|
||||
|
||||
$oFormManager->PrepareFormAndHTMLDocument();
|
||||
$oFormManager->PrepareFields();
|
||||
$oFormManager->Build();
|
||||
$aFormData['hidden_fields'] = $oFormManager->GetHiddenFieldsId();
|
||||
// Check the number of editable fields
|
||||
@@ -399,7 +404,7 @@ class ObjectFormHandlerHelper
|
||||
ApplicationContext::MakeObjectUrl($sObjectClass, $sObjectId)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return $aFormData;
|
||||
}
|
||||
|
||||
|
||||
@@ -476,8 +476,8 @@
|
||||
sBody = '{{ 'Error:XHR:Fail'|dict_format(constant('ITOP_APPLICATION_SHORT'))|escape('js') }}';
|
||||
}
|
||||
var oModalElem = $('#modal-for-alert');
|
||||
oModalElem.find('.modal-content .modal-header .modal-title').html(sTitle);
|
||||
oModalElem.find('.modal-content .modal-body .alert').addClass('alert-danger').html(sBody);
|
||||
oModalElem.find('.modal-content .modal-header .modal-title').text(sTitle);
|
||||
oModalElem.find('.modal-content .modal-body .alert').addClass('alert-danger').text(sBody);
|
||||
oModalElem.modal('show');
|
||||
};
|
||||
{% endblock %}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-portal/3.1.1', array(
|
||||
'itop-portal/3.1.3', array(
|
||||
// Identification
|
||||
'label' => 'Enhanced Customer Portal',
|
||||
'category' => 'Portal',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-problem-mgmt/3.1.1',
|
||||
'itop-problem-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-profiles-itil/3.1.1',
|
||||
'itop-profiles-itil/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-request-mgmt-itil/3.1.1',
|
||||
'itop-request-mgmt-itil/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-request-mgmt/3.1.1',
|
||||
'itop-request-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-service-mgmt-provider/3.1.1',
|
||||
'itop-service-mgmt-provider/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-service-mgmt/3.1.1',
|
||||
'itop-service-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-sla-computation/3.1.1',
|
||||
'itop-sla-computation/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-storage-mgmt/3.1.1',
|
||||
'itop-storage-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-structure/3.1.1',
|
||||
'itop-structure/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-themes-compat/3.1.1',
|
||||
'itop-themes-compat/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -227,6 +227,11 @@
|
||||
<callback>OnLinksChangedTicket</callback>
|
||||
<rank>0</rank>
|
||||
</event_listener>
|
||||
<event_listener id="UpdateImpactAnalysisLocal">
|
||||
<event>EVENT_DB_BEFORE_WRITE</event>
|
||||
<callback>OnBeforeWriteTicket</callback>
|
||||
<rank>0</rank>
|
||||
</event_listener>
|
||||
</event_listeners>
|
||||
<methods>
|
||||
<method id="OnLinksChangedTicket">
|
||||
@@ -237,6 +242,20 @@
|
||||
public function OnLinksChangedTicket(Combodo\iTop\Service\Events\EventData $oEventData)
|
||||
{
|
||||
$this->UpdateImpactedItems();
|
||||
}
|
||||
]]></code>
|
||||
</method>
|
||||
<method id="OnBeforeWriteTicket">
|
||||
<static>false</static>
|
||||
<access>public</access>
|
||||
<type>EventListener</type>
|
||||
<code><![CDATA[
|
||||
public function OnBeforeWriteTicket(Combodo\iTop\Service\Events\EventData $oEventData)
|
||||
{
|
||||
$aChanges = $this->ListChanges();
|
||||
if ($this->IsNew() || array_key_exists('functionalcis_list', $aChanges) || array_key_exists('contacts_list', $aChanges)) {
|
||||
$this->UpdateImpactedItems();
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</method>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__,
|
||||
'itop-tickets/3.1.1',
|
||||
'itop-tickets/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-virtualization-mgmt/3.1.1',
|
||||
'itop-virtualization-mgmt/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'itop-welcome-itil/3.1.1',
|
||||
'itop-welcome-itil/3.1.3',
|
||||
array(
|
||||
// Identification
|
||||
//
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<information>
|
||||
<version>3.1.1</version>
|
||||
<version>3.1.3</version>
|
||||
</information>
|
||||
|
||||
@@ -74,7 +74,7 @@ try
|
||||
// First check if we can redirect the route to a dedicated controller
|
||||
$sRoute = utils::ReadParam('route', '', false, utils::ENUM_SANITIZATION_FILTER_ROUTE);
|
||||
$oRouter = Router::GetInstance();
|
||||
if ($oRouter->CanDispatchRoute($sRoute)) {
|
||||
if ($operation === '' && $oRouter->CanDispatchRoute($sRoute)) {
|
||||
$mResponse = $oRouter->DispatchRoute($sRoute);
|
||||
|
||||
// If response isn't a \WebPage, it is most likely that the output already occured, stop the script.
|
||||
@@ -767,12 +767,12 @@ try
|
||||
$sClass = utils::ReadParam('className', '', false, 'class');
|
||||
$sRootClass = utils::ReadParam('baseClass', '', false, 'class');
|
||||
$currentId = utils::ReadParam('currentId', '');
|
||||
$sTableId = utils::ReadParam('_table_id_', null, false, 'raw_data');
|
||||
$sTableId = utils::ReadParam('_table_id_', null, false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
|
||||
$sAction = utils::ReadParam('action', '');
|
||||
$sSelectionMode = utils::ReadParam('selection_mode', null, false, 'raw_data');
|
||||
$sResultListOuterSelector = utils::ReadParam('result_list_outer_selector', null, false, 'raw_data');
|
||||
$scssCount = utils::ReadParam('css_count', null, false, 'raw_data');
|
||||
$sTableInnerId = utils::ReadParam('table_inner_id', $sTableId, false, 'raw_data');
|
||||
$sSelectionMode = utils::ReadParam('selection_mode');
|
||||
$sResultListOuterSelector = utils::ReadParam('result_list_outer_selector', null,false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER); // actually an Id not a selector
|
||||
$scssCount = utils::ReadParam('css_count', null,false,utils::ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR);
|
||||
$sTableInnerId = utils::ReadParam('table_inner_id', null,false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
|
||||
|
||||
$oFilter = new DBObjectSearch($sClass);
|
||||
$oSet = new CMDBObjectSet($oFilter);
|
||||
@@ -1097,31 +1097,28 @@ EOF
|
||||
$aUpdatedDecoded[] = $sDecodedProp;
|
||||
}
|
||||
|
||||
$oDashlet->FromParams($aCurrentValues);
|
||||
$sPrevClass = get_class($oDashlet);
|
||||
$oDashlet = $oDashlet->Update($aValues, $aUpdatedDecoded);
|
||||
$sNewClass = get_class($oDashlet);
|
||||
if ($sNewClass != $sPrevClass) {
|
||||
$oPage->add_ready_script("$('#dashlet_$sDashletId').dashlet('option', {dashlet_class: '$sNewClass'});");
|
||||
}
|
||||
if ($oDashlet->IsRedrawNeeded()) {
|
||||
$oBlock = $oDashlet->DoRender($oPage, true, false, $aExtraParams);
|
||||
$sHtml = ConsoleBlockRenderer::RenderBlockTemplateInPage($oPage, $oBlock);
|
||||
$sHtml = str_replace("\n", '', $sHtml);
|
||||
$sHtml = str_replace("\r", '', $sHtml);
|
||||
$sHtml = str_replace("'", "\'", $sHtml);
|
||||
$oPage->add_script("$('#dashlet_$sDashletId').html('$sHtml');");
|
||||
}
|
||||
if ($oDashlet->IsFormRedrawNeeded()) {
|
||||
$oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed
|
||||
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property', 'extra_params' => $aExtraParams));
|
||||
$sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true, '.itop-dashboard'));
|
||||
$sHtml = str_replace("\n", '', $sHtml);
|
||||
$sHtml = str_replace("\r", '', $sHtml);
|
||||
$oPage->add_script("$('#dashlet_properties_$sDashletId').html('$sHtml')");
|
||||
}
|
||||
$oDashlet->FromParams($aCurrentValues);
|
||||
$sPrevClass = get_class($oDashlet);
|
||||
$oDashlet = $oDashlet->Update($aValues, $aUpdatedDecoded);
|
||||
$sNewClass = get_class($oDashlet);
|
||||
if ($sNewClass != $sPrevClass) {
|
||||
$oPage->add_ready_script("$('#dashlet_$sDashletId').dashlet('option', {dashlet_class: '$sNewClass'});");
|
||||
}
|
||||
break;
|
||||
if ($oDashlet->IsRedrawNeeded()) {
|
||||
$oBlock = $oDashlet->DoRender($oPage, true, false, $aExtraParams);
|
||||
$sHtml = ConsoleBlockRenderer::RenderBlockTemplateInPage($oPage, $oBlock);
|
||||
$sHtml= json_encode($sHtml);
|
||||
$oPage->add_script("$('#dashlet_$sDashletId').html({$sHtml});");
|
||||
}
|
||||
if ($oDashlet->IsFormRedrawNeeded()) {
|
||||
$oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed
|
||||
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property', 'extra_params' => $aExtraParams));
|
||||
$sHtml = $oForm->RenderAsPropertySheet($oPage, true, '.itop-dashboard');
|
||||
$sHtml= json_encode($sHtml);
|
||||
$oPage->add_script("$('#dashlet_properties_$sDashletId').html({$sHtml});");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'dashlet_creation_dlg':
|
||||
$sOQL = utils::ReadParam('oql', '', false, 'raw_data');
|
||||
|
||||
@@ -245,11 +245,11 @@ JS
|
||||
$aMoreInfoBlocks = [];
|
||||
|
||||
$oDevelopedQuerySet = new FieldSet(Dict::S('UI:RunQuery:DevelopedQuery'));
|
||||
$oDevelopedQuerySet->AddSubBlock(UIContentBlockUIBlockFactory::MakeForCode(utils::EscapeHtml($oFilter->ToOQL())));
|
||||
$oDevelopedQuerySet->AddSubBlock(UIContentBlockUIBlockFactory::MakeForCode($oFilter->ToOQL()));
|
||||
$aMoreInfoBlocks[] = $oDevelopedQuerySet;
|
||||
|
||||
$oSerializedQuerySet = new FieldSet(Dict::S('UI:RunQuery:SerializedFilter'));
|
||||
$oSerializedQuerySet->AddSubBlock(UIContentBlockUIBlockFactory::MakeForCode(utils::EscapeHtml($oFilter->serialize())));
|
||||
$oSerializedQuerySet->AddSubBlock(UIContentBlockUIBlockFactory::MakeForCode($oFilter->serialize()));
|
||||
$aMoreInfoBlocks[] = $oSerializedQuerySet;
|
||||
|
||||
|
||||
|
||||
@@ -343,6 +343,8 @@ function DisplayEvents(WebPage $oPage, $sClass)
|
||||
}
|
||||
}
|
||||
$sListener = $sListenerClass.'->'.$aListener['callback'][1].'(\Combodo\iTop\Service\Events\EventData $oEventData)';
|
||||
} else if (is_array($aListener['callback'])) {
|
||||
$sListener = $aListener['callback'][0].'::'.$aListener['callback'][1];
|
||||
} else {
|
||||
$sListener = $aListener['callback'].'(\Combodo\iTop\Service\Events\EventData $oEventData)';
|
||||
}
|
||||
|
||||
@@ -1165,6 +1165,7 @@ EOF
|
||||
*/
|
||||
protected function QuoteForPHP($sStr, $bSimpleQuotes = false)
|
||||
{
|
||||
$sStr = $sStr ?? '';
|
||||
if ($bSimpleQuotes)
|
||||
{
|
||||
$sEscaped = str_replace(array('\\', "'"), array('\\\\', "\\'"), $sStr);
|
||||
@@ -3229,10 +3230,11 @@ EOF;
|
||||
|
||||
$aEntriesPHP = array();
|
||||
$oEntries = $oDictionaryNode->GetUniqueElement('entries');
|
||||
/** @var MFElement $oEntry */
|
||||
foreach ($oEntries->getElementsByTagName('entry') as $oEntry)
|
||||
{
|
||||
$sStringCode = $oEntry->getAttribute('id');
|
||||
$sValue = $oEntry->GetText();
|
||||
$sValue = $oEntry->GetText('');
|
||||
$aEntriesPHP[] = "\t'$sStringCode' => ".self::QuoteForPHP(self::FilterDictString($sValue), true).",";
|
||||
}
|
||||
$sEntriesPHP = implode("\n", $aEntriesPHP);
|
||||
@@ -3267,7 +3269,7 @@ EOF;
|
||||
file_put_contents($sLanguagesFile, $sLanguagesFileContent);
|
||||
}
|
||||
|
||||
protected static function FilterDictString($s)
|
||||
protected static function FilterDictString(string $s): string
|
||||
{
|
||||
if (strpos($s, '~') !== false)
|
||||
{
|
||||
|
||||
@@ -1295,6 +1295,12 @@ EOF
|
||||
$aResult['checks'][] = new CheckResult(CheckResult::INFO, "MySQL server's max_connections is set to $iMaxConnections.");
|
||||
}
|
||||
|
||||
$iClusters = $oDBSource->GetClusterNb();
|
||||
if ($iClusters > 0) {
|
||||
SetupLog::Warning('Warning - Using Galera will cause malfunctions and data corruptions. Combodo does not support this type of infrastructure.');
|
||||
$aResult['checks'][] = new CheckResult(CheckResult::WARNING, 'Using Galera will cause malfunctions and data corruptions. Combodo does not support this type of infrastructure.');
|
||||
}
|
||||
|
||||
try {
|
||||
$aResult['databases'] = $oDBSource->ListDB();
|
||||
}
|
||||
|
||||
@@ -2,24 +2,72 @@
|
||||
|
||||
This script allows to install and update iTop via CLI.
|
||||
|
||||
For more information, see the official Wiki : [Automated installation [iTop Documentation]](https://www.itophub.io/wiki/page?id=latest:advancedtopics:automatic_install)
|
||||
For more information, see the official Wiki : [Automated installation [iTop Documentation]](https://www.itophub.io/wiki/page?id=latest:advancedtopics:automatic_install)
|
||||
|
||||
# unattended-install.php
|
||||
|
||||
## Usage
|
||||
|
||||
Execution of the unattended installation
|
||||
> Note:
|
||||
> Because the installation runs from the command line, make sure that the current user has enough rights to access the different locations and that the web server will be able to access the files and directories created during the scripted installation. In order to exactly emulate the behavior of
|
||||
the interactive installation it may be a good practice to run this installation from the user account used for running the web server process.
|
||||
|
||||
Launch the script with the following command: ```bash php unattended_install.php --param-file=fresh-install.xml ```
|
||||
|
||||
Where: `fresh-install.xml` is the response file containing your desired settings for the installation (there are 4 models available in the folder `xml_setup`: fresh-install.xml, itil-fresh-install.xml, itil-upgrade.xml, upgrade.xml)
|
||||
|
||||
Fresh installation parameters
|
||||
> Important:
|
||||
> In the case of a fresh installation (<mode>install</mode>), do not forget to complete below mandatory parameters before:
|
||||
|
||||
```xml
|
||||
<database>
|
||||
<server></server>
|
||||
<user></user>
|
||||
<pwd></pwd>
|
||||
<name></name>
|
||||
<db_tls_enabled></db_tls_enabled>
|
||||
<db_tls_ca></db_tls_ca>
|
||||
<prefix></prefix>
|
||||
</database>
|
||||
<url>
|
||||
</url>
|
||||
<graphviz_path>/usr/bin/dot</graphviz_path>
|
||||
<admin_account>
|
||||
<user></user>
|
||||
<pwd></pwd>
|
||||
<language></language>
|
||||
</admin_account>
|
||||
<language></language>
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
To get all available options of the script, you can perform the following command :
|
||||
```php unattended-install.php --help```
|
||||
|
||||
# install-itop.sh
|
||||
|
||||
## Usage
|
||||
|
||||
#install-itop.sh
|
||||
You can install your iTop by only using config-itop.php settings and run either
|
||||
|
||||
- a non-ITIL iTop fresh installation (use itil-fresh-install.xml to have ITIL modules instead)
|
||||
|
||||
```
|
||||
./install-itop.sh ./xml_setup/fresh-install.xml
|
||||
```
|
||||
|
||||
- a non-ITIL iTop upgrade (use itil-upgrade.xml to have ITIL modules instead)
|
||||
|
||||
```
|
||||
./install-itop.sh ./xml_setup/upgrade.xml
|
||||
```
|
||||
|
||||
- a specific iTop installation by providing both xml setup file
|
||||
in below example file provided is the one generated by iTop during last setup.
|
||||
in below example file provided is the one generated by iTop during last setup.
|
||||
|
||||
```
|
||||
./install-itop.sh ../../log/install-2024-04-03.xml
|
||||
```
|
||||
|
||||
@@ -25,7 +25,9 @@ EOF;
|
||||
exit(-1);
|
||||
}
|
||||
/////////////////////////////////////////////////
|
||||
if (! utils::IsModeCLI())
|
||||
|
||||
$sCleanName = strtolower(trim(PHP_SAPI));
|
||||
if ($sCleanName !== 'cli')
|
||||
{
|
||||
echo "Mode CLI only";
|
||||
exit(-1);
|
||||
|
||||
@@ -2589,7 +2589,13 @@ class WizStepDone extends WizardStep
|
||||
$oProductionEnv->InitDataModel($oConfig, true);
|
||||
$sIframeUrl = $oConfig->GetModuleSetting('itop-hub-connector', 'setup_url', '');
|
||||
|
||||
if ($sIframeUrl != '') {
|
||||
$sSetupTokenFile = APPROOT.'data/.setup';
|
||||
$sSetupToken = bin2hex(random_bytes(12));
|
||||
file_put_contents($sSetupTokenFile, $sSetupToken);
|
||||
$sIframeUrl.= "&setup_token=$sSetupToken";
|
||||
|
||||
if ($sIframeUrl != '')
|
||||
{
|
||||
$oPage->add('<iframe id="fresh_content" frameborder="0" scrolling="auto" src="'.$sIframeUrl.'"></iframe>');
|
||||
|
||||
$oPage->add_script("
|
||||
|
||||
@@ -27,6 +27,7 @@ use DBObject;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use Exception;
|
||||
use ExecutionKPI;
|
||||
use IssueLog;
|
||||
use MetaModel;
|
||||
|
||||
@@ -58,6 +59,7 @@ class ActivityPanelFactory
|
||||
*/
|
||||
public static function MakeForObjectDetails(DBObject $oObject, string $sMode = cmdbAbstractObject::DEFAULT_DISPLAY_MODE)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$sObjClass = get_class($oObject);
|
||||
$sObjId = $oObject->GetKey();
|
||||
|
||||
@@ -171,6 +173,8 @@ class ActivityPanelFactory
|
||||
}
|
||||
}
|
||||
|
||||
$oKPI->ComputeStatsForExtension(new ActivityPanelFactory(), 'MakeForObjectDetails');
|
||||
|
||||
return $oActivityPanel;
|
||||
}
|
||||
}
|
||||
@@ -45,33 +45,45 @@ class UIContentBlockUIBlockFactory extends AbstractUIBlockFactory
|
||||
* The \n are replaced by <br>
|
||||
*
|
||||
* @api
|
||||
* @param string $sCode
|
||||
* @param string $sCode plain text code
|
||||
* @param string|null $sId
|
||||
*
|
||||
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
|
||||
*/
|
||||
public static function MakeForCode(string $sCode, string $sId = null)
|
||||
{
|
||||
$oCode = new UIContentBlock($sId, ['ibo-is-code']);
|
||||
$sCode = str_replace("\n", '<br>', $sCode);
|
||||
$oCode->AddSubBlock(new Html($sCode));
|
||||
$sCode = str_replace("\n", '<br>', \utils::HtmlEntities($sCode));
|
||||
|
||||
return $oCode;
|
||||
return self::MakeFromHTMLCode($sId, $sCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to display a block of preformatted text in a <pre> tag.
|
||||
*
|
||||
* @api
|
||||
* @param string $sCode
|
||||
* @param string $sCode plain text code
|
||||
* @param string|null $sId
|
||||
*
|
||||
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
|
||||
*/
|
||||
public static function MakeForPreformatted(string $sCode, string $sId = null)
|
||||
{
|
||||
$sCode = '<pre>'.$sCode.'</pre>';
|
||||
$sCode = '<pre>'.\utils::HtmlEntities($sCode).'</pre>';
|
||||
|
||||
return static::MakeForCode($sCode, $sId);
|
||||
return self::MakeFromHTMLCode($sId, $sCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $sId
|
||||
* @param string $sCode
|
||||
*
|
||||
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
|
||||
*/
|
||||
private static function MakeFromHTMLCode(?string $sId, string $sCode): UIContentBlock
|
||||
{
|
||||
$oCode = new UIContentBlock($sId, ['ibo-is-code']);
|
||||
$oCode->AddSubBlock(new Html($sCode));
|
||||
|
||||
return $oCode;
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ class BlockList extends UIContentBlock
|
||||
{
|
||||
return '$("#'.$this->sId.'").block();
|
||||
$.post("ajax.render.php?operation=refreshDashletList",
|
||||
{ style: "list", filter: "'.$this->sFilter.'", extra_params: '.json_encode($this->aExtraParams).' },
|
||||
{ style: "list", filter: '.json_encode($this->sFilter).', extra_params: '.json_encode($this->aExtraParams).' },
|
||||
function(data){
|
||||
$("#'.$this->sId.'")
|
||||
.empty()
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory;
|
||||
use Laminas\Mail\Header\ContentType;
|
||||
use Laminas\Mail\Header\InReplyTo;
|
||||
use Laminas\Mail\Header\MessageId;
|
||||
use Laminas\Mail\Message;
|
||||
use Laminas\Mail\Protocol\Smtp\Auth\Oauth;
|
||||
use Laminas\Mail\Transport\File;
|
||||
@@ -331,11 +332,11 @@ class EMailLaminas extends Email
|
||||
{
|
||||
$this->m_aData['message_id'] = $sId;
|
||||
|
||||
// Note: Swift will add the angle brackets for you
|
||||
// Note: The email library will add the angle brackets for you
|
||||
// so let's remove the angle brackets if present, for historical reasons
|
||||
$sId = str_replace(array('<', '>'), '', $sId);
|
||||
|
||||
$this->m_oMessage->getHeaders()->addHeaderLine('Message-ID', $sId);
|
||||
$this->m_oMessage->getHeaders()->addHeader((new MessageId())->setId($sId));
|
||||
}
|
||||
|
||||
public function SetReferences($sReferences)
|
||||
@@ -354,7 +355,11 @@ class EMailLaminas extends Email
|
||||
*/
|
||||
public function SetInReplyTo(string $sMessageId)
|
||||
{
|
||||
$this->AddToHeader('In-Reply-To', $sMessageId);
|
||||
// Note: Laminas will add the angle brackets for you
|
||||
// so let's remove the angle brackets if present, for historical reasons
|
||||
$sId = str_replace(array('<', '>'), '', $sMessageId);
|
||||
|
||||
$this->m_oMessage->getHeaders()->addHeader((new InReplyTo())->setIds([$sId]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -398,19 +403,6 @@ class EMailLaminas extends Email
|
||||
$oBody->addPart($oAdditionalPart);
|
||||
}
|
||||
|
||||
if ($oBody->isMultiPart()) {
|
||||
$oContentTypeHeader = $this->m_oMessage->getHeaders();
|
||||
foreach ($oContentTypeHeader as $oHeader) {
|
||||
if (!$oHeader instanceof ContentType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$oHeader->setType(Mime::MULTIPART_MIXED);
|
||||
$oHeader->addParameter('boundary', $oBody->getMime()->boundary());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->m_oMessage->setBody($oBody);
|
||||
}
|
||||
|
||||
@@ -431,22 +423,13 @@ class EMailLaminas extends Email
|
||||
$oNewPart = new Part($sText);
|
||||
$oNewPart->encoding = Mime::ENCODING_8BIT;
|
||||
$oNewPart->type = $sMimeType;
|
||||
$this->m_oMessage->getBody()->addPart($oNewPart);
|
||||
|
||||
// setBody called only to refresh Content-Type to multipart/mixed
|
||||
$this->m_oMessage->setBody($this->m_oMessage->getBody()->addPart($oNewPart));
|
||||
}
|
||||
|
||||
public function AddAttachment($data, $sFileName, $sMimeType)
|
||||
{
|
||||
$oBody = $this->m_oMessage->getBody();
|
||||
|
||||
if (!$oBody->isMultiPart()) {
|
||||
$multipart_content = new Part($oBody->generateMessage());
|
||||
$multipart_content->setType($oBody->getParts()[0]->getType());
|
||||
$multipart_content->setBoundary($oBody->getMime()->boundary());
|
||||
|
||||
$oBody = new Laminas\Mime\Message();
|
||||
$oBody->addPart($multipart_content);
|
||||
}
|
||||
|
||||
if (!array_key_exists('attachments', $this->m_aData)) {
|
||||
$this->m_aData['attachments'] = array();
|
||||
}
|
||||
@@ -457,23 +440,8 @@ class EMailLaminas extends Email
|
||||
$oNewAttachment->disposition = Mime::DISPOSITION_ATTACHMENT;
|
||||
$oNewAttachment->encoding = Mime::ENCODING_BASE64;
|
||||
|
||||
|
||||
$oBody->addPart($oNewAttachment);
|
||||
|
||||
if ($oBody->isMultiPart()) {
|
||||
$oContentTypeHeader = $this->m_oMessage->getHeaders();
|
||||
foreach ($oContentTypeHeader as $oHeader) {
|
||||
if (!$oHeader instanceof ContentType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$oHeader->setType(Mime::MULTIPART_MIXED);
|
||||
$oHeader->addParameter('boundary', $oBody->getMime()->boundary());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->m_oMessage->setBody($oBody);
|
||||
// setBody called only to refresh Content-Type to multipart/mixed
|
||||
$this->m_oMessage->setBody($this->m_oMessage->getBody()->addPart($oNewAttachment));
|
||||
}
|
||||
|
||||
public function SetSubject($sSubject)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
namespace Combodo\iTop\Form;
|
||||
|
||||
use Combodo\iTop\Renderer\FormRenderer;
|
||||
use CoreException;
|
||||
|
||||
/**
|
||||
* Description of formmanager
|
||||
@@ -59,6 +60,12 @@ abstract class FormManager
|
||||
$oFormManager = new static();
|
||||
|
||||
$sFormRendererClass = $aJson['formrenderer_class'];
|
||||
// N°7455 - Ensure form renderer class extends FormRenderer
|
||||
if (false === is_a($sFormRendererClass, FormRenderer::class, true))
|
||||
{
|
||||
throw new CoreException('Form renderer class must extend '.FormRenderer::class);
|
||||
}
|
||||
|
||||
/** @var \Combodo\iTop\Renderer\FormRenderer $oFormRenderer */
|
||||
$oFormRenderer = new $sFormRendererClass();
|
||||
$oFormRenderer->SetEndpoint($aJson['formrenderer_endpoint']);
|
||||
|
||||
@@ -768,6 +768,15 @@ JS
|
||||
*/
|
||||
protected function InjectRendererFileAssets(string $sClass, array $aAttributesCodesToDisplay, $oOutput)
|
||||
{
|
||||
// handle abstract class
|
||||
while(MetaModel::IsAbstract($sClass)){
|
||||
$aChildClasses = MetaModel::EnumChildClasses($sClass);
|
||||
if(count($aChildClasses) > 0){
|
||||
$sClass = $aChildClasses[0];
|
||||
}
|
||||
}
|
||||
|
||||
// create a fake object to pass to renderers for retrieving global assets
|
||||
$oItem = MetaModel::NewObject($sClass);
|
||||
|
||||
// Iterate throw attributes...
|
||||
@@ -776,10 +785,13 @@ JS
|
||||
// Retrieve attribute definition
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
|
||||
// make form field from attribute
|
||||
$oField = $oAttDef->MakeFormField($oItem);
|
||||
|
||||
// retrieve the form field renderer
|
||||
$sFieldRendererClass = static::GetFieldRendererClass($oField);
|
||||
|
||||
// retrieve renderer global assets
|
||||
if ($sFieldRendererClass !== null) {
|
||||
/** @var FieldRenderer $oFieldRenderer */
|
||||
$oFieldRenderer = new $sFieldRendererClass($oField);
|
||||
|
||||
@@ -592,6 +592,7 @@ EOF
|
||||
*/
|
||||
private static function GetDivAlert(string $message): string
|
||||
{
|
||||
$message = utils::EscapeHtml($message);
|
||||
return "<div class=\"ibo-csv-import--cell-error ibo-csv-import--cell-message\">$message</div>\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ class ModuleService
|
||||
$sExtension = $this->GetModuleNameFromObject($oReflectionClass->getName());
|
||||
if (strlen($sExtension) !== 0) {
|
||||
$sSignature .= '['.$sExtension.'] ';
|
||||
} else {
|
||||
$sSignature .= '[core] ';
|
||||
}
|
||||
$sSignature .= $oReflectionClass->getShortName().'::'.$sMethod.'()';
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ let oWidget{{ oUIBlock.GetId() }} = $('#{{ oUIBlock.GetId() }}').selectize({
|
||||
// Filter old options data to keep selected values
|
||||
// (options with force flag will be kept event if they doesn't be part of the current value)
|
||||
let options = Object.values(me.options);
|
||||
me.optionsBeforeFilter = options;
|
||||
options = options.filter(item => (typeof(item.force) !== "undefined" && item.force === true) || aSelectedItems.includes(item['{{ oDataProvider.GetDataValueField() }}']));
|
||||
// Merge kept and new values
|
||||
options = $.merge(options, res.data.search_data);
|
||||
@@ -199,6 +200,11 @@ let oWidget{{ oUIBlock.GetId() }} = $('#{{ oUIBlock.GetId() }}').selectize({
|
||||
CombodoTooltip.InitTooltipFromMarkup($item);
|
||||
},
|
||||
|
||||
onBlur: function(){
|
||||
this.clearOptionGroups()
|
||||
this.addOption(this.optionsBeforeFilter)
|
||||
},
|
||||
|
||||
{# plugin combodo_add_button #}
|
||||
{% if oUIBlock.HasAddOptionButton() and oUIBlock.HasAddOptionButtonJsOnClick() %}
|
||||
onAdd: function(){
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div id="login-content">
|
||||
<h1>{{ 'UI:ResetPwd-Title'|dict_s }}</h1>
|
||||
{% if bNoUser %}
|
||||
<p>{{ 'UI:ResetPwd-Error-WrongLogin'|dict_format(sAuthUser) }}</p>
|
||||
<p>{{ 'UI:ResetPwd-EmailSent'|dict_s }}</p>
|
||||
{% elseif bBadToken %}
|
||||
<p>{{ 'UI:ResetPwd-Error-InvalidToken'|dict_s }}</p>
|
||||
{% else %}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div id="login-title">
|
||||
<h1>{{ 'UI:ResetPwd-Title'|dict_s }}</h1>
|
||||
{% if bNoUser and sErrorMessage is null %}
|
||||
<p>{{ 'UI:ResetPwd-Error-WrongLogin'|dict_format(sAuthUser) }}</p>
|
||||
<p>{{ 'UI:ResetPwd-EmailSent'|dict_s }}</p>
|
||||
{% elseif bBadToken and sErrorMessage is null %}
|
||||
<p>{{ 'UI:ResetPwd-Error-InvalidToken'|dict_s }}</p>
|
||||
{% else %}
|
||||
|
||||
@@ -41,6 +41,7 @@ use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\Object\ObjectFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\PageContent\PageContentFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockWithJSRefreshCallback;
|
||||
use iTopWebPage;
|
||||
use LoginWebPage;
|
||||
@@ -355,6 +356,22 @@ $oDashletFieldset2->AddSubBlock($oDashletField4);
|
||||
$oDashletFieldset2->AddSubBlock($oDashletField5);
|
||||
$oDashletFieldset2->AddSubBlock($oDashletField6);
|
||||
|
||||
/////////
|
||||
// Code
|
||||
/////////
|
||||
|
||||
$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral('Code examples (MakeForCode)', 2 ));
|
||||
$oCode1 = UIContentBlockUIBlockFactory::MakeForCode('function mean(int $a, int $b) {
|
||||
return ($a + $b)/2
|
||||
}');
|
||||
$oPage->AddUiBlock($oCode1);
|
||||
|
||||
$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral('Code examples (MakeForPreformatted)', 2 ));
|
||||
$oCode2 = UIContentBlockUIBlockFactory::MakeForPreformatted('function mean(int $a, int $b) {
|
||||
return ($a + $b)/2
|
||||
}');
|
||||
$oPage->AddUiBlock($oCode2);
|
||||
|
||||
/////////
|
||||
// Pill
|
||||
/////////
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"sempro/phpunit-pretty-print": "^1.4"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"unitary-tests/"
|
||||
],
|
||||
"psr-4": {
|
||||
"Combodo\\iTop\\Test\\UnitTest\\": "src/BaseTestCase/",
|
||||
"Combodo\\iTop\\Test\\UnitTest\\Hook\\": "src/Hook/",
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
printerClass="\Sempro\PHPUnitPrettyPrinter\PrettyPrinterForPhpUnit9"
|
||||
>
|
||||
|
||||
<extensions>
|
||||
<extension class="Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook" />
|
||||
</extensions>
|
||||
|
||||
<php>
|
||||
<ini name="error_reporting" value="E_ALL"/>
|
||||
<ini name="display_errors" value="On"/>
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
printerClass="\Sempro\PHPUnitPrettyPrinter\PrettyPrinterForPhpUnit9"
|
||||
>
|
||||
|
||||
<extensions>
|
||||
<extension class="Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook" />
|
||||
</extensions>
|
||||
|
||||
<php>
|
||||
<ini name="memory_limit" value="512M"/>
|
||||
<ini name="error_reporting" value="E_ALL"/>
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
printerClass="\Sempro\PHPUnitPrettyPrinter\PrettyPrinterForPhpUnit9"
|
||||
>
|
||||
|
||||
<extensions>
|
||||
<extension class="Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook" />
|
||||
</extensions>
|
||||
|
||||
<php>
|
||||
<ini name="error_reporting" value="E_ALL"/>
|
||||
<ini name="display_errors" value="On"/>
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
namespace Combodo\iTop\Test\UnitTest;
|
||||
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook;
|
||||
use Combodo\iTop\Test\UnitTest\Service\UnitTestRunTimeEnvironment;
|
||||
use Config;
|
||||
use Exception;
|
||||
@@ -30,9 +29,9 @@ use utils;
|
||||
abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
{
|
||||
/**
|
||||
* @var bool[]
|
||||
*/
|
||||
protected static $aReadyCustomEnvironments = [];
|
||||
* @var UnitTestRunTimeEnvironment
|
||||
*/
|
||||
protected $oEnvironment = null;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
@@ -50,11 +49,19 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
$this->setRunClassInSeparateProcess(true);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @return string Abs path to the XML delta to use for the tests of that class
|
||||
*/
|
||||
abstract public function GetDatamodelDeltaAbsPath(): string;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
static::LoadRequiredItopFiles();
|
||||
$this->oEnvironment = new UnitTestRunTimeEnvironment('production', $this->GetTestEnvironment());
|
||||
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@@ -92,40 +99,16 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} as ready (compiled)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function MarkEnvironmentReady(): void
|
||||
{
|
||||
if (false === $this->IsEnvironmentReady()) {
|
||||
touch(static::GetTestEnvironmentFolderAbsPath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True if the {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} is ready (compiled, but not started)
|
||||
*
|
||||
* @details Having the environment ready means that it has been compiled for this global tests run, not that it is a relic from a previous global tests run
|
||||
* @return bool True if the {@see \Combodo\iTop\Test\UnitTest\ItopDataTestCase::GetTestEnvironment()} is ready (compiled, up-to-date, but not necessarily started)
|
||||
*/
|
||||
final protected function IsEnvironmentReady(): bool
|
||||
{
|
||||
// As these test cases run in separate processes, the best way we found to let know a process if its environment was already prepared for **this run** was to compare the modification times of:
|
||||
// - its own env-<ENV> folder
|
||||
// - a file generated at the beginning of the global test run {@see \Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook}
|
||||
$sRunStartedFilePath = TestsRunStartHook::GetRunStartedFileAbsPath();
|
||||
$sEnvFolderPath = static::GetTestEnvironmentFolderAbsPath();
|
||||
|
||||
clearstatcache();
|
||||
if (false === file_exists($sRunStartedFilePath) || false === file_exists($sEnvFolderPath)) {
|
||||
if (false === file_exists($this->GetTestEnvironmentFolderAbsPath())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$iRunStartedFileModificationTime = filemtime($sRunStartedFilePath);
|
||||
$iEnvFolderModificationTime = filemtime($sEnvFolderPath);
|
||||
|
||||
return $iEnvFolderModificationTime >= $iRunStartedFileModificationTime;
|
||||
}
|
||||
return $this->oEnvironment->IsUpToDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
@@ -140,6 +123,12 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
// Note: To improve performances, we compile all XML deltas from test cases derived from this class and make a single environment where everything will be ran at once.
|
||||
// This requires XML deltas to be compatible, but it is a known and accepted trade-off. See PR #457
|
||||
if (false === $this->IsEnvironmentReady()) {
|
||||
|
||||
$this->debug("Preparing custom environment '$sTestEnv' with the following datamodel files:");
|
||||
foreach ($this->oEnvironment->GetCustomDatamodelFiles() as $sCustomDatamodelFile) {
|
||||
$this->debug(" - $sCustomDatamodelFile");
|
||||
}
|
||||
|
||||
//----------------------------------------------------
|
||||
// Clear any previous "$sTestEnv" environment
|
||||
//----------------------------------------------------
|
||||
@@ -152,14 +141,6 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
SetupUtils::tidydir($sConfFolder);
|
||||
}
|
||||
|
||||
// - Datamodel delta files
|
||||
// - Cache folder
|
||||
// - Compiled folder
|
||||
// We don't need to clean them as they are already by the compilation
|
||||
|
||||
// - Drop database
|
||||
// We don't do that now, it will be done before re-creating the DB, once the metamodel is started
|
||||
|
||||
//----------------------------------------------------
|
||||
// Prepare "$sTestEnv" environment
|
||||
//----------------------------------------------------
|
||||
@@ -178,7 +159,7 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
$oTestConfig->Set('db_name', $oTestConfig->Get('db_name').'_'.$sTestEnvSanitizedForDBName);
|
||||
|
||||
// - Compile env. based on the existing 'production' env.
|
||||
$oEnvironment = new UnitTestRunTimeEnvironment($sTestEnv);
|
||||
$oEnvironment = new UnitTestRunTimeEnvironment($sSourceEnv, $sTestEnv);
|
||||
$oEnvironment->WriteConfigFileSafe($oTestConfig);
|
||||
$oEnvironment->CompileFrom($sSourceEnv);
|
||||
|
||||
@@ -192,8 +173,7 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
// N°7446 For some reason we need to create the DB schema before starting the MM, then only we can create the tables.
|
||||
MetaModel::DBCreate();
|
||||
|
||||
$this->MarkEnvironmentReady();
|
||||
$this->debug('Preparation of custom environment "'.$sTestEnv.'" done.');
|
||||
$this->debug("Custom environment '$sTestEnv' is ready!");
|
||||
}
|
||||
|
||||
parent::PrepareEnvironment();
|
||||
|
||||
@@ -50,7 +50,7 @@ abstract class ItopTestCase extends TestCase
|
||||
|
||||
static::$DEBUG_UNIT_TEST = getenv('DEBUG_UNIT_TEST');
|
||||
|
||||
require_once static::GetAppRoot() . 'approot.inc.php';
|
||||
require_once __DIR__.'/../../../../approot.inc.php';
|
||||
|
||||
if ((static::DISABLE_DEPRECATEDCALLSLOG_ERRORHANDLER)
|
||||
&& (false === defined(ITOP_PHPUNIT_RUNNING_CONSTANT_NAME))) {
|
||||
@@ -78,6 +78,7 @@ abstract class ItopTestCase extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
* @param string $sExportFileName relative to log folder
|
||||
@@ -93,43 +94,41 @@ abstract class ItopTestCase extends TestCase
|
||||
* @return string
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public static function ExportFunctionParameterValues(array $args, string $sExportFileName, array $aExcludedParams = []): string
|
||||
{
|
||||
public static function ExportFunctionParameterValues(array $args, string $sExportFileName, array $aExcludedParams = []): string
|
||||
{
|
||||
// get sclass et function dans la callstrack
|
||||
|
||||
// in the callstack get the call function name
|
||||
$aCallStack = debug_backtrace();
|
||||
$sCallFunction = $aCallStack[1]['function'];
|
||||
// in the casll stack get the call class name
|
||||
$sCallClass = $aCallStack[1]['class'];
|
||||
$reflectionFunc = new ReflectionMethod($sCallClass, $sCallFunction);
|
||||
$parameters = $reflectionFunc->getParameters();
|
||||
// in the callstack get the call function name
|
||||
$aCallStack = debug_backtrace();
|
||||
$sCallFunction = $aCallStack[1]['function'];
|
||||
// in the casll stack get the call class name
|
||||
$sCallClass = $aCallStack[1]['class'];
|
||||
$reflectionFunc = new ReflectionMethod($sCallClass, $sCallFunction);
|
||||
$parameters = $reflectionFunc->getParameters();
|
||||
|
||||
$aParamValues = [];
|
||||
foreach ($parameters as $index => $param) {
|
||||
$aParamValues[$param->getName()] = $args[$index] ?? null;
|
||||
}
|
||||
$aParamValues = [];
|
||||
foreach ($parameters as $index => $param) {
|
||||
$aParamValues[$param->getName()] = $args[$index] ?? null;
|
||||
}
|
||||
|
||||
$paramValues = $aParamValues;
|
||||
foreach ($aExcludedParams as $sExcludedParam) {
|
||||
unset($paramValues[$sExcludedParam]);
|
||||
}
|
||||
$paramValues = $aParamValues;
|
||||
foreach ($aExcludedParams as $sExcludedParam) {
|
||||
unset($paramValues[$sExcludedParam]);
|
||||
}
|
||||
|
||||
// extract oPage from the array in parameters and make a foreach on exlucded parameters
|
||||
foreach ($aExcludedParams as $sExcludedParam) {
|
||||
unset($paramValues[$sExcludedParam]);
|
||||
}
|
||||
// extract oPage from the array in parameters and make a foreach on exlucded parameters
|
||||
foreach ($aExcludedParams as $sExcludedParam) {
|
||||
unset($paramValues[$sExcludedParam]);
|
||||
}
|
||||
|
||||
$var_export = var_export($paramValues, true);
|
||||
file_put_contents(APPROOT.'/log/' .$sExportFileName, $var_export);
|
||||
$var_export = var_export($paramValues, true);
|
||||
file_put_contents(APPROOT.'/log/' .$sExportFileName, $var_export);
|
||||
return $var_export;
|
||||
}
|
||||
}
|
||||
|
||||
protected function setUp(): void {
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->debug("\n----------\n---------- ".$this->getName()."\n----------\n");
|
||||
|
||||
$this->LoadRequiredItopFiles();
|
||||
$this->LoadRequiredTestFiles();
|
||||
}
|
||||
@@ -170,6 +169,27 @@ abstract class ItopTestCase extends TestCase
|
||||
return $sAppRootPath . '/';
|
||||
}
|
||||
|
||||
private static function GetFirstDirUpContainingFile(string $sSearchPath, string $sFileToFindGlobPattern): ?string
|
||||
{
|
||||
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
|
||||
$aGlobFiles = glob($sSearchPath . '/' . $sFileToFindGlobPattern);
|
||||
if (is_array($aGlobFiles) && (count($aGlobFiles) > 0)) {
|
||||
return $sSearchPath . '/';
|
||||
}
|
||||
$iOffsetSep = strrpos($sSearchPath, '/');
|
||||
if ($iOffsetSep === false) {
|
||||
$iOffsetSep = strrpos($sSearchPath, '\\');
|
||||
if ($iOffsetSep === false) {
|
||||
// Do not throw an exception here as PHPUnit will not show it clearly when determing the list of test to perform
|
||||
return 'Could not find the approot file in ' . $sSearchPath;
|
||||
}
|
||||
}
|
||||
$sSearchPath = substr($sSearchPath, 0, $iOffsetSep);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overload this method to require necessary files through {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceItopFile()}
|
||||
*
|
||||
@@ -178,8 +198,9 @@ abstract class ItopTestCase extends TestCase
|
||||
*/
|
||||
protected function LoadRequiredItopFiles(): void
|
||||
{
|
||||
// Empty until we actually need to require some files in the class
|
||||
}
|
||||
// At least make sure that the autoloader will be loaded, and that the APPROOT constant is defined
|
||||
require_once __DIR__.'/../../../../approot.inc.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload this method to require necessary files through {@see \Combodo\iTop\Test\UnitTest\ItopTestCase::RequireOnceUnitTestFile()}
|
||||
@@ -206,23 +227,6 @@ abstract class ItopTestCase extends TestCase
|
||||
require_once $this->GetAppRoot() . $sFileRelPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to load a module file. The caller test must be in that module !
|
||||
* Will browse dir up to find a module.*.php
|
||||
*
|
||||
* @param string $sFileRelPath for example 'portal/src/Helper/ApplicationHelper.php'
|
||||
* @since 2.7.10 3.1.1 3.2.0 N°6709 method creation
|
||||
*/
|
||||
protected function RequireOnceCurrentModuleFile(string $sFileRelPath): void
|
||||
{
|
||||
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
|
||||
$sCallerFileFullPath = $aStack[0]['file'];
|
||||
$sCallerDir = dirname($sCallerFileFullPath);
|
||||
|
||||
$sModuleRootPath = static::GetFirstDirUpContainingFile($sCallerDir, 'module.*.php');
|
||||
require_once $sModuleRootPath . $sFileRelPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Require once a unit test file (eg. a mock class) from its relative path from the *current* dir.
|
||||
* This ensure that required files don't crash when unit tests dir is moved in the iTop structure (see N°5608)
|
||||
@@ -240,26 +244,6 @@ abstract class ItopTestCase extends TestCase
|
||||
require_once $sCallerDirAbsPath . DIRECTORY_SEPARATOR . $sFileRelPath;
|
||||
}
|
||||
|
||||
private static function GetFirstDirUpContainingFile(string $sSearchPath, string $sFileToFindGlobPattern): ?string
|
||||
{
|
||||
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
|
||||
$aGlobFiles = glob($sSearchPath . '/' . $sFileToFindGlobPattern);
|
||||
if (is_array($aGlobFiles) && (count($aGlobFiles) > 0)) {
|
||||
return $sSearchPath . '/';
|
||||
}
|
||||
$iOffsetSep = strrpos($sSearchPath, '/');
|
||||
if ($iOffsetSep === false) {
|
||||
$iOffsetSep = strrpos($sSearchPath, '\\');
|
||||
if ($iOffsetSep === false) {
|
||||
// Do not throw an exception here as PHPUnit will not show it clearly when determing the list of test to perform
|
||||
return 'Could not find the approot file in ' . $sSearchPath;
|
||||
}
|
||||
}
|
||||
$sSearchPath = substr($sSearchPath, 0, $iOffsetSep);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function debug($sMsg)
|
||||
{
|
||||
if (static::$DEBUG_UNIT_TEST) {
|
||||
@@ -402,11 +386,11 @@ abstract class ItopTestCase extends TestCase
|
||||
*/
|
||||
private function GetProperty(string $sClass, string $sProperty): \ReflectionProperty
|
||||
{
|
||||
$class = new \ReflectionClass($sClass);
|
||||
$property = $class->getProperty($sProperty);
|
||||
$property->setAccessible(true);
|
||||
$oClass = new \ReflectionClass($sClass);
|
||||
$oProperty = $oClass->getProperty($sProperty);
|
||||
$oProperty->setAccessible(true);
|
||||
|
||||
return $property;
|
||||
return $oProperty;
|
||||
}
|
||||
|
||||
|
||||
@@ -417,7 +401,7 @@ abstract class ItopTestCase extends TestCase
|
||||
*
|
||||
* @since 2.7.8 3.0.3 3.1.0
|
||||
*/
|
||||
public function SetNonPublicProperty(object $oObject, string $sProperty, $value)
|
||||
public function SetNonPublicProperty($oObject, string $sProperty, $value)
|
||||
{
|
||||
$oProperty = $this->GetProperty(get_class($oObject), $sProperty);
|
||||
$oProperty->setValue($oObject, $value);
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Hook;
|
||||
|
||||
require_once __DIR__ . '/../../../../approot.inc.php';
|
||||
|
||||
use PHPUnit\Runner\AfterLastTestHook;
|
||||
use PHPUnit\Runner\BeforeFirstTestHook;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
* Class TestsRunStartHook
|
||||
*
|
||||
* IMPORTANT: This will no longer work in PHPUnit 10.0 and there is no alternative for now, so we will have to migrate it when the time comes
|
||||
* @link https://localheinz.com/articles/2023/02/14/extending-phpunit-with-its-new-event-system/#content-hooks-event-system
|
||||
*
|
||||
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
|
||||
* @package Combodo\iTop\Test\UnitTest\Hook
|
||||
* @since N°6097 2.7.10 3.0.4 3.1.1
|
||||
*/
|
||||
class TestsRunStartHook implements BeforeFirstTestHook, AfterLastTestHook
|
||||
{
|
||||
/**
|
||||
* Use the modification time on this file to check whereas it is newer than the requirements in a test case
|
||||
*
|
||||
* @return string Abs. path to a file generated when the global tests run starts.
|
||||
*/
|
||||
public static function GetRunStartedFileAbsPath(): string
|
||||
{
|
||||
// Note: This can't be put in the cache-<ENV> folder as we have multiple <ENV> running across the test cases
|
||||
// We also don't want to put it in the unit tests folder as it is not supposed to be writable
|
||||
return APPROOT.'data/.php-unit-tests-run-started';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function executeBeforeFirstTest(): void
|
||||
{
|
||||
// Create / change modification timestamp of file marking the beginning of the tests run
|
||||
touch(static::GetRunStartedFileAbsPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function executeAfterLastTest(): void
|
||||
{
|
||||
// Cleanup of file marking the beginning of the tests run
|
||||
if (file_exists(static::GetRunStartedFileAbsPath())) {
|
||||
unlink(static::GetRunStartedFileAbsPath());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -9,9 +9,13 @@ namespace Combodo\iTop\Test\UnitTest\Service;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
|
||||
use IssueLog;
|
||||
use LogChannels;
|
||||
use MFCoreModule;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use ReflectionClass;
|
||||
use RunTimeEnvironment;
|
||||
use utils;
|
||||
|
||||
|
||||
/**
|
||||
@@ -25,62 +29,140 @@ use RunTimeEnvironment;
|
||||
class UnitTestRunTimeEnvironment extends RunTimeEnvironment
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $aCustomDatamodelFiles = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sSourceEnv;
|
||||
|
||||
public function __construct($sSourceEnv, $sTargetEnv)
|
||||
{
|
||||
parent::__construct($sTargetEnv);
|
||||
$this->sSourceEnv = $sSourceEnv;
|
||||
}
|
||||
|
||||
public function GetEnvironment(): string
|
||||
{
|
||||
return $this->sFinalEnv;
|
||||
}
|
||||
|
||||
public function IsUpToDate()
|
||||
{
|
||||
clearstatcache();
|
||||
$fLastCompilationTime = filemtime(APPROOT.'env-'.$this->sFinalEnv);
|
||||
$aModifiedFiles = [];
|
||||
$this->FindFilesModifiedAfter($fLastCompilationTime, APPROOT.'datamodels/2.x', $aModifiedFiles);
|
||||
$this->FindFilesModifiedAfter($fLastCompilationTime, APPROOT.'extensions', $aModifiedFiles);
|
||||
$this->FindFilesModifiedAfter($fLastCompilationTime, APPROOT.'data/production-modules', $aModifiedFiles);
|
||||
foreach ($this->GetCustomDatamodelFiles() as $sCustomDatamodelFile) {
|
||||
if (filemtime($sCustomDatamodelFile) > $fLastCompilationTime) {
|
||||
$aModifiedFiles[] = $sCustomDatamodelFile;
|
||||
}
|
||||
}
|
||||
if (count($aModifiedFiles) > 0) {
|
||||
echo "The following files have been modified after the last compilation:\n";
|
||||
foreach ($aModifiedFiles as $sFile) {
|
||||
echo " - $sFile\n";
|
||||
}
|
||||
}
|
||||
return (count($aModifiedFiles) === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
|
||||
{
|
||||
$aRet = parent::GetMFModulesToCompile($sSourceEnv, $sSourceDir);
|
||||
|
||||
/** @var string[] $aDeltaFiles Referential of loaded deltas. Mostly to avoid duplicates. */
|
||||
$aDeltaFiles = [];
|
||||
foreach (get_declared_classes() as $sClass) {
|
||||
// Filter on classes derived from this \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCaseItopCustomDatamodelTestCase
|
||||
if (false === is_a($sClass, ItopCustomDatamodelTestCase::class, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$oReflectionClass = new ReflectionClass($sClass);
|
||||
$oReflectionMethod = $oReflectionClass->getMethod('GetDatamodelDeltaAbsPath');
|
||||
|
||||
// Filter on classes with an actual XML delta (eg. not \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase and maybe some other deriving from a class with a delta)
|
||||
if ($oReflectionMethod->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase $oTestClassInstance */
|
||||
$oTestClassInstance = new $sClass();
|
||||
|
||||
// Check test class is for desired environment
|
||||
if ($oTestClassInstance->GetTestEnvironment() !== $this->sFinalEnv) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check XML delta actually exists
|
||||
$sDeltaFile = $oTestClassInstance->GetDatamodelDeltaAbsPath();
|
||||
if (false === is_file($sDeltaFile)) {
|
||||
$this->fail("Could not prepare '$this->sFinalEnv' as the XML delta file '$sDeltaFile' (used in $sClass) does not seem to exist");
|
||||
}
|
||||
|
||||
// Avoid duplicates
|
||||
if (in_array($sDeltaFile, $aDeltaFiles)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prepare fake module name for delta
|
||||
$sDeltaName = preg_replace('/[^\d\w]/', '', $sDeltaFile);
|
||||
// Note: We can't use \MFDeltaModule as we can't specify the ID which leads to only 1 delta being applied... In the future we might introduce a new MFXXXModule, but in the meantime it feels alright (GLA / RQU)
|
||||
$oDelta = new MFCoreModule($sDeltaName, $sDeltaName, $sDeltaFile);
|
||||
|
||||
IssueLog::Debug('XML delta found for unit tests', static::class, [
|
||||
'Unit test class' => $sClass,
|
||||
'Delta file path' => $sDeltaFile,
|
||||
]);
|
||||
|
||||
$aDeltaFiles[] = $sDeltaFile;
|
||||
$aRet[$sDeltaName] = $oDelta;
|
||||
foreach ($this->GetCustomDatamodelFiles() as $sDeltaFile) {
|
||||
$sDeltaId = preg_replace('/[^\d\w]/', '', $sDeltaFile);
|
||||
$sDeltaName = basename($sDeltaFile);
|
||||
$sDeltaDir = dirname($sDeltaFile);
|
||||
$oDelta = new MFCoreModule($sDeltaName, "$sDeltaDir/$sDeltaName", $sDeltaFile);
|
||||
$aRet[$sDeltaId] = $oDelta;
|
||||
}
|
||||
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
public function GetCustomDatamodelFiles()
|
||||
{
|
||||
if (!is_null($this->aCustomDatamodelFiles)) {
|
||||
return $this->aCustomDatamodelFiles;
|
||||
}
|
||||
$this->aCustomDatamodelFiles = [];
|
||||
|
||||
// Search for the PHP files implementing the method GetDatamodelDeltaAbsPath
|
||||
// and extract the delta file path from the method
|
||||
foreach(['unitary-tests', 'integration-tests'] as $sTestDir) {
|
||||
// Iterate on all PHP files in subdirectories
|
||||
// Note: grep is not available on Windows, so we will use the PHP Reflection API
|
||||
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__."/../../$sTestDir")) as $oFile) {
|
||||
if ($oFile->isDir()){
|
||||
continue;
|
||||
}
|
||||
if (pathinfo($oFile->getFilename(), PATHINFO_EXTENSION) !== 'php') {
|
||||
continue;
|
||||
}
|
||||
$sFile = $oFile->getPathname();
|
||||
$sContent = file_get_contents($sFile);
|
||||
if (strpos($sContent, 'GetDatamodelDeltaAbsPath') === false) {
|
||||
continue;
|
||||
}
|
||||
$sClass = '';
|
||||
$aMatches = [];
|
||||
if (preg_match('/namespace\s+([^;]+);/', $sContent, $aMatches)) {
|
||||
$sNamespace = $aMatches[1];
|
||||
$sClass = $sNamespace.'\\'.basename($sFile, '.php');
|
||||
}
|
||||
if (preg_match('/\s+class\s+([^ ]+)\s+/', $sContent, $aMatches)) {
|
||||
$sClass = $sNamespace.'\\'.$aMatches[1];
|
||||
}
|
||||
if ($sClass === '') {
|
||||
continue;
|
||||
}
|
||||
require_once $sFile;
|
||||
$oReflectionClass = new ReflectionClass($sClass);
|
||||
if ($oReflectionClass->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
// Check if the class extends ItopCustomDatamodelTestCase
|
||||
if (!$oReflectionClass->isSubclassOf(ItopCustomDatamodelTestCase::class)) {
|
||||
continue;
|
||||
}
|
||||
/** @var \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase $oTestClassInstance */
|
||||
$oTestClassInstance = new $sClass();
|
||||
if ($oTestClassInstance->GetTestEnvironment() !== $this->sFinalEnv) {
|
||||
continue;
|
||||
}
|
||||
$sDeltaFile = $oTestClassInstance->GetDatamodelDeltaAbsPath();
|
||||
if (!is_file($sDeltaFile)) {
|
||||
throw new \Exception("Unknown delta file: $sDeltaFile, from test class '$sClass'");
|
||||
}
|
||||
if (!in_array($sDeltaFile, $this->aCustomDatamodelFiles)) {
|
||||
$this->aCustomDatamodelFiles[] = $sDeltaFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->aCustomDatamodelFiles;
|
||||
}
|
||||
|
||||
private function FindFilesModifiedAfter(float $fReferenceTimestamp, string $sPathToScan, array &$aModifiedFiles)
|
||||
{
|
||||
if (!is_dir($sPathToScan)) {
|
||||
return;
|
||||
}
|
||||
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($sPathToScan)) as $oFile) {
|
||||
if ($oFile->isDir()) {
|
||||
continue;
|
||||
}
|
||||
if (filemtime($oFile->getPathname()) > $fReferenceTimestamp) {
|
||||
$aModifiedFiles[] = $oFile->getPathname();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0">
|
||||
<classes>
|
||||
<class id="UserRequest">
|
||||
<fields>
|
||||
<field id="status" xsi:type="AttributeEnum">
|
||||
<always_load_in_tables>true</always_load_in_tables>
|
||||
<sort_type>rank</sort_type>
|
||||
<values>
|
||||
<value id="New_org_name_with_quote" _delta="define">
|
||||
<code>New'status</code>
|
||||
<rank>32</rank>
|
||||
</value>
|
||||
</values>
|
||||
</field>
|
||||
</fields>
|
||||
</class>
|
||||
</classes>
|
||||
</itop_design>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user